Skip to content

Commit 2c4025c

Browse files
committedJul 9, 2022
Added initial repository contents and updated the readme.
1 parent 61917cf commit 2c4025c

File tree

3 files changed

+791
-0
lines changed

3 files changed

+791
-0
lines changed
 

‎README.md ‎README.rst

File renamed without changes.

‎cleanup_source

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/bin/bash
2+
#-*-bash-*-#############################################################################################################
3+
# Copyright 2022, Inesonic, LLC
4+
#
5+
# GNU Public License, Version 3:
6+
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
7+
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
8+
# version.
9+
#
10+
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
11+
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12+
# details.
13+
#
14+
# You should have received a copy of the GNU General Public License along with this program. If not, see
15+
# <https://www.gnu.org/licenses/>.
16+
########################################################################################################################
17+
# Small shell script the identifies files with tabs and/or trailing whitespace and converts tabs to spaces and removes
18+
# the trailing whitespace.
19+
#
20+
21+
TEMPORARY_DIRECTORY=$(mktemp -d "${TMPDIR:-/tmp/}$(basename $0).XXXXXXXX")
22+
FILE_LIST=${TEMPORARY_DIRECTORY}/files_to_scrub
23+
SORTED_FILE_LIST=${TEMPORARY_DIRECTORY}/sorted_files_to_scrub
24+
WORK_FILE=${TEMPORARY_DIRECTORY}/in_work
25+
26+
grep -l --recursive -P '\t' * >${FILE_LIST}
27+
grep -l --recursive -P '\s+$' * >>${FILE_LIST}
28+
cat ${FILE_LIST} | grep -v 'vendor/' | sort | uniq >${SORTED_FILE_LIST}
29+
30+
for FILE in $(cat ${SORTED_FILE_LIST});
31+
do
32+
MIME_TYPE=$(file --mime "${FILE}" | sed -e 's/^[^:]*: *//' | sed -e 's/;.*//')
33+
if echo ${MIME_TYPE} | grep -q -e '^text/.*';
34+
then
35+
expand --tabs=4 <"${FILE}" | sed -e 's/\s+$//' >${WORK_FILE}
36+
mv ${WORK_FILE} "${FILE}"
37+
echo Processed ${FILE}
38+
fi
39+
done

‎modify_license.py

+752
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,752 @@
1+
#!/usr/bin/env python3
2+
#-*-python-*-##################################################################
3+
# Copyright 2022 Inesonic, LLC
4+
#
5+
# GNU Public License, Version 3:
6+
# This program is free software: you can redistribute it and/or modify it
7+
# under the terms of the GNU General Public License as published by the Free
8+
# Software Foundation, either version 3 of the License, or (at your option)
9+
# any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful, but WITHOUT
12+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14+
# more details.
15+
#
16+
# You should have received a copy of the GNU General Public License along
17+
# with this program. If not, see <https://www.gnu.org/licenses/>.
18+
###############################################################################
19+
20+
"""
21+
Command line tool you can use to update source file licensing terms.
22+
23+
"""
24+
25+
###############################################################################
26+
# Import:
27+
#
28+
29+
import os
30+
import shutil
31+
import sys
32+
import datetime
33+
import re
34+
import textwrap3
35+
import argparse
36+
37+
###############################################################################
38+
# Globals:
39+
#
40+
41+
VERSION = "1.0"
42+
"""
43+
The script version number.
44+
45+
"""
46+
47+
DESCRIPTION = """
48+
Command line tool you can use to modify source file license terms.
49+
50+
"""
51+
"""
52+
The comamnd description.
53+
54+
"""
55+
56+
DEFAULT_COLUMN_WIDTH = 79
57+
"""
58+
The default column width.
59+
60+
"""
61+
62+
LICENSE_TEXT = {
63+
"commercial" : {
64+
"header" : "Inesonic Commercial License",
65+
"text" : "All rights reserved."
66+
},
67+
"aion" : {
68+
"header" : "Aion End User License Agreement",
69+
"text" : "Use under the terms of the Aion End User License Agreement."
70+
},
71+
"mit" : {
72+
"header" : "MIT License",
73+
"text" : """
74+
Permission is hereby granted, free of charge, to any person obtaining a copy
75+
of this software and associated documentation files (the "Software"), to deal
76+
in the Software without restriction, including without limitation the rights
77+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
78+
copies of the Software, and to permit persons to whom the Software is furnished
79+
to do so, subject to the following conditions:
80+
81+
The above copyright notice and this permission notice shall be included in all
82+
copies or substantial portions of the Software.
83+
84+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
85+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
86+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
87+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
88+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
89+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
90+
SOFTWARE.
91+
"""
92+
},
93+
"gplv2" : {
94+
"header" : "GNU Public License, Version 2",
95+
"text" : """
96+
This program is free software; you can redistribute it and/or modify it under
97+
the terms of the GNU General Public License as published by the Free Software
98+
Foundation; either version 2 of the License, or (at your option) any later
99+
version.
100+
101+
This program is distributed in the hope that it will be useful, but WITHOUT
102+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
103+
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
104+
105+
You should have received a copy of the GNU General Public License along with
106+
this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
107+
Street, Fifth Floor, Boston, MA 02110-1301, USA.
108+
"""
109+
},
110+
"lgplv2" : {
111+
"header" : "GNU Lesser Public License, Version 2",
112+
"text" : """
113+
This library is free software; you can redistribute it and/or modify it under
114+
the terms of the GNU Lesser General Public License as published by the Free
115+
Software Foundation; either version 2.1 of the License, or (at your option)
116+
any later version.
117+
118+
This library is distributed in the hope that it will be useful, but WITHOUT
119+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
120+
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
121+
details.
122+
123+
You should have received a copy of the GNU Lesser General Public License along
124+
with this library; if not, write to the Free Software Foundation, Inc., 51
125+
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
126+
"""
127+
},
128+
"gplv3" : {
129+
"header" : "GNU Public License, Version 3",
130+
"text" : """
131+
This program is free software: you can redistribute it and/or modify it under
132+
the terms of the GNU General Public License as published by the Free Software
133+
Foundation, either version 3 of the License, or (at your option) any later
134+
version.
135+
136+
This program is distributed in the hope that it will be useful, but WITHOUT ANY
137+
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
138+
PARTICULAR PURPOSE. See the GNU General Public License for more details.
139+
140+
You should have received a copy of the GNU General Public License along with
141+
this program. If not, see <https://www.gnu.org/licenses/>.
142+
"""
143+
},
144+
"lgplv3" : {
145+
"header" : "GNU Lesser Public License, Version 3",
146+
"text" : """
147+
This library is free software; you can redistribute it and/or modify it under
148+
the terms of the GNU Lesser General Public License as published by the Free
149+
Software Foundation; either version 3 of the License, or (at your option) any
150+
later version.
151+
152+
This library is distributed in the hope that it will be useful, but WITHOUT
153+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
154+
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
155+
details.
156+
157+
You should have received a copy of the GNU Lesser General Public License along
158+
with this library; if not, write to the Free Software Foundation, Inc., 51
159+
Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
160+
"""
161+
}
162+
}
163+
164+
BACKUP_DIRECTORY = "backup_license"
165+
"""
166+
The backup directory name.
167+
168+
"""
169+
170+
COPYRIGHT_DATE_RE = re.compile(
171+
r'(.*)Copyright(\s+)(2[0-9]{3})(\s*-\s*(2[0-9]{3}))?([, ].*)?'
172+
)
173+
"""
174+
Regular expression used to update copyright dates.
175+
176+
"""
177+
178+
COPYRIGHT_DATE_HEADER = re.compile(r'\s*[#*]?\s+Copyright\s+[0-9]{4}.*')
179+
"""
180+
Regular expression used to identify copyright dates. The first of these lines
181+
are preserved and placed at the top of the copyright region.
182+
183+
"""
184+
185+
LINE_START_RE = re.compile(r'(\s*[*#]*[^*#]).*')
186+
"""
187+
Regular expression used to identify how each line should be started.
188+
189+
"""
190+
191+
###############################################################################
192+
# Functions
193+
#
194+
195+
def modify_copyright_dates(file_content, wrap_column):
196+
"""
197+
Function that scans a file for copyright strings, modifying them as needed.
198+
199+
:param file_content:
200+
An array of file lines to be modified.
201+
202+
:param wrap_column:
203+
The maximum allowed line width in characters.
204+
205+
:return:
206+
Returns True on success. Returns false on error.
207+
208+
:type file_content: list
209+
:type wrap_count: int
210+
:rtype: bool
211+
212+
"""
213+
214+
current_year = str(datetime.date.today().year)
215+
216+
number_lines = len(file_content)
217+
i = 0;
218+
while i < number_lines:
219+
l = file_content[i]
220+
match = COPYRIGHT_DATE_RE.fullmatch(l)
221+
if match:
222+
change_made = True
223+
actual_change_made = False
224+
while change_made:
225+
change_made = False
226+
227+
groups = match.groups();
228+
start_year = groups[2]
229+
if start_year != current_year:
230+
end_year = groups[4]
231+
if end_year is None or end_year != current_year:
232+
pre_string = groups[0]
233+
post_string = groups[5]
234+
space_between = groups[1]
235+
236+
if post_string is None:
237+
post_string = ''
238+
239+
l = (
240+
pre_string
241+
+ 'Copyright'
242+
+ space_between
243+
+ start_year
244+
+ ' - '
245+
+ current_year
246+
+ post_string
247+
)
248+
249+
actual_change_made = True
250+
change_made = True
251+
252+
match = COPYRIGHT_DATE_RE.fullmatch(l)
253+
254+
if actual_change_made:
255+
file_content[i] = l
256+
if len(l) > wrap_column:
257+
sys.stderr.write(
258+
"*** Warning: Line %d exceeds maximum line length.\n"
259+
" %s"%(
260+
i + 1,
261+
l
262+
)
263+
)
264+
265+
i += 1
266+
267+
return True
268+
269+
270+
def interesting_line(l, wrap_column):
271+
"""
272+
Method that determines if a line is interesting as a copyright start or end.
273+
274+
:param l:
275+
The line to be tested.
276+
277+
:param wrap_column:
278+
The maximum line length. Interesting lines must be at least 90% of this
279+
value.
280+
281+
:return:
282+
Returns True if this line marks the start/end of a copyright header.
283+
Returns False otherwise.
284+
285+
:type l: str
286+
:type wrap_column: int
287+
:rtype: bool
288+
289+
"""
290+
291+
result = (
292+
(len(l) >= 0.5 * wrap_column)
293+
and ( (l.count('*') >= 0.7 * len(l))
294+
or (l.count('#') >= 0.7 * len(l))
295+
)
296+
)
297+
298+
return result
299+
300+
301+
def update_license_header(
302+
file_content,
303+
wrap_column,
304+
license_list
305+
):
306+
"""
307+
Function that updates the license header data.
308+
309+
:param file_content:
310+
List of file lines.
311+
312+
:param wrap_column:
313+
The maximum line length in characters.
314+
315+
:param license_list:
316+
A list of licenses to include in the file header.
317+
318+
:return:
319+
Returns the updated file content or None on error.
320+
321+
:type file_content: list
322+
:type wrap_column: int
323+
:type license_list: list
324+
:rtype: list or None
325+
326+
"""
327+
328+
i = 0;
329+
number_lines = len(file_content)
330+
while i < number_lines and \
331+
not interesting_line(file_content[i], wrap_column) :
332+
i += 1
333+
334+
start_line = file_content[i]
335+
336+
i += 1
337+
copyright_region_start = i;
338+
339+
copyright_date_line = None
340+
line_start = None
341+
while i < number_lines and \
342+
not interesting_line(file_content[i], wrap_column) :
343+
l = file_content[i]
344+
if not copyright_date_line and COPYRIGHT_DATE_HEADER.match(l):
345+
copyright_date_line = l
346+
347+
if not line_start:
348+
match = LINE_START_RE.match(l)
349+
if match:
350+
line_start = match.group(1)
351+
352+
i += 1
353+
354+
copyright_region_end = i - 1
355+
356+
file_pre = file_content[:copyright_region_start]
357+
file_post = file_content[i:]
358+
359+
if copyright_date_line is not None:
360+
copyright_content = [ copyright_date_line ]
361+
else:
362+
copyright_content = []
363+
364+
indented_line_start = line_start + ' '
365+
maximum_text_width = wrap_column - len(indented_line_start)
366+
367+
for license in license_list:
368+
copyright_content.append(line_start.rstrip())
369+
header = LICENSE_TEXT[license]['header']
370+
text = LICENSE_TEXT[license]['text']
371+
372+
copyright_content.append(line_start + header + ':')
373+
374+
text_lines = [ l.strip() for l in text.split("\n") ]
375+
while text_lines[0] == '':
376+
text_lines = text_lines[1:]
377+
378+
while text_lines[-1] == '':
379+
text_lines = text_lines[:-1]
380+
381+
paragraph = ''
382+
for l in text_lines:
383+
if l != "":
384+
if paragraph == '':
385+
paragraph = l
386+
else:
387+
paragraph += ' ' + l
388+
else:
389+
if paragraph != '':
390+
wrapped = textwrap3.wrap(paragraph, maximum_text_width)
391+
for l in wrapped:
392+
copyright_content.append(
393+
indented_line_start + l.strip()
394+
)
395+
396+
copyright_content.append(indented_line_start)
397+
398+
paragraph = ''
399+
400+
if paragraph != '':
401+
wrapped = textwrap3.wrap(paragraph, maximum_text_width)
402+
for l in wrapped:
403+
copyright_content.append(indented_line_start + l.strip())
404+
405+
file_content = (
406+
file_pre
407+
+ copyright_content
408+
+ file_post
409+
)
410+
411+
return file_content
412+
413+
414+
def process_file(
415+
file_handle,
416+
verbose,
417+
license_list,
418+
modify_dates,
419+
create_backups,
420+
wrap_column
421+
):
422+
"""
423+
Function you can use to parse a single file.
424+
425+
:param file_handle:
426+
The file handle to the file to be processed.
427+
428+
:param verbose:
429+
If True, then verbose reporting will be generated.
430+
431+
:param license_list:
432+
An ordered list of licenses to be inserted into the source file header.
433+
434+
:param modify_dates:
435+
If True, then copyright dates in the file should be updated.
436+
437+
:param create_backups:
438+
If True, then a backup of the file should be created.
439+
440+
:param wrap_column:
441+
The maximum column width for the file.
442+
443+
:return:
444+
Returns True if the operation was successful. Returns False on error.
445+
446+
:type file_handle: file
447+
:type verbose: bool
448+
:type license_list: list
449+
:type modify_dates: bool
450+
:type create_backups: bool
451+
:type wrap_column: int
452+
:rtype: bool
453+
454+
"""
455+
456+
success = True
457+
458+
filename = os.path.abspath(file_handle.name)
459+
if verbose:
460+
sys.stdout.write("Processing %s:\n"%filename)
461+
462+
if create_backups:
463+
( filepath, basename ) = os.path.split(filename)
464+
465+
backup_path = os.path.join(filepath, BACKUP_DIRECTORY)
466+
467+
if not os.path.exists(backup_path):
468+
if verbose:
469+
sys.stdout.write(
470+
" Creating backup directory %s\n"%backup_path
471+
)
472+
473+
try:
474+
os.mkdir(backup_path)
475+
except:
476+
success = False
477+
478+
if not success:
479+
sys.stderr.write(
480+
"*** Could not create backup directory %s\n"
481+
" exiting...\n"%backup_path
482+
)
483+
elif not os.path.isdir(backup_path):
484+
sys.stderr.write(
485+
"*** Could not create backup directory %s, already exists "
486+
"as file\n"
487+
" exiting...\n"%backup_path
488+
)
489+
success = False
490+
491+
if success:
492+
backup_file = os.path.join(backup_path, basename)
493+
if verbose:
494+
sys.stdout.write(" Copying to %s\n"%backup_file)
495+
496+
try:
497+
shutil.copyfile(filename, backup_file)
498+
except:
499+
success = False
500+
501+
if not success:
502+
sys.stderr.write(
503+
"*** Could not copy file %s to %s\n"%(
504+
filename,
505+
backup_file
506+
)
507+
)
508+
509+
if success:
510+
if verbose:
511+
sys.stdout.write(" Reading.\n")
512+
try:
513+
file_lines = file_handle.readlines()
514+
except:
515+
success = False
516+
517+
if success:
518+
file_content = [ l.rstrip() for l in file_lines ]
519+
else:
520+
sys.stderr.write("*** Could not read file %s\n"%filename)
521+
522+
if success and modify_dates:
523+
if verbose:
524+
sys.stdout.write(" Updating copyright dates.\n")
525+
526+
success = modify_copyright_dates(file_content, wrap_column)
527+
528+
if success and license_list:
529+
if verbose:
530+
sys.stdout.write(" Updating licenses.\n")
531+
532+
new_file_content = update_license_header(
533+
file_content,
534+
wrap_column,
535+
license_list
536+
)
537+
538+
if new_file_content is None:
539+
success = False
540+
else:
541+
file_content = new_file_content
542+
543+
if success:
544+
file_handle.close()
545+
546+
if verbose:
547+
sys.stdout.write(" Writing updates.\n")
548+
549+
try:
550+
with open(filename, "w+") as file_handle:
551+
for l in file_content:
552+
file_handle.write(l + "\n")
553+
except Exception as e:
554+
sys.stderr.write(
555+
"*** Failed to write updates to %s: %s\n"%(
556+
filename,
557+
str(e)
558+
)
559+
)
560+
success = False
561+
562+
return success
563+
564+
###############################################################################
565+
# Main:
566+
#
567+
568+
success = True;
569+
570+
command_line_parser = argparse.ArgumentParser(description = DESCRIPTION)
571+
command_line_parser.add_argument(
572+
"-V",
573+
"--version",
574+
help = "You can use this switch to obtain the software release "
575+
"version.",
576+
action = "version",
577+
version = VERSION
578+
)
579+
580+
command_line_parser.add_argument(
581+
"-v",
582+
"--verbose",
583+
help = "You can use this switch to request verbose status updates.",
584+
action = "store_true",
585+
default = False,
586+
dest = "verbose"
587+
)
588+
589+
command_line_parser.add_argument(
590+
"-c",
591+
"--commercial",
592+
help = "You can use this switch to specify that Inesonic commercial "
593+
"license should be included.",
594+
action = "store_true",
595+
default = False,
596+
dest = "commercial"
597+
)
598+
599+
command_line_parser.add_argument(
600+
"-a",
601+
"--aion",
602+
help = "You can use this switch to specify that Inesonic Aion EULA should "
603+
"be included.",
604+
action = "store_true",
605+
default = False,
606+
dest = "aion"
607+
)
608+
609+
command_line_parser.add_argument(
610+
"-m",
611+
"--mit",
612+
help = "You can use this switch to specify that the MIT license should be "
613+
"included.",
614+
action = "store_true",
615+
default = False,
616+
dest = "mit_license"
617+
)
618+
619+
command_line_parser.add_argument(
620+
"-g",
621+
"--gplv2",
622+
help = "You can use this switch to specify that the GPLv2 license should "
623+
"be included.",
624+
action = "store_true",
625+
default = False,
626+
dest = "gplv2_license"
627+
)
628+
629+
command_line_parser.add_argument(
630+
"-l",
631+
"--lgplv2",
632+
help = "You can use this switch to specify that the LGPLv2 license should "
633+
"be included.",
634+
action = "store_true",
635+
default = False,
636+
dest = "lgplv2_license"
637+
)
638+
639+
command_line_parser.add_argument(
640+
"-G",
641+
"--gplv3",
642+
help = "You can use this switch to specify that the GPLv3 license should "
643+
"be included.",
644+
action = "store_true",
645+
default = False,
646+
dest = "gplv3_license"
647+
)
648+
649+
command_line_parser.add_argument(
650+
"-L",
651+
"--lgplv3",
652+
help = "You can use this switch to specify that the LGPLv3 license should "
653+
"be included.",
654+
action = "store_true",
655+
default = False,
656+
dest = "lgplv3_license"
657+
)
658+
659+
command_line_parser.add_argument(
660+
"-d",
661+
"--date",
662+
help = "You can use this switch to identify and adjust any copyright "
663+
"dates. Date strings will be identified by the regular expression "
664+
"Copyright 2[0-9]{3}(-2[0-9]{3})?.",
665+
action = "store_true",
666+
default = False,
667+
dest = "modify_dates"
668+
)
669+
670+
command_line_parser.add_argument(
671+
"-b",
672+
"--backup",
673+
help = "You can use this switch to indicate that a backup for each file "
674+
"should be created. Backups will be placed in a \"%s\" "
675+
"directory under the same filename."%BACKUP_DIRECTORY,
676+
action = "store_true",
677+
default = True,
678+
dest = "create_backups"
679+
)
680+
681+
command_line_parser.add_argument(
682+
"-w",
683+
"--wrap",
684+
help = "You can use this switch to specify the maximum column width for "
685+
"content. This script will make a best attempt to meet this "
686+
"requirement, throwing an error if the requirement can not be "
687+
"met. Note that this assumes the file uses spaces, not tabs.",
688+
type = int,
689+
default = DEFAULT_COLUMN_WIDTH,
690+
dest = "wrap"
691+
)
692+
693+
command_line_parser.add_argument(
694+
"files",
695+
help = "One or more files to be modified.",
696+
type = argparse.FileType('r', encoding='utf-8'),
697+
nargs = '+',
698+
)
699+
700+
arguments = command_line_parser.parse_args()
701+
702+
verbose = arguments.verbose
703+
commercial = arguments.commercial
704+
aion = arguments.aion
705+
mit_license = arguments.mit_license
706+
gplv2_license = arguments.gplv2_license
707+
lgplv2_license = arguments.lgplv2_license
708+
gplv3_license = arguments.gplv3_license
709+
lgplv3_license = arguments.lgplv3_license
710+
modify_dates = arguments.modify_dates
711+
create_backups = arguments.create_backups
712+
wrap_column = arguments.wrap
713+
files = arguments.files
714+
715+
license_list = []
716+
if commercial:
717+
license_list.append('commercial')
718+
719+
if aion:
720+
license_list.append('aion')
721+
722+
if mit_license:
723+
license_list.append('mit')
724+
725+
if gplv2_license:
726+
license_list.append('gplv2')
727+
728+
if lgplv2_license:
729+
license_list.append('lgplv2')
730+
731+
if gplv3_license:
732+
license_list.append('gplv3')
733+
734+
if lgplv3_license:
735+
license_list.append('lgplv3')
736+
737+
success = True
738+
for fh in files:
739+
if success:
740+
success = process_file(
741+
file_handle = fh,
742+
verbose = verbose,
743+
license_list = license_list,
744+
modify_dates = modify_dates,
745+
create_backups = create_backups,
746+
wrap_column = wrap_column,
747+
)
748+
749+
if success:
750+
exit(0)
751+
else:
752+
exit(1)

0 commit comments

Comments
 (0)
Please sign in to comment.