]> Softwares of Agnibho - statin.git/blob - statin/statin.py
Uniform styling for return statements
[statin.git] / statin / statin.py
1 '''
2 Copyright (c) 2018 Agnibho Mondal
3 All rights reserved
4
5 This file is part of Statin.
6
7 Statin is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 Statin is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Statin. If not, see <http://www.gnu.org/licenses/>.
19 '''
20
21 from glob import glob
22 from os import path, popen, unlink, makedirs
23 from shutil import copyfile, rmtree, copytree, ignore_patterns
24 from datetime import datetime
25 import argparse
26 import re
27 import tempfile
28
29 from statin.conf import *
30
31 # Global variables
32 conflist = {"timefmt": TIMEFMT, "sizefmt": SIZEFMT, "errmsg": ERRMSG}
33 varlist = {}
34 varlist["DATE_LOCAL"] = datetime.now().strftime(conflist["timefmt"])
35 varlist["DATE_GMT"] = datetime.utcnow().strftime(conflist["timefmt"])
36 openif = False
37 ifstatus = False
38 ifskip = False
39
40 # Start script
41 def main():
42 global args
43 global OUTPUT_DIR, PROCESS_PATT, MAX_RECURSION
44 PROCESS_PATT = set(PROCESS_PATT)
45
46 #Parse arguments
47 parser = argparse.ArgumentParser(description="Generate static html files")
48 verbo = parser.add_mutually_exclusive_group()
49 verbo.add_argument("-q", "--quiet", help="Suppress text output to console", action="store_true")
50 verbo.add_argument("-v", "--verbose", help="Verbose text output to console", action="store_true")
51 parser.add_argument("-s", "--safe", help="Disable python eval of strings", action="store_true")
52 parser.add_argument("-r", "--recursive", help="Process files recursively", action="store_true")
53 parser.add_argument("-l", "--level", help="Maximum recursion level", type=int)
54 parser.add_argument("-p", "--pattern", help="Filename patterns to be processed", action="append")
55 parser.add_argument("-o", "--output", help="Specify the output directory")
56 parser.add_argument("files", help="List of files to be processed", nargs="*")
57 args = parser.parse_args()
58
59 # Reassign variables from option
60 if(args.level != None):
61 MAX_RECURSION = args.level
62 if(args.pattern != None):
63 PROCESS_PATT = PROCESS_PATT.union(args.pattern)
64 if(args.output != None):
65 if(args.output[-1:] != "/"):
66 args.output = args.output + "/"
67 if(args.output[:2] != "./" and args.output[:1] != "/"):
68 args.output = "./" + args.output
69 OUTPUT_DIR = args.output
70
71 # List all files to be processed
72 filelist = []
73 if(args.files):
74 filelist = args.files
75 else:
76 for patt in PROCESS_PATT:
77 filelist.extend(glob(patt))
78
79 # Purge output directory and rebuild if specific filenames not supplied
80 if(not args.files):
81 rmtree(OUTPUT_DIR, True)
82 copytree(".", OUTPUT_DIR, ignore=ignore_patterns(*PROCESS_PATT))
83 if(not args.quiet):
84 print("Contents copied to " + OUTPUT_DIR + "\n")
85 else:
86 makedirs(OUTPUT_DIR, exist_ok=True)
87
88 # Send each file for processing
89 for filename in filelist:
90 if(not args.quiet):
91 print("Processing '" + filename + "'")
92 temp = []
93 if(args.recursive):
94 if(not args.quiet):
95 print("Creating temporary files")
96 rlvl = 0
97 fdir = path.dirname(path.realpath(filename))
98 temp.append(tempfile.NamedTemporaryFile(dir=fdir, prefix=".", delete=False))
99 temp.append(tempfile.NamedTemporaryFile(dir=fdir, prefix=".", delete=False))
100 copyfile(filename, temp[0].name)
101 if(args.verbose):
102 print("'" + filename + "' copied to '" + temp[0].name + "'")
103 if(args.verbose):
104 print("Processing '" + temp[0].name + "' to '" + temp[1].name + "'")
105 while(rlvl < MAX_RECURSION and not process_file(temp[0].name, temp[1].name, filename)):
106 temp.append(tempfile.NamedTemporaryFile(dir=fdir, prefix=".", delete=False))
107 unlink(temp.pop(0).name)
108 if(args.verbose):
109 print("Processing '" + temp[0].name + "' to '" + temp[1].name + "'")
110 rlvl += 1
111 if(not args.quiet and rlvl >= MAX_RECURSION):
112 print("Maximum recursion level reached")
113 outfile = OUTPUT_DIR + path.splitext(path.basename(filename))[0] + ".html"
114 copyfile(temp[0].name, outfile)
115 if(not args.quiet):
116 print("Output saved to '" + outfile + "'")
117 if(not args.quiet):
118 print("Cleaning up temporary files")
119 for t in temp:
120 unlink(t.name)
121 else:
122 outfile = OUTPUT_DIR + path.splitext(path.basename(filename))[0] + ".html"
123 process_file(filename, outfile)
124 if(not args.quiet):
125 print("Output saved to '" + outfile + "'")
126
127 # Process the file
128 def process_file(filename, outfile, original = None):
129 global args
130 global openif, ifstatus, ifskip
131
132 # Assign variable values
133 no_directive = True
134 if(original == None):
135 original = filename
136
137 try:
138 varlist["DOCUMENT_URI"] = original
139 varlist["DOCUMENT_NAME"] = path.basename(original)
140 varlist["LAST_MODIFIED"] = datetime.fromtimestamp(path.getmtime(original)).strftime(conflist["timefmt"])
141 with open(filename) as src, open(outfile, "w") as out:
142 for line in src:
143 line = re.split("(<!--#.+-->)", line)
144 for item in line:
145 if(item.strip()):
146 if(item.strip()[:5] == "<!--#"):
147 no_directive = False
148 item = process_directive(item.strip()[5:][:-3].strip(), filename)
149 if(not ifskip and (not openif or ifstatus)):
150 out.write(str(item))
151 if(openif and not args.quiet):
152 print("Error: Unexpected end of file reached. Expecting 'endif'.")
153 except FileNotFoundError as e:
154 if(not args.quiet):
155 print("Error: file '" + e.filename + "' could not be found. Please check if the file exists.")
156 except IsADirectoryError as e:
157 if(not args.quiet):
158 print("Error: can't process directory '" + e.filename + "'. Please provide file names only.")
159
160 return(no_directive)
161
162 # Process the directives
163 def process_directive(line, filename):
164 global args
165 global varlist, conflist
166 global openif, ifstatus, ifskip
167
168 if(args.verbose):
169 print(" Processing directive : "+line)
170
171 # Tokenize directives
172 line = re.split('''\s(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', line)
173 directive = line.pop(0);
174 params = {}
175 for pair in line:
176 pair = re.split('''=(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', pair)
177 params[pair[0]] = pair[1][1:-1]
178
179 # Parse conditionals
180 if(directive == "if"):
181 openif = True
182 try:
183 ifstatus = (evaluate_expression(params["expr"]) == True)
184 except KeyError:
185 if(args.verbose):
186 print(" Error: no expression to process")
187 return(conflist["errmsg"])
188 return("")
189 elif(directive == "elif"):
190 if(ifskip):
191 return("")
192 if(ifstatus):
193 ifskip = True
194 else:
195 try:
196 ifstatus = (evaluate_expression(params["expr"]) == True)
197 except KeyError:
198 if(args.verbose):
199 print(" Error: no expression to process")
200 return(conflist["errmsg"])
201 return("")
202 elif(directive == "else"):
203 if(ifskip):
204 return("")
205 ifskip = ifstatus
206 ifstatus = not ifstatus
207 return("")
208 elif(directive == "endif"):
209 openif = False
210 ifskip = False
211 return("")
212
213 # Skip if conditional false
214 if(ifskip or (openif and not ifstatus)):
215 return("")
216
217 # Parse directives
218 if(directive == "include"):
219 try:
220 with open(params["virtual"]) as f:
221 return(f.read())
222 except KeyError:
223 pass
224 except FileNotFoundError as e:
225 if(not args.quiet):
226 print("Error: file '" + e.filename + "' could not be found. Please check if the file exists.")
227 try:
228 with open(path.dirname(path.realpath(filename)) + "/" + params["file"]) as f:
229 return(f.read())
230 except KeyError:
231 pass
232 except FileNotFoundError as e:
233 if(not args.quiet):
234 print("Error: file '" + e.filename + "' could not be found. Please check if the file exists.")
235 if(args.verbose):
236 print(" Error: no file to include")
237 return(conflist["errmsg"])
238 elif(directive == "exec"):
239 try:
240 return(popen(params["cmd"]).read())
241 except KeyError:
242 pass
243 try:
244 return(popen(params["cgi"]).read())
245 except KeyError:
246 pass
247 if(args.verbose):
248 print(" Error: no command to execute")
249 return(conflist["errmsg"])
250 elif(directive == "echo"):
251 try:
252 return(varlist[params["var"]])
253 except KeyError:
254 if(args.verbose):
255 print(" Error: no variable to display")
256 return(conflist["errmsg"])
257 elif(directive == "config"):
258 conflist.update(params)
259 varlist["DATE_LOCAL"] = datetime.now().strftime(conflist["timefmt"])
260 varlist["DATE_GMT"] = datetime.utcnow().strftime(conflist["timefmt"])
261 varlist["LAST_MODIFIED"] = datetime.fromtimestamp(path.getmtime(filename)).strftime(conflist["timefmt"])
262 elif(directive == "flastmod"):
263 try:
264 return(datetime.fromtimestamp(path.getmtime(params["virtual"])).strftime(conflist["timefmt"]))
265 except KeyError:
266 pass
267 except FileNotFoundError as e:
268 if(not args.quiet):
269 print("Error: file '" + e.filename + "' could not be found. Please check if the file exists.")
270 return(conflist["errmsg"])
271 try:
272 return(datetime.fromtimestamp(path.getmtime(path.dirname(path.realpath(filename)) + "/" + params["file"])).strftime(conflist["timefmt"]))
273 except KeyError:
274 pass
275 except FileNotFoundError as e:
276 if(not args.quiet):
277 print("Error: file '" + e.filename + "' could not be found. Please check if the file exists.")
278 return(conflist["errmsg"])
279 if(args.verbose):
280 print(" Error: missing filename")
281 return(conflist["errmsg"])
282 elif(directive == "fsize"):
283 idx = { "B":1, "KB":1024, "MB":1048576, "GB":1073741824, "TB":1099511627776, "b":1, "kb":1024, "mb":1048576, "gb":1073741824, "tb":1099511627776, "bytes":1, "kilobytes":1024, "megabytes":1048576, "gigabytes":1073741824, "terabytes":1099511627776 }
284 if(conflist["sizefmt"] == "abbrev"):
285 conflist["sizefmt"] = "kb"
286 if(not conflist["sizefmt"] in idx):
287 if(args.verbose):
288 print(" Error: invalid size format")
289 return(conflist["errmsg"])
290 try:
291 return("{0:.2f}".format(path.getsize(params["virtual"]) / idx[conflist["sizefmt"]]) + " " + conflist["sizefmt"])
292 except KeyError:
293 pass
294 except FileNotFoundError as e:
295 if(not args.quiet):
296 print("Error: file '" + e.filename + "' could not be found. Please check if the file exists.")
297 return(conflist["errmsg"])
298 try:
299 return("{0:.2f}".format(path.getsize(path.dirname(path.realpath(filename)) + "/" + params["file"]) / idx[conflist["sizefmt"]]) + " " + conflist["sizefmt"])
300 except KeyError:
301 pass
302 except FileNotFoundError as e:
303 if(not args.quiet):
304 print("Error: file '" + e.filename + "' could not be found. Please check if the file exists.")
305 return(conflist["errmsg"])
306 if(args.verbose):
307 print(" Error: missing filename")
308 return(conflist["errmsg"])
309 elif(directive == "printenv"):
310 return(varlist)
311 elif(directive == "set"):
312 try:
313 varlist[params["var"]] = evaluate_expression(params["value"])
314 except KeyError:
315 if(args.verbose):
316 print(" Error: missing variable or value")
317 return(conflist["errmsg"])
318 else:
319 if(args.verbose):
320 print(" Error: unrecognized directive")
321 return(conflist["errmsg"])
322 return("")
323
324 # Expression evaluation
325 def evaluate_expression(expr):
326 global args
327 expr = str(expr)
328 if(args.safe):
329 if(args.verbose):
330 print(" Can't evaluate expression in safe mode")
331 return(conflist["errmsg"])
332 try:
333 m=re.findall("\$\{*[^\}\s=><!+\-*/^%]+\}*", expr)
334 for v in m:
335 expr = expr.replace(v, str(varlist[v.replace("$", "").replace("{", "").replace("}", "")]))
336 except Exception:
337 pass
338 expr = re.sub("([\w\s]+)", r"'\1'", expr)
339 expr = re.sub("'([\d]+)'", r"\1", expr)
340 try:
341 return(eval(expr))
342 except Exception:
343 return(re.sub("'([\w\s]+)'", r"\1", expr))
344
345 if(__name__ == "__main__"):
346 main()