File: updateCopyright.py

package info (click to toggle)
oscar 1.5.3-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 27,508 kB
  • sloc: cpp: 80,911; ansic: 6,910; python: 1,727; sh: 1,044; xml: 150; javascript: 74; makefile: 34; awk: 18
file content (371 lines) | stat: -rw-r--r-- 13,819 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
#!/usr/bin/env python3
"""
Copyright (c) 2023-2024 The OSCAR Team

Find text files with the word "Copyright" ending with a year (4 digits) range like 2019-2022 and then
followed by a string containing OSCAR. The script changes the 2 year to the current year.
This script will search all files in the folder where the script is run:
1) From the Git top-level folder. The script will also access the translation files and Build file.
2) From the oscar folder (that contains oscar.pro). The script will also access the code files.
"""

import os
import re
import subprocess
import time
import filecmp
import inspect
import sys

current_year = time.localtime().tm_year
top_dir = subprocess.check_output(['git', 'rev-parse', '--show-toplevel']).decode('utf-8').strip()
relative_sync_dir = os.path.join(top_dir, '..')
verbose = False
changed = False
list = False
list_ignored = False
testExecution = False
debug = False
single = True

def validStr(str) :
    if str is None: return False
    tmp = not str
    return not tmp
    
def validYear(year) :
    if year is None: return False
    try:
        tmp = int(year)
    except ValueError:
        return False
    tmp = int(year)
    return  ((tmp > 2000) and (tmp < 2099) )

class Stats:
    def __init__(self, field1 , field2 , field3 , field4 , field5 ):
        self.files_date_changed              = field1
        self.files_date_already_changed      = field2
        self.files_needing_inspection        = field3
        self.files_ignored                   = field4
        self.files_total                     = field5

statistics = Stats(0,0,0,0,0)

class LineInfo:
    def __init__(self, field1 , field2 , field3 , field4 , field5 , field6 , field7 , field8):
        self.lineNumber     = field1        ## read only
        self.line           = field2        
        self.baseName       = field3        ## read only
        self.fileModified   = field4        ## numberlines Modified
        self.lineModified   = field5        ## True if line changed
        self.validSignature = field6        ## only set to true
        self.needReview     = field7        ## only set to true
        self.countUpdated   = field8        ## only set to true

def processLine(lineInfo):
    lineInfo.lineModified = False;
    baseNameLine = f"{lineInfo.baseName}[{lineInfo.lineNumber}]"
    baseNameLineJ = baseNameLine.ljust(30)
    
    ## read only properties
    global relative_sync_dir, current_year, verbose , list , debug , statstics

    #{ limit processing line 
    ## where * is any string 
    ## SOURCE:               * Copyright (c) YEAR - YEAR The OSCAR Team *
    ## Modifiable:           * Copyright * YEAR - YEAR The OSCAR Team *
    ## Modifiable:           * YEAR - YEAR The OSCAR Team *
    ## Modifiable:           * YEAR - YEAR OSCAR Team *
    ## Modifiable/WARNING:   * YEAR - YEAR OSCAR *
    ## WARNING:              * YEAR  OSCAR *
    ## WARNING:              * copyright * OSCAR *

    ## find copyright or year
    next = 0;
    pattern = r'copyright'
    copyright = False
    
    match = re.search(pattern,lineInfo.line,re.IGNORECASE)
    if (match) :
        copyright = True
        ###next = match.end()

    pattern = r'(^.*?)((\d{4})\s*(-)?\s*)(\d{4})?(\s*(The)?\s*(OSCAR)\s*(Team)?\b)(.*$)'   #works
    match = re.search(pattern,lineInfo.line,re.IGNORECASE)
    if match :  ## match 
        ## match of oscar copyright signature
        #{
        before        = match.group(1)
        year1dash     = match.group(2)
        year1         = match.group(3)
        dash          = match.group(4)
        year2         = match.group(5)
        theoscarteam  = match.group(6)
        the           = match.group(7)
        oscar         = match.group(8)
        team          = match.group(9)
        after         = match.group(10)

        if debug :
            sum = f"""
            before          1  {before} 
            year1dash       2  {year1dash} 
            year1           3  {year1} 
            dash            4  {dash} 
            year2           5  {year2} 
            TheOscarTeam    6  {theoscarteam} 
            The             7  {the} 
            OSCAR           8  {oscar} 
            Team            9  {team} 
            after           10 {after} 
            """
            print(sum);
        newCopyright = f"{year1dash}{current_year}{theoscarteam}"
        newLine = f"{before}{newCopyright}{after}\n"

        # Note  OSCAR is always valid and is assumed to be true
        validOscarCopyright   =  validYear(year1) and validStr(dash) and validYear(year2) 

        if (validOscarCopyright) :
            if not lineInfo.needReview : lineInfo.needReview = not ( copyright or validStr(the) or validStr(team) )
            fileValidOscarCopyright = True
            if (str(year2)==str(current_year)) :
                if not lineInfo.countUpdated :
                    statistics.files_date_already_changed += 1
                    lineInfo.countUpdated = True
                if verbose : print(f"  Already Modified:  {baseNameLineJ}")
                return;
            else :  ## need to modify date
                if not lineInfo.countUpdated :
                    statistics.files_date_changed += 1
                    lineInfo.countUpdated = True
                if verbose : print(f"  File Modified:     {baseNameLineJ}{newLine}",end='')
                lineInfo.lineModified = True;
                lineInfo.fileModified += 1;
                if debug :
                    print(lineInfo.line)
                    print(newLine)
                lineInfo.line = newLine
                return;
        #} end match of oscar copyright signature
    #} End limit processing line 

def processFile(filename):
    """
    Process the file to update the copyright year if necessary.
    Args:
    filename: name of the file to be processed
    """
    global relative_sync_dir, current_year, verbose , list , debug , statistics

    statistics.files_total =  statistics.files_total +1
    relativeFileName = os.path.relpath(filename, relative_sync_dir)
    baseName = os.path.basename(filename)
    lines = []
    lineNumber = 0;
    fileValidOscarCopyright = False
    needReview = False

    ##if debug : print(f" processFile {baseName}")

    lineInfo = LineInfo(0 , "" , baseName , 0 , False , False ,False , False);
    codeFile = baseName.endswith(".cpp") or baseName.endswith(".h") 
    try:
    #{ try loop
        ##  skip files not be changed.
        ##  only do .h and .cpp files and not third party software or auto generated files.
        if ( 
            ## Folder that should be excluded
            ("thirdparty" in filename.split(os.path.sep)) or ("tests" in filename.split(os.path.sep)) or ("git_info.h" == baseName )
            ## file types that should be excluded
            or (not codeFile)
            ## for test or (not ("aboutdialog.h" == baseName or  "newprofile.cpp" == baseName) )
            )    :
            statistics.files_ignored += 1
            lineInfo.countUpdated = True     ## insure a file is counted only once.
            if verbose or list_ignored: print(f"  Ignored:           {relativeFileName}")
            return;
        ## Common encodings to try: utf-8 ,  latin-1 , iso-8859-1 , cp1252 , utf-16 , big5 , gb18030 .
        if debug : print(f" processFile {baseName}")
        with open(filename, 'r+' , encoding="latin-1") as file_handle:   ## latin-1 works  utf-8 fails
            #{ start of processing all lines
            ## only search until 1st signature is found. except newprofile where copyright is displayed
            single = not ( "newprofile.cpp" == baseName )  
            if (list)  :
                print(f" Opened {relativeFileName}")
            elif (not single or lineInfo.fileModified == 0) :
                for line in file_handle:
                    #{ start of processing line
                        lineNumber += 1
                        lineInfo.lineModified  = False
                        lineInfo.lineNumber = lineNumber;
                        lineInfo.line = line;
                        processLine(lineInfo);
                        if lineInfo.lineModified : 
                            lines.append(lineInfo.line)
                        else :
                            lines.append(line)
                    #} end processing line
                if ( lineInfo.needReview or (codeFile and not lineInfo.countUpdated) ) :
                    print(f"  Copyright Check    {relativeFileName}")
                    if not lineInfo.countUpdated : 
                        statistics.files_needing_inspection += 1
                        lineInfo.countUpdated = True
                if lineInfo.fileModified == 0:
                    if not lineInfo.countUpdated : ## already modified is excluded here.
                        statistics.files_ignored += 1
                        lineInfo.countUpdated = True
                        if verbose or list_ignored: print(f"  Ignored:           {relativeFileName}")
                    return;
                if testExecution : return
                file_handle.seek(0)
                file_handle.truncate()
                file_handle.write(''.join(lines))
                file_handle.flush()
                os.fsync(file_handle.fileno())
                return
            #} end of processing all lines
    except IOError as e:
        print(f"  File Open Error:   {relativeFileName}")
    #} try loop

def isBinaryFile(file_path):
    with open(file_path, 'rb') as f:
        for block in f:
            if b'\0' in block:
                return True
    return False

"""
def xisBinaryFile(file_path):
    result = subprocess.run(['file', '--mime-encoding', file_path], capture_output=True, text=True)
    return 'binary' in result.stdout
"""

def handleFile(file_path):
    """
    Process the file if it is a text file.
    Args:
    file_path (str): The path of the file to be processed.
    """
    if os.path.isfile(file_path) :
        if not isBinaryFile(file_path):
            processFile(file_path)

def help_menu():
    """
    Display the help menu.
    """
    help_msg = """
    Help Menu
    {}

    # uses the current year to modifed files with copyright.
    # This script modifies the first line with the following signature (case insensative).
        YYYY and ZZZZ are sequences of 4 digits representing year
        asterisk " means any sequence of charaters.
    # signature: * YYYY-ZZZZ * OSCAR *
    # The script will only change ZZZZ to the current year. No file size change.
    # No other lines will be modfied.

    --help          displays help message
    --execute       allows script to execute
    -v --verbose    displays status for each file accessed
    --changed       displays each line modified
    --list          displays filenames to be search and exits
    --ignored       List files that are ignored.
    --test          Execute code but skips the actual file write
    --year <year>   Overrides system year 
    --code          starts working at OSCAR-code/oscar
    --base          starts working at OSCAR-code
    <folderName>    starts working at OSCAR-code/folderName
    defaultFolder   starts working at the current folder
    """.format(__file__)
    print(help_msg)
    exit()

####################################################################################################

start_dir = os.getcwd().rstrip('\n')
options = ""
execute = None
verbose = False
list = False

while len(sys.argv) > 1:
    arg = sys.argv.pop(1)
    options += " " + arg
    if arg == '--help':
        help_menu()
    elif arg == '--debug' or arg == '-d':
        debug = True
    elif arg == '--verbose' or arg == '-v':
        verbose = True
    elif arg == '--changed':
        changed = True
    elif arg == '--year':
        year = 0;
        if len(sys.argv) > 1:
            tmp = sys.argv.pop(1)
            try:
                year = int(tmp)
                if not validYear(year) :
                    print("Invalid year: " + tmp)
                    help_menu()
                current_year = year;
            except ValueError:
                year =0
                print("Invalid year: " + tmp)
                help_menu()
    elif arg == '--test':
        testExecution = True
    elif arg == '--list':
        list = True
    elif arg == '--ignored':
        list_ignored = True
    elif arg == '--code':
        ## starts form topLevelFolder/oscar
        start_dir = os.path.join(top_dir, 'oscar')
    elif arg == '--execute':
        execute = True
    elif arg == '--base':
        start_dir = top_dir
    else:
        tmp_dir = os.path.join(top_dir, arg)
        if os.path.isdir(tmp_dir):
            start_dir = tmp_dir
        else:
            print("Invalid Parameter: " + arg)
            help_menu()

relativeStartDir = os.path.relpath(start_dir, relative_sync_dir)

if execute is None:
    print("Requires --execute parameter to execute script")
    help_menu()
    exit()


# Call the walk function to process all files recursively
for root, dirs, files in os.walk(start_dir):
    for file in files:
        filename=os.path.join(root, file)
        if os.path.isfile(filename) and not isBinaryFile(filename):
            processFile(filename)

print(f"{os.path.basename(__file__)} {relativeStartDir} {options}")

if list : exit()

summary = f"""
Summary of Text Files searched               {statistics.files_total}
Number of files with date modified:          {statistics.files_date_changed}
Number of files with date already modified:  {statistics.files_date_already_changed}
Number of files with Copyright Check:        {statistics.files_needing_inspection}
Number of files ignored                      {statistics.files_ignored}
"""

print(summary)