#!/usr/bin/env python import string,re,sys,stat,os,getopt,time ## SELF_INFOR = "ktv-docstrip (version Python.%d)" % SELF_VER GEN_DATE = "%d/%02d/%02d" % time.localtime()[:3] ## :: Regular Expression ## file DTX re_begin = re.compile("%<\*([!a-zA-Z(][()a-zA-Z0-9_|&!]*)>") # begin block re_end = re.compile("%") # end block re_sblock = re.compile("%<([!a-zA-Z(][()a-zA-Z0-9_|&!]*)>(.+\n)")# inline block re_code = re.compile("^[^%]") # code ## file KINS re_from = re.compile("\$from\(([^)]+)\)") re_gen = re.compile("\$gen\(([^)]+)\)[ ]*\(([^)]+)\)[ ]*(.*)") re_asg = re.compile("\$([a-z][a-z0-9_]*)[ ]*=[ ]*(.*)") ## re_asg = re.compile("\$([a-z][a-z0-9_]*)[ ]*=[ ]*(.*)") re_kasg = re.compile("([a-z][a-zA-Z0-9_]*)[ ]*=[ ]*(.*)") re_com = re.compile("#.*") ## comment line re_isnum = re.compile("[+|-]*[0-9]+") re_local = re.compile("\$local\(([^)]+)\)") re_eof = re.compile("\$eof=([01])") ## re_eoj = re.compile("\$eoj=([01])") re_import = re.compile("\$import\(([^)]+)\)") ## add, Ky Anh, 2003/12/25 re_if = re.compile("\$if\(([^)]+)\)") re_elif = re.compile("\$elif\(([^)]+)\)") re_exec = re.compile("\$<<<(.*)") ## re_expand = re.compile("\$\([a-z][a-z0-9_]*\)") def _xsplit(st): """Tao list cac terminal/guard, bo qua &,|,!,(,) Chi tra ve cac list chua co trong danh sach __guards ma thoi """ ## last modify: Ky Anh, 2003/12/27 ## : use global __guards global __guards for ops in ['&', '(', ')', '!', '|', ',', ' ']: st = string.replace(st, ops, ':') tmp = [] for ops in string.split(st, ':'): if (ops != '') and (not ops in __guards): tmp.append(ops) __guards.extend(tmp) __use(tmp,0) def _translate(st): # """Dich mot bieu thuc logic thanh loi chuan cua PYTHON""" st = "(%s)" % st idx = 0 tmpst = "" find_next_op = 0 group_level = 0 while idx < len(st): if st[idx] == '!': tmpst = tmpst + "(not " find_next_op = 1 elif st[idx] == '(': tmpst = tmpst + '(' group_level = group_level + 1 elif st[idx] == ')': tmpst = tmpst + ')' group_level = group_level - 1 else: # ! if find_next_op: while (find_next_op > 0) and (idx < len(st)): if (st[idx] in __block_ops) or (group_level == 0): tmpst = tmpst + ')' + st[idx] find_next_op = 0 else: tmpst = tmpst + st[idx] idx = idx + 1 else: tmpst = tmpst + st[idx] idx = idx +1 idx = string.count(tmpst,'(') - string.count(tmpst,')') while idx > 0 : tmpst = tmpst + ')' idx = idx - 1 return tmpst[1:-1] def __setval(guard, value=1): ## add, Ky Anh, 2003/12/27 exec("global %s\n%s=%d" % (guard,guard,value)) def __use(options, value=1): ## add, Ky Anh, 2003/12/27 ## mod, Ky Anh, 2003/12/30, v1792, remove __alpha/__beta option check for weapon in options: tmpst = string.strip(weapon) if tmpst: __setval(tmpst,value) ## if tmpst in __alpha_options: ## __setval(tmpst,1) ## elif tmpst in __beta_options: ## __setval(tmpst,0) ## else: def _use(opts_string, value=1): __use(string.split(opts_string, ","), value) def _do_global(): ## add, Ky Anh, 2003/12/27 ## _force_ ## _gonly_ ## _kins_nopre ## _kins_nopost ## _kins_debug ## _kins_destdir ## jobnames ## list of job ( files) ## __guards ## list of guard in DTX files ## add, Ky Anh, 2003/12/27 ## NO ## list of NO-equivalent (0, No, NO, etc.) ## __block_ops ## list of operators in a DTX guard (block) ## __kins_vars ## list of global kins variables ## __global_kins_vars ## kins variables that are also the global ## variables in this program ## __no_skip_vars ## variables appear in the third part of |$gen| ## command; these variables arenot the ## __alpha_options ## this DTX options will always be set to 1 ## __beta_options ## - 0 ## _tex_ ## output as the INS file global\ _force_,\ _gonly_,\ _kins_nopre,\ _kins_nopost,\ _kins_debug,\ _kins_destdir,\ jobnames,\ __guards,\ NO,\ __block_ops,\ __kins_vars,\ __global_kins_vars,\ __no_skip_vars,\ _tex_,\ _kins_pre_def,\ _kins_pos_def ## __alpha_options,\ ## __beta_options NO = ['0','no'] __block_ops = ['&','|'] ## donot accept the !. See the _translate() __global_kins_vars = ["pre", "post"] __no_skip_vars = ["append", "nopost", "nopre",\ "destdir", "source", "debug"] ## __alpha_options = ["alpha", "com"] ## __beta_options = [] jobnames = [] __guards = [] _force_ = 0 _gonly_ = 0 _tex_ = 0 ## initialize the global variables ## these variable can be exported, i.e., ## we can use `--exec cmd' at command line. _kins_nopre = '0' _kins_nopost = '0' _kins_debug = '0' _kins_destdir = '.' __kins_vars = ["nopre", "nopost", "destdir", "debug"] _kins_pre_def = "%% This file was generate\n\ %% \tfrom the file `$source'\n\ %% \twith option `$option'\n\ %% GenDate: $date\n\ %% Generator: $generator\n" _kins_pos_def = "%% End of file `$dest'" ## init the guards ## we neednot do this, because if a guard appears in a DTX file ## it is recognize by _xsplit and then set the value by __use ## see the defintionof _xplist for more details ## __use(__alpha_options + __beta_options) def _xstrip(options, debug=0): """Tach ma tu file DTX voi options""" global __ilines, __output _use(options, 1) __output = [] __level = 0 __prints = [1] if debug: print "%s" % ("=" * 72), for weapon in __ilines: print_code = 0 if re_begin.match(weapon): __level = __level + 1 if __prints[__level - 1]: # previous block current_bblock = re_begin.match(weapon).group(1) exec("print_code=%s" % _translate(current_bblock)) __prints.append(int(print_code)) if debug: print "\nlevel%2d %s *%s -> "\ % (__level,__prints,re_begin.match(weapon).group(1)), elif re_end.match(weapon): __level = __level - 1 if __level < 0: break __prints.pop() if debug: print "\nlevel%2d %s /%s -> "\ % (__level,__prints,re_end.match(weapon).group(1)), elif re_sblock.match(weapon): # if __prints[__level]: current_bblock = re_sblock.match(weapon).group(1) exec("print_code=%s" % _translate(current_bblock)) if debug: print "\nlevel%2d %s %s -> "\ % (__level,__prints,re_sblock.match(weapon).group(1)), if print_code: if debug: sys.stdout.write('s') else: __output.append(re_sblock.match(weapon).group(2)) if debug: print "\nlevel%2d %s -> " % (__level,__prints), elif re_code.match(weapon): if __prints[__level]: if debug: sys.stdout.write('n') else: __output.append(weapon) if __level: print \ "\nThe blocks of your DTX file donot balance.\n\ To debug, use with `--debug' option." if debug: print "\n%s" % ("=" * 72) _use(options, 0) # reset options' value to 0 def _readkins(filename): ## add, Ky Anh, 2003/12/25 """Read a KINS file, remove the comment line, and strip the lines. This function returns a LIST of KINS lines.""" mykinslines = [] f = open(filename, 'r') iline = f.readline() while iline: iline = re_com.split(iline)[0] iline = string.strip(iline) if re_eof.match(iline): if re_eof.match(iline).group(1) == '1': break ## reach END OF FILE elif iline: mykinslines.append(iline) iline = f.readline() f.close() return mykinslines def _dokins(filename): """Excute the KINS code. The original name is |_readkins|; this version wasnot support |$import|. To do |$import|, a new function |_readkins| was added, and this function is now named |_dokins|.""" global _kins_pre, _kins_post, kins_lines, _kins_pre_def, _kins_pos_def kins_lines = [] __imported = [] # list of imported files _kins_pre = _kins_pre_def # default preamble/ _kins_post = _kins_pos_def # default /postamble _kins_source= '' in_pre = in_post = 0 in_if = [1] __iflevel = 0 __ifcont = [] kins_lines = _readkins(filename) while kins_lines: iline = kins_lines.pop(0) ## :: = $if if re_if.match(iline): if_value = 0 __iflevel = __iflevel + 1 if in_if[__iflevel - 1]: var_name = re_if.match(iline).group(1) if re_kasg.match(var_name): var_value = re_kasg.match(var_name).group(2) var_name = re_kasg.match(var_name).group(1) try: if var_name in __kins_vars: exec("tmp_value=_kins_%s" % var_name) else: exec("tmp_value=_gen_%s" % var_name) if var_value == tmp_value: if_value = 1 except NameError: pass in_if.append(if_value) ## :: = $elif elif re_elif.match(iline): __iflevel = __iflevel - 1 iline = "$if%s" % iline[5:] kins_lines.insert(0, iline) ## :: = $else elif iline == "$else": in_if[__iflevel] = 1 - in_if[__iflevel] ## :: = $enif elif iline == "$enif": __iflevel = __iflevel - 1 if __iflevel < 0: sys.stderr.write(\ "\nerror: to much $enif. The program is going to stop.\n") sys.exit(1) else: in_if.pop() if __iflevel == 0: kins_lines[0:0], __ifcont = __ifcont, [] elif __iflevel: if in_if[__iflevel]: __ifcont.append(iline) ## :: = pre, post elif iline == "$pre": _kins_pre, in_pre = "", 1 elif iline == "$post": _kins_post, in_post = "", 1 elif iline == "$enpre": in_pre = 0 elif iline == "$enpost": in_post = 0 ## ## :: = $(.+) ## elif re_expand.match(iline): ## var_name = re_expand.match(iline).group(1) ## :: = $<<<< elif re_exec.match(iline): try: exec(re_exec.match(iline).group(1)) except: sys.stderr.write("E") ## :: = if in_pre, in_post ## hai do`ng sau pha?i dde^? sau bo^'n do`ng tre^n elif in_pre: _kins_pre = _kins_pre + iline + '\n' elif in_post: _kins_post = _kins_post + iline + '\n' ## :: = $from elif re_from.match(iline): # from macro _kins_source = re_from.search(iline).group(1) _readsource(_kins_source) sys.stderr.write('_') ## :: = $import elif re_import.match(iline): _i_file = re_import.match(iline).group(1) if not _i_file in __imported: __imported.append(_i_file) if not string.lower(_i_file[-5:]) == ".kins": _i_file = _i_file + ".kins" kins_lines[0:0] = _readkins(_i_file) sys.stderr.write('=') else: sys.stderr.write('*') ## :: = assign elif re_asg.match(iline): var_name = re_asg.match(iline).group(1) var_value= re_asg.match(iline).group(2) ## :: = = $docstrip if var_name == 'docstrip': # if SELF_VER < int(var_value): sys.stderr.write(\ "need docstrip %d or higher. Current docstrip's version: %d.\n"\ % (int(var_value), SELF_VER)) return ## :: = = global kins variables elif var_name in __global_kins_vars: exec('global _kins_%s\n_kins_%s="""%s"""'\ % (var_name, var_name, var_value)) ## :: = = other kins variables else: if not var_name in __kins_vars: __kins_vars.append(var_name) exec('_kins_%s="""%s"""' % (var_name,var_value)) if var_name == 'source': _readsource(_kins_source) ## :: = $local elif re_local.match(iline): zzz = re_local.match(iline).group(1) if re_kasg.match(zzz): var_name = re_kasg.match(zzz).group(1) var_value = re_kasg.match(zzz).group(2) exec('_gen_%s="""%s"""' % (var_name,var_value)) ## :: = $gen elif re_gen.match(iline): # gen macro gen_des = re_gen.match(iline).group(1) if (not _gonly_) or (only_files.search(gen_des)): gen_opt = re_gen.match(iline).group(2) ## neu $gen co them tham so thu ba ## tham so nay phai de trong ngoac: (.+) kins_skip = 0 _gen_append = '0' # _gen_nopre = _kins_nopre # get the global value _gen_nopost = _kins_nopost # as the initialization _gen_destdir = _kins_destdir# add 2003/12/10 _gen_debug = _kins_debug # if re_gen.search(iline).group(3) != "": gen_optlist = string.split(\ re_gen.search(iline).group(3)[1:-1],',') for item in gen_optlist: item = string.strip(item) if item == "append": _gen_append = '1' elif item == "nopost": _gen_nopost = '1' elif item == "nopre" : _gen_nopre = '1' else: zzz = re_kasg.match(item) if zzz: var_name = zzz.group(1) var_value= str(zzz.group(2)) if var_name in __no_skip_vars: exec('_gen_%s="""%s"""' % (var_name,var_value)) else: try: if var_name in __kins_vars: exec("tmp_value=_kins_%s" % var_name) else: exec("tmp_value=_gen_%s" % var_name) if not var_value == tmp_value: kins_skip = 1 break # break the FOR loop ## donot mention other check except NameError: exec('_gen_%s="""%s"""' % (var_name,var_value)) if kins_skip: sys.stderr.write('o') else: gen_des = "%s/%s" % (_gen_destdir, gen_des) _gen(gen_des,gen_opt,\ _gen_append,\ _gen_nopre,_gen_nopost,\ _kins_source, _gen_debug) else: sys.stderr.write('o') ## :: = anything else else: pass if __iflevel != 0: sys.stderr.write("\nerror: missing $if or $enif\n") def _readsource(source): ## last modify: Ky Anh, 2003/12/27 ## : according to changes of _xsplit global __ilines,\ src_time __ilines = [] src_time = os.stat(source)[stat.ST_MTIME] f = open(source, 'r') iline = f.readline() # while iline: if re_code.match(iline): # code __ilines.append(iline) elif re_begin.match(iline): # begin block __ilines.append(iline) _xsplit(re_begin.match(iline).group(1)) elif re_sblock.match(iline):# single block __ilines.append(iline) _xsplit(re_sblock.match(iline).group(1)) elif re_end.match(iline): # end block __ilines.append(iline) iline = f.readline() f.close() def _gen(filename, opt,\ _gen_append='0',\ _gen_nopre='0', _gen_nopost='0',\ source_name='', _gen_debug='0'): if not _gen_debug in NO: #yes, debug is ON sys.stderr.write('-') print "f(%s)s(%s)o(%s)a(%s)npre(%s)npos(%s)bug(%s)"\ % (filename, source_name, opt,\ _gen_append, _gen_nopre, _gen_nopost, _gen_debug) _xstrip(opt, 1) else: des = string.split(filename, '/')[-1:][0] try: dest_time = os.stat(filename)[stat.ST_MTIME] except os.error: dest_time = 0 if _force_ or (src_time > dest_time): try: if _gen_append in NO: g = open(filename, 'w') sys.stderr.write('.') else: g = open(filename, 'a') g.write('\n') sys.stderr.write('+') except IOError: sys.stderr.write('X') return if _gen_nopre in NO: global _kins_pre ## expand |preample| tmp__pre = string.replace(_kins_pre,"$date", GEN_DATE) tmp__pre = string.replace(tmp__pre, "$c", '#') tmp__pre = string.replace(tmp__pre, "$generator", SELF_INFOR) tmp__pre = string.replace(tmp__pre, "$option", opt) tmp__pre = string.replace(tmp__pre, "$dest", des) tmp__pre = string.replace(tmp__pre, "$source", source_name) g.write(tmp__pre) ## if _tex_: ## if tmp__pre[-1:] == '\n': ## tmp__pre = tmp__pre[:-1] ## print "\def\ktvpre{^^A" ## print string.replace(tmp__pre, "\n", "^^J^^A\n"), ## print "}\usepreamble\ktvpre" _xstrip(opt, 0) g.writelines(__output) if _tex_: print "\\generate{\\file{%s}{\\from{%s}{%s}}}"\ % (filename, source_name, opt) if _gen_nopost in NO: global _kins_post tmp__pre = _kins_post ## expand |posamble|. Only macro "$dest" is allowed. tmp__pre = string.replace(_kins_post,"$dest", des) tmp__pre = string.replace(tmp__pre, "$c", "#") g.write(tmp__pre)# the tail g.close() os.utime(filename, (src_time, src_time)) else: sys.stderr.write('|') def usage(): sys.stdout.write(\ """Usage: docstrip [options] file1 file2... -e --exec code excute before any job (only the assignments are accepted) -g --gen expr generate only files whose names match the regular expression -j --job job specify the kins file named (the extension `.kins' can be omitted) -t --tex print the (TeX) INS code -d --debug print debug information (donot generate any file) -f --force force generatation (skip time check) -h --help show this message -v --version print version information and exit Report: . generate sucessfully + generate in `append' mode o skip file (specified by file) ? some things wrong occur _ opening new source file = importing a kins file * file is already imported. Inogre it! X cannot open file for writting E failed on excuting embeded python code | skip file (the source time <= the destination time) Within the `--debug' option: - debugging n normal code s single block code """) if __name__ == '__main__': try: opts, args\ = getopt.getopt(sys.argv[1:],\ "vdhftg:e:j:",\ ["debug","tex","job=","version", "gen=","force","help","exec="]) except: sys.stderr.write("Please try |docstrip.py --help|\n") sys.exit(1) _do_global() for o, a in opts: if o in ("-h", "--help"): usage() sys.exit(0) elif o in ("-v", "--version"): sys.stdout.write("%s\n" % SELF_INFOR) sys.exit(0) elif o in ("-f", "--force"): _force_ = 1 elif o in ("-t", "--tex"): _tex_ = 1 elif o in ("-d", "--debug"): _kins_debug = '1' elif o in ("-g", "--gen"): _gonly_ = 1 only_files = re.compile(a) sys.stderr.write(" genonly = %s\n" % a) elif o in ("-j", "--job"): if not string.lower(a[-5:]) == ".kins": a = a + ".kins" jobnames.append(a) elif o in ("-e", "--exec"): a = string.split(a, ';') for code in a: zzz = re_kasg.match(string.strip(code)) if zzz: var_name = zzz.group(1) var_value= zzz.group(2) exec('_kins_%s="""%s"""' % (var_name,var_value)) if not var_name in __kins_vars: __kins_vars.append(var_name) sys.stderr.write(" %s = %s\n" % (var_name,var_value)) else: sys.stderr.write(" ?code: %s\n" % code) for item in args: if not string.lower(item[-5:]) == ".kins": item = item + ".kins" jobnames.append(item) if jobnames == []: sys.stderr.write("Please try |docstrip.py --help|\n") sys.exit(1) if _tex_: print\ "%%%% This file was generated by `%s'\n\ %%%% from the source(s) %s\n\ \\input docstrip.tex" % (SELF_INFOR, jobnames) for jobname in jobnames: if os.path.isfile(jobname): sys.stderr.write("\n<<%s>>\n" % jobname) _dokins(jobname) else: sys.stderr.write("\n<<%s>>: cannot open this file\n" % jobname) if _tex_: print "\\endbatchfile\n\\endinput\n%% End of file." sys.stderr.write("(ok)\n") #################### ## END OF PROGRAM ## #################### ## TO DO: ## 2003/12/27: ## $if, $fi, $else, $elif