1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 """
21 This module implements the necessary routines used to embed license
22 informations into source code project files.
23 The package installation also installs a command line utility along with the
24 license files in textual format in a hidden directory inside the user home.
25
26 @author: Marco Bartolini
27 @contact: marco.bartolini@gmail.com
28 @version: 1.0
29 """
30
31 import os
32 import getpass
33 import datetime
34 import re
35
38 """
39 Default constructor sets default values for cato options.
40 """
41 self.home_path = os.path.expanduser("~")
42 self.cato_dir = os.path.join(self.home_path, ".cato")
43 self.license_dir = os.path.join(self.cato_dir, "licenses")
44 self.end_phrase = "END OF TERMS AND CONDITIONS"
45
46 self.comment_syntax = {"c" : "//",
47 "cpp" : "//",
48 "cc" : "//",
49 "h" : "//",
50 "hpp" : "//",
51 "py" : "#",
52 "java" : "//",
53 "f" : "!",
54 "rb" : "#",
55 "default" : "*",
56 }
57
58 self.license_tags = {"<year>" : str(datetime.datetime.now().year),
59 "<owner>" : getpass.getuser(),
60 "<email>" : getpass.getuser() + "@example.com",
61 }
62
63 self.eol = "\n"
64 self.loaded = False
65
67 """
68 Parse a license file and split it into the full flagged license to
69 be included into a \"LICENSE\" file and an embedded version to be
70 included at the beginning of each source file. It also substitute the
71 license tags in the license text.
72 @type lic_name: string
73 @param lic_name: license file name key
74 @return: (full_licensem, embedded_license)
75 """
76 licenses = {}
77 for license_file in os.listdir(self.license_dir):
78 licenses[license_file[:license_file.rfind(".")]] = \
79 os.path.abspath(os.path.join(self.license_dir, license_file))
80 try:
81 with open(licenses[lic_name], "rt") as f:
82 text = f.read()
83 except KeyError:
84 raise KeyError("License " + lic_name + " not found in " +
85 self.license_dir)
86 for tag, substitution in self.license_tags.iteritems():
87 pattern = re.compile(tag)
88 text = re.subn(pattern, substitution, text)[0]
89 license_parts = text.split(self.end_phrase)
90 extended_license = license_parts[0]
91 if len(license_parts) == 1:
92 embedded_license = license_parts[0].split(self.eol)
93 else:
94 embedded_license = license_parts[1].split(self.eol)
95 return (extended_license, embedded_license)
96
98 """
99 Embed the license in the given source code file. The license is inserted
100 where the first empty line is found. If no empty line is present no license
101 is embedded.
102 @param filename: file path
103 @param embedded_license: the embedded license as obtained by
104 L{parse_license} function
105 @return: True if a license has been embedded
106 """
107 extension = filename.split(".")[-1]
108 comment = self.comment_syntax.get(extension, self.comment_syntax["default"])
109 embedded = False
110 with open(filename, "rt") as input:
111 with open(filename + ".cato", "wt") as output:
112 for input_line in input:
113 output.write(input_line)
114 if not embedded and input_line.strip() == "":
115 for embed_line in embedded_license:
116 output.write(comment + embed_line + self.eol)
117 embedded = True
118 os.rename(filename + ".cato", filename)
119 return embedded
120
121 - def patch_dir(self, dirname, extended_license):
122 """
123 Insert a license file into a directory naming it \"LICENSE\".
124 @param dirname: the target directory path
125 @param extended_license: the license text as obtained by L{parse_license}
126 function
127 """
128 with open(os.path.join(dirname, "LICENSE"), "wt") as license_file:
129 license_file.write(extended_license)
130
132 """
133 Function that parses command line options and invokes cato methods on
134 selected files. Use --help for online help.
135 """
136 from optparse import OptionParser
137 from ConfigParser import SafeConfigParser
138 import os
139 import fnmatch
140
141 cato_licenser = Cato()
142
143
144 scp = SafeConfigParser()
145 scp.read(os.path.join(cato_licenser.cato_dir, "cato.cfg"))
146 if 'Cato' in scp.sections():
147 if 'owner' in scp.options('Cato'):
148 cato_licenser.license_tags['<owner>'] = scp.get('Cato', 'owner')
149 if 'email' in scp.options('Cato'):
150 cato_licenser.license_tags['<email>'] = scp.get('Cato', 'email')
151 if 'end_phrase' in scp.options('Cato'):
152 cato_licenser.end_phrase = scp.get('Cato', 'end_phrase')
153 if 'Comments' in scp.sections():
154 for c in scp.options('Comments'):
155 cato_licenser.comment_syntax[c] = scp.get('Comments', c)
156
157
158 op = OptionParser()
159 op.add_option("--list", action="store_true", default=False,
160 dest="lic_list", help="Lists available licenses and quit")
161 op.add_option("-l", "--license", dest="license", help="The license name, use --list to list all licenses available")
162 op.add_option("-o", "--owner", dest="owner", help="the copyright owner")
163 op.add_option("-e", "--email", dest="email", help="email contact of the copyright owner")
164 op.add_option("-y", "--year", dest="year", help="the copyright year, defaults to current year")
165 op.add_option("-d", "--directory", dest="directory", help="a target directory where to find sources and add a LICENSE file")
166 op.add_option("-r", action="store_true", default="False", dest="recursive",
167 help="only with -d. If set recursively parses directory tree")
168 op.add_option("-c", "--comment", dest="comment", help="overrides comment syntax")
169 op.set_usage("cato -l gpl-3.0 -o \"John Doe\" -e john@doe.com -y 2012 *.py")
170 op.set_description('''
171 cato is free software for applying licenses to your source code
172 files. You can customize cato changing the cato.cfg file that you
173 find in ~/.cato/ and adding license textual files in
174 ~/cato/licenses/.
175
176 License files can contain <owner> and <email>
177 textual tags which will be replaced with cato informations.
178
179 Embedded license versions will be applied on the first empty line of
180 each source file scanned, if no empty line is found, no license is
181 applied.
182
183 In its normal behaviour cato apply the license to the files given as
184 arguments on the command line, while using -d option it scans the
185 given directory for file extensions specified as command line
186 arguments. If -r is provided in conjunction with -d, all the
187 directory tree is scanned starting from the supplied dir.
188 ''')
189 options, args = op.parse_args(args)
190
191
192 licenses = []
193 for lic_file in os.listdir(cato_licenser.license_dir):
194 licenses.append(lic_file[:lic_file.rfind('.')])
195
196
197 if options.lic_list:
198 for l in licenses:
199 print l
200 return
201
202
203 if options.owner:
204 cato_licenser.license_tags['<owner>'] = options.owner
205 if options.year:
206 cato_licenser.license_tags['<year>'] = options.year
207 if options.email:
208 cato_licenser.license_tags['<email>'] = options.email
209 if options.comment:
210 cato_licenser.comment_syntax = {'default': options.comment}
211
212
213 if not options.license:
214 license = licenses[0]
215 else:
216 license = options.license
217 (extended_lic, embedded_lic) = cato_licenser.parse_license(license)
218
219
220 if options.directory:
221 dir = options.directory
222 cato_licenser.patch_dir(dir, extended_lic)
223 if options.recursive:
224 for root, dirs, files in os.walk(dir):
225 for f in files:
226 match = False
227 for a in args:
228 if fnmatch.fnmatch(f, "*." + a):
229 match = True
230 if match:
231 print "Applying license to file: " + f
232 cato_licenser.patch_file(os.path.join(root, f), embedded_lic)
233 else:
234 for f in os.listdir(dir):
235 match = False
236 for a in args:
237 if fnmatch.fnmatch(f, "*." + a):
238 match = True
239 if match:
240 print "Applying license to file: " + f
241 cato_licenser.patch_file(os.path.join(root, f), embedded_lic)
242 else:
243 for f in args:
244 print "Applying license to file: " + f
245 cato_licenser.patch_file(f, embedded_lic)
246