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/.\n
175 License files can contain <owner> and <email>
176 textual tags which will be replaced with cato informations.\n
177 Embedded license versions will be applied on the first empty line of
178 each source file scanned, if no empty line is found, no license is
179 applied.\n
180 In its normal behaviour cato apply the license to the files given as
181 arguments on the command line, while using -d option it scans the
182 given directory for file extensions specified as command line
183 arguments. If -r is provided in conjunction with -d, all the
184 directory tree is scanned starting from the supplied dir.
185 ''')
186 options, args = op.parse_args(args)
187
188
189 licenses = []
190 for lic_file in os.listdir(cato_licenser.license_dir):
191 licenses.append(lic_file[:lic_file.rfind('.')])
192
193
194 if options.lic_list:
195 for l in licenses:
196 print l
197 return
198
199
200 if options.owner:
201 cato_licenser.license_tags['<owner>'] = options.owner
202 if options.year:
203 cato_licenser.license_tags['<year>'] = options.year
204 if options.email:
205 cato_licenser.license_tags['<email>'] = options.email
206 if options.comment:
207 cato_licenser.comment_syntax = {'default': options.comment}
208
209
210 if not options.license:
211 license = licenses[0]
212 else:
213 license = options.license
214 (extended_lic, embedded_lic) = cato_licenser.parse_license(license)
215
216
217 if options.directory:
218 dir = options.directory
219 cato_licenser.patch_dir(dir, extended_lic)
220 if options.recursive:
221 for root, dirs, files in os.walk(dir):
222 for f in files:
223 match = False
224 for a in args:
225 if fnmatch.fnmatch(f, "*." + a):
226 match = True
227 if match:
228 print "Applying license to file: " + f
229 cato_licenser.patch_file(os.path.join(root, f), embedded_lic)
230 else:
231 root = os.path.abspath(dir)
232 for f in os.listdir(root):
233 if os.path.isfile(os.path.join(root, f)):
234 match = False
235 for a in args:
236 if fnmatch.fnmatch(f, "*." + a):
237 match = True
238 if match:
239 print "Applying license to file: " + f
240 cato_licenser.patch_file(os.path.join(root, f), embedded_lic)
241 else:
242 for f in args:
243 print "Applying license to file: " + f
244 cato_licenser.patch_file(f, embedded_lic)
245