123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- #!/bin/python3
-
- ## listings-extx.py -- Some program to get named entries for listings-ext
- ## Author: Jonathan P. Spratte <j.spratte(at)yahoo.de>
- ## Version: 1.0.1
- ## Keywords: LaTeX, listings
- ## Copyright (C) 2019 Jonathan P. Spratte <j.spratte(at)yahoo.de>
- ##-------------------------------------------------------------------
- ##
- ## This program is free software: you can redistribute it and/or modify
- ## it under the terms of the GNU General Public License as published by
- ## the Free Software Foundation, either version 3 of the License, or
- ## (at your option) any later version.
- ##
- ## This program is distributed in the hope that it will be useful,
- ## but WITHOUT ANY WARRANTY; without even the implied warranty of
- ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ## GNU General Public License for more details.
- ##
- ## You should have received a copy of the GNU General Public License
- ## along with this program. If not, see <https://www.gnu.org/licenses/>.
-
- import getopt, textwrap, re
- from os import getcwd
- from os import name as os_name
- from sys import argv, stdout, stderr
- from pathlib import Path, PurePosixPath
- from shutil import get_terminal_size
-
- extensiondict = {# {{{
- ".c" : "/\*|//",
- ".cc" : "/\*|//",
- ".cpp" : "/\*|//",
- ".cxx" : "/\*|//",
- ".py" : "#",
- ".tex" : "%",
- ".sh" : "#",
- ".cs" : "//",
- ".m" : "/\*|%",
- ".mm" : "/\*",
- ".java" : "//|/\*",
- ".jav" : "//|/\*",
- ".j" : "//|/\*",
- ".php" : "//",
- ".css" : "/\*",
- ".html" : "<!--",
- ".htm" : "<!--",
- ".xhtml" : "<!--",
- ".jhtml" : "<!--",
- ".xml" : "<!--",
- ".rb" : "#",
- ".rbw" : "#",
- ".go" : "//",
- ".js" : "//",
- ".lua" : "--",
- ".pl" : "#",
- ".r" : "#",
- ".rs" : "//|/\*",
- ".scpt" : "--",
- ".sql" : "--",
- ".for" : "!",
- ".ftn" : "!",
- ".f" : "!",
- ".f77" : "!",
- ".f90" : "!",
- ".erl" : "%",
- ".coffee" : "#",
- ".clj" : ";",
- ".swift" : "//",
- ".scala" : "//|/\*",
- ".pas" : "{\*|{",
- ".pp" : "{\*|{",
- ".p" : "{\*|{",
- }# }}}
-
- tags_allowed = "\w:"
- join_argsregex = re.compile("\A(?:\s*=)?["+tags_allowed+" ]+")
- join_argregex = re.compile("["+tags_allowed+"]+")
- maxwd = min(80, get_terminal_size(fallback=(70,24))[0])
-
- def eprint(*args, **kwargs):# {{{
- print(*args, file=stderr, **kwargs)# }}}
-
- class Object(object): # hacky way to have a nice parameters structure {{{
- pass# }}}
-
- def print_version():# {{{
- print("listings-extx.py v1.0.1")
- print("Copyright (C) 2019 Jonathan P. Spratte")
- print_wrapped(
- "License GPLv3+: GNU GPL version 3 or later "
- + "<https://gnu.org/licenses/gpl.html>.",
- indent='', moreindent=''
- )
- print_wrapped(
- "This is free software: you are free to change and redistribute "
- + "it.",
- indent='', moreindent=''
- )
- print_wrapped(
- "There is NO WARRANTY, to the extent permitted by law.",
- indent='', moreindent=''
- )
- quit()# }}}
-
- def arg_parse(name,argv,parameters):# {{{
- try:
- opts, other_args = getopt.gnu_getopt(
- argv,
- "habvSs:o:c:",
- (
- "help",
- "abs-path",
- "basename",
- "version",
- "silent",
- "spaces=",
- "output-file=",
- "comment-char="
- )
- )
- except getopt.GetoptError as err:
- eprint(err.msg)
- print("Here's how you use %s:"%(name))
- give_help(name,1)
- for opt, arg in opts:
- if opt in ("-h", "--help"):
- give_help(name)
- elif opt in ("-a", "--abs-path"):
- parameters.abspath = True
- elif opt in ("-b", "--basename"):
- parameters.basename = True
- elif opt in ("-c", "--comment-char"):
- parameters.comment_char = arg
- elif opt in ("-o", "--output-file"):
- parameters.output = arg
- elif opt in ("-p", "--prefix"):
- parameters.prefix = arg
- elif opt in ("-S", "--silent"):
- parameters.silent = True
- elif opt in ("-s", "--spaces"):
- if arg == "none":
- parameters.no_spaces = 2
- elif arg == "can":
- parameters.no_spaces = 1
- elif arg == "must":
- parameters.no_spaces = 0
- else:
- eprint(
- "Argument to '--spaces' must be one of 'none', "
- + "'can', 'must'"
- )
- quit(3)
- elif opt in ("-v", "--version"):
- print_version()
- parameters.files = other_args# }}}
-
- def print_wrapped(text, indent="\t", moreindent="\t"):# {{{
- for i in textwrap.wrap(
- text, initial_indent=indent, subsequent_indent=indent+moreindent,
- width = maxwd
- ):
- print(i)# }}}
-
- def print_opt(opt, text, indent="\t", moreindent="\t"):# {{{
- print_wrapped(
- opt + ":"
- + ("\t" if (len(opt)+1 < len("\t".expandtabs())) else " ")
- + text,
- indent=indent, moreindent=moreindent
- )# }}}
-
- def give_help(name,err=0):# {{{
- print_wrapped(
- name + " -- Some programme to get named entries for listings-ext\n",
- indent='', moreindent=' '
- )
- print_wrapped("Usage: %s [options] <filename> ...\n"%(name), indent='')
- print("Options:")
- print_opt("-a, --abs-path", "use the absolute filepaths in the output")
- print_opt("-b, --basename",
- "prefix every identifier with the processed file's basename "
- + "followed by a period."
- )
- print_opt("-c, --comment-char=<regex>",
- "use <regex> as the comment starting character. For "
- + "instance to match C-style comments this would be '//|/\*'."
- )
- print_opt("-h", "print this help and exit")
- print_opt("-o, --output-file=<output filename>",
- "if this argument is present, the output "
- + "will be written into a file <output filename>; "
- + "if <output filename> is an empty string, the output is "
- + "redirected into a file with a basename corresponding to the "
- + "name of the current directory with the extension '.lst'"
- )
- print_opt("-p, --prefix=<string>",
- "prefix every identifier with this <string>, if both this and the "
- + "'--basename' option is used, the prefix is added before the "
- + "basename."
- )
- print_opt("-S, --silent",
- "If used the program doesn't report which files to process and "
- + "where to write the output to."
- )
- print_opt("-s, --spaces=<choice>",
- "Defines how spaces at the begin of the line are handled. "
- + "<choice> must be one of:"
- )
- print_opt("'must'",
- "(default) there must be spaces before the start of the "
- + "comment",
- indent="\t\t", moreindent=" "
- )
- print_opt("'can'",
- "there can be spaces before the start of the comment",
- indent="\t\t", moreindent=" "
- )
- print_opt("'none'",
- "no spaces are allowed before the start of the comment",
- indent="\t\t", moreindent=" "
- )
- print_opt("-v, --version", "Print some version information and exit")
- quit(err)# }}}
-
- def compile_regex(comment_char, no_spaces):# {{{
- if no_spaces == 0:
- spaces = "(?:\s+)"
- elif no_spaces == 1:
- spaces = "(?:\s*)"
- elif no_spaces == 2:
- spaces = ""
- return re.compile(
- "\A" + spaces + "(?:" + comment_char + ") ([abej])e: "
- + "(["+tags_allowed+"]+)"
- )# }}}
-
- def handle_join(argstr):# {{{
- match = join_argsregex.match(argstr)
- if match == None: return []
- else:
- return join_argregex.findall(match.group(0))# }}}
-
- def process_file(fname, parameters):# {{{
- fPath = Path(fname)
- if not fPath.is_file():
- eprint("%% Couldn't find file %s"%(fname))
- return
- try:
- fHandle = fPath.open()
- except:
- eprint("%% Couldn't open file %s"%(fname))
- return
- if parameters.comment_char == False:
- try:
- comment_char = extensiondict[fPath.suffix]
- except KeyError:
- eprint("%% Unknown file extension of file %s"%(fname))
- eprint("% Guessing comments to start with one of:\n%\t",end='')
- for i in ('#', '%', '!', ';', '//', '/*', '--'):
- eprint("'%s' "%(i),end='')
- eprint()
- comment_char = "#|%|!|;|//|/\*|--"
- else:
- comment_char = parameters.comment_char
- if parameters.abspath:
- fname = PurePosixPath(fPath.absolute()).as_posix()
- if os_name == "nt": fname = fname.replace("\\", "", 1)
- elif os_name == "nt":
- fname = PurePosixPath(fPath).as_posix()
- regprog = compile_regex(comment_char, parameters.no_spaces)
- cp = Object()
- cp.a = set()
- cp.j = {}
- cp.be = []
- be = Object()
- be.name = None
- be.start = None
- line = Object()
- line.str = ""
- line.nr = 0
- while True: # read the file {{{
- line.nr += 1
- line.str = fHandle.readline()
- if line.str == "": break
- match = regprog.match(line.str)
- if match == None: continue
- if match.group(1) == 'a':
- cp.a.add(match.group(2))
- elif match.group(1) == 'b':
- if be.name == None:
- be.name = match.group(2)
- be.start = line.nr
- else:
- eprint("Nested code blocks are not supported!")
- quit(4)
- elif match.group(1) == 'e':
- if match.group(2) == be.name:
- cp.be += ((be.start, line.nr, be.name),)
- be.name = None
- else:
- eprint("Code block '%s' ended by '%s'"%(be.name,
- match.group(2)))
- quit(5)
- elif match.group(1) == 'j':
- argstr = line.str[len(match.group(0)):]
- joining = handle_join(argstr)
- for j in joining:
- if j in cp.j: cp.j[j].add(match.group(2))
- else: cp.j[j] = set((match.group(2),))
- #}}}
- fHandle.close()
- be = {}
- ba = []
- bj = {}
- for start, end, name in cp.be:
- if start + 1 < end:
- lines = "%d-%d"%(start + 1, end - 1)
- if name in be: be[name] += ",%s"%(lines)
- else: be[name] = lines
- if name in cp.j:
- for n in cp.j[name]:
- if n in bj: bj[n] += ",%s"%(lines)
- else: bj[n] = lines
- if any(cp.a): ba += (lines,)
- if any(cp.a):
- ba = [ (a, ','.join(ba)) for a in cp.a ]
- bj = [ (name, lines) for name, lines in bj.items() ]
- be = [ (name, lines) for name, lines in be.items() ]
- return (fname, ba + bj + be)# }}}
-
- def write_code_point(f, code_point, out, prefix, basename):# {{{
- b = Path(f).stem + "." if basename else ""
- out.write("\\lstdef{"+p+b+code_point[0]+"}{"+f+"}{"+code_point[1]+"}\n")# }}}
-
- def main(name,args):# {{{
- parameters = Object()
- parameters.files = []
- parameters.output = False
- parameters.abspath = False
- parameters.silent = False
- parameters.comment_char = False
- parameters.basename = False
- parameters.no_spaces = 0
- parameters.prefix = ""
- arg_parse(name,args,parameters)
- if len(parameters.files) == 0:
- eprint("No input files given")
- print("Here's how you use %s:"%(name))
- give_help(name,2)
- needs_close = True
- if parameters.output == False:
- out = stdout
- needs_close = False
- written_to = "stdout"
- elif parameters.output == "":
- written_to = getcwd().split('/')[-1] + ".lst"
- try:
- out = open(written_to, "w")
- except:
- eprint("Couldn't open %s for writing"%(written_to))
- quit(1)
- else:
- written_to = parameters.output
- try:
- out = open(written_to, "w")
- except:
- eprint("Couldn't open %s for writing"%(written_to))
- quit(1)
- if not parameters.silent:
- print("% Files you want me to process:")
- for i in parameters.files:
- print("%%\t%s"%(i))
- print("%% Output is written to: %s"%(written_to))
- code_points = [
- process_file(f, parameters) or ("",[])
- for f in parameters.files
- ]
- if not needs_close: print()
- for f, cc in code_points:
- for c in cc:
- write_code_point(f, c, out, parameters.prefix, parameters.basename)
- if needs_close: out.close()# }}}
-
- if __name__ == "__main__":
- main(argv[0],argv[1:])
|