commit e170f25508833c70be34f929ac78df201dcee67a Author: Daniel Knuettel Date: Thu Dec 28 00:13:55 2017 +0100 initial diff --git a/licor/__init__.py b/licor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/licor/__main__.py b/licor/__main__.py new file mode 100644 index 0000000..e519113 --- /dev/null +++ b/licor/__main__.py @@ -0,0 +1,185 @@ +# +# Copyright(c) Daniel Knüttel +# + +# This program is free software. +# Anyways if you think this program is worth it +# and we meet shout a drink for me. + + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# Dieses Programm ist Freie Software: Sie können es unter den Bedingungen +# der GNU Affero General Public License, wie von der Free Software Foundation, +# Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren +# veröffentlichten Version, weiterverbreiten und/oder modifizieren. +# +# Dieses Programm wird in der Hoffnung, dass es nützlich sein wird, aber +# OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite +# Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. +# Siehe die GNU Affero General Public License für weitere Details. +# +# Sie sollten eine Kopie der GNU Affero General Public License zusammen mit diesem +# Programm erhalten haben. Wenn nicht, siehe . + + +from .main import (list_this_path, list_all, list_db, + print_uncommented_line_based, + print_uncommented_block_based, + print_template_options) +import docopt, datetime, sys + +usage = '''\ + +Insert license/copyright/warranty disclaimer to source files. + +Usage: + licor list-db [] + licor list-all [] + licor list-path [] + licor list-templates + licor print-templ [options] + licor insert-header [] [options] + +Options: + --comment-start= Comment start token to use [default: //] + --comment-stop= Comment stop token to use [default: */] + --border= Border character for some fancy stuff [default: *] + -f --fancy Use more fancy comments + --after-comment= A string to seperate border and content (defaults to one blank) + -c --confirm Wait for user confirmation before modifying files + --format= Use a special comment format [default: block] + --license= Use this license template [default: GPLv3] + --single-file Use single-file templates + --copyright Use templates containing copyright information + -a --author= Set the author (required for --copyright) + -p --project= Set the project (required unless --single-file is specified) + -e --file-ending= Search for files ending with this ending [default: c] + -i --ignore-db Ignore the database of processed files + --ignore-paths= Ignore all paths with one of `` in it (comma-seperated) [default: .git] + --pad-to= Pad comment blocks to this width [default: 0] +''' + +if( __name__ == "__main__"): + args = docopt.docopt(usage) + + if(args["list-db"]): + path = args[""] + if(not path): + path = "." + try: + list_db(path) + except Exception as e: + print(e) + sys.exit(1) + + if(args["list-path"]): + path = args[""] + if(not path): + path = "." + ending = args["--file-ending"] + ignore_db = args["--ignore-db"] + + list_this_path(path, ending, ignore_db = ignore_db) + + if(args["list-all"]): + path = args[""] + if(not path): + path = "." + ending = args["--file-ending"] + ignore_paths = args["--ignore-paths"].split(",") + ignore_db = args["--ignore-db"] + + list_all(path, ending, ignore_paths, ignore_db = ignore_db) + + if(args["print-templ"]): + form = args[""] + license_name = args["--license"] + modifiers = [] + if(args["--single-file"]): + modifiers.append("single-file") + if(args["--copyright"]): + modifiers.append("copyright") + + data = {} + if(args["--author"]): + data["author"] = args["--author"] + if(args["--project"]): + data["project"] = args["--project"] + data["year"] = str(datetime.datetime.now().year) + + after_comment = " " + if(args["--after-comment"]): + after_comment = args["--after-comment"] + + try: + pad_to = int(args["--pad-to"]) + except: + print("Failed to convert {} to int".format(args["--pad-to"])) + sys.exit(1) + + if(form == "line"): + print_uncommented_line_based(license_name, modifiers, data, args["--comment-start"], + fancy = args["--fancy"], after_comment = after_comment, + pad_to = pad_to) + elif(form == "block"): + method = args["--format"] + if(not method): + method = "line" + + print_uncommented_block_based(license_name, modifiers, data, + args["--comment-start"], args["--comment-stop"], + border = args["--border"], + fancy = args["--fancy"], after_comment = after_comment, + method = method, pad_to = pad_to) + else: + print("Unknown format ({}). Use line or block.".format(form)) + + if(args["list-templates"]): + print_template_options() + + if(args["insert-header"]): + form = args[""] + license_name = args["--license"] + modifiers = [] + if(args["--single-file"]): + modifiers.append("single-file") + if(args["--copyright"]): + modifiers.append("copyright") + + data = {} + if(args["--author"]): + data["author"] = args["--author"] + if(args["--project"]): + data["project"] = args["--project"] + data["year"] = str(datetime.datetime.now().year) + + after_comment = " " + if(args["--after-comment"]): + after_comment = args["--after-comment"] + + try: + pad_to = int(args["--pad-to"]) + except: + print("Failed to convert {} to int".format(args["--pad-to"])) + sys.exit(1) + + path = args[""] + ignore_paths = args["--ignore-paths"].split(",") + ignore_db = args["--ignore-db"] + insert_templates_all(path, args["--file-ending"], ignore_paths, license_name, + modifiers, data, args["--comment-start"], args["--comment-stop"], + form, method = method, border = args["--border"], fancy = args["--fancy"], + after_comment = after_comment, pad_to = pad_to, ignore_db = args["--ignore-db"], + confirm = args["--confirmation"]) diff --git a/licor/comment.py b/licor/comment.py new file mode 100644 index 0000000..acec8d7 --- /dev/null +++ b/licor/comment.py @@ -0,0 +1,153 @@ + + +# +# Copyright(c) Daniel Knüttel +# + +# This program is free software. +# Anyways if you think this program is worth it +# and we meet shout a drink for me. + + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# Dieses Programm ist Freie Software: Sie können es unter den Bedingungen +# der GNU Affero General Public License, wie von der Free Software Foundation, +# Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren +# veröffentlichten Version, weiterverbreiten und/oder modifizieren. +# +# Dieses Programm wird in der Hoffnung, dass es nützlich sein wird, aber +# OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite +# Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. +# Siehe die GNU Affero General Public License für weitere Details. +# +# Sie sollten eine Kopie der GNU Affero General Public License zusammen mit diesem +# Programm erhalten haben. Wenn nicht, siehe . + + +def pad_line(line, pad_to): + """ + Pad line with blanks until the line is ``pad_to`` + long. + """ + + return line + " " * (pad_to - len(line)) + +def uncomment_line_based(text, comment_start, + fancy = False, + after_comment = " ", + pad_to = 0): + """ + Generate an uncommented version of the text that can be inserted into + a source file. + + If ``fancy`` is False, the code will be just uncommented + by inserting a ``comment_start`` and a ``after_comment`` at every nonempty line. + + If ``fancy`` is True the block will be padded with blanks + (either to ``pad_to`` or until the longest line is matched) + and there will be another ``comment_start`` at the end of + every line. + + + Example:: + + uncomment_line_based("This is a text\n\nblock\n", "#") + + Will result in:: + + # This is a text + + # block + + The trailing newline will be preserved. + """ + + text = text.split("\n") + + if(not fancy): + text = [comment_start + after_comment + line if line else line for line in text] + else: + max_length = len(max(text, key = lambda x: len(x))) + + if(max_length > pad_to): + pad_to = max_length + + text = [comment_start + after_comment + + pad_line(line, pad_to) + + after_comment + comment_start + for line in text] + return "\n".join(text) + + +def uncomment_multiline_line_oriented(text, comment_start, + comment_stop, after_comment = " ", + fancy = False, border = "*", pad_to = 0): + """ + Just like ``uncomment_line_based``, but with start and stop + for comments. + + **Note**: ``border`` must be a length-1 string. + """ + + text = text.split("\n") + max_length = len(max(text, key = lambda x: len(x))) + + if(max_length > pad_to): + pad_to = max_length + + if(not fancy): + text = [comment_start + after_comment + pad_line(line, pad_to) + + after_comment + comment_stop + if line else line for line in text] + else: + text_res = [comment_start + after_comment + border * pad_to + + after_comment + comment_stop] + text_res += [comment_start + after_comment + + pad_line(line, pad_to) + + after_comment + comment_stop + for line in text] + text_res += [comment_start + after_comment + border * pad_to + + after_comment + comment_stop] + text = text_res + return "\n".join(text) + + +def uncomment_multiline_block_oriented(text, comment_start, comment_stop, + after_comment = " ", fancy = False, + border = "*", + pad_to = 0): + """ + Uncomment a text block block oriented. + """ + + text = text.split("\n") + max_length = len(max(text, key = lambda x: len(x))) + + if(max_length > pad_to): + pad_to = max_length + + indent = " " * (len(comment_start) - 1) + border + + + text_res = [comment_start + after_comment + border * pad_to + + after_comment + border] + text_res += [indent + after_comment + pad_line(line, pad_to) + + after_comment + border + for line in text] + text_res += [indent + after_comment + border * pad_to + + after_comment + comment_stop] + text = text_res + + return "\n".join(text) diff --git a/licor/filediscovery.py b/licor/filediscovery.py new file mode 100644 index 0000000..5760ad9 --- /dev/null +++ b/licor/filediscovery.py @@ -0,0 +1,68 @@ +# +# Copyright(c) Daniel Knüttel +# + +# This program is free software. +# Anyways if you think this program is worth it +# and we meet shout a drink for me. + + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# Dieses Programm ist Freie Software: Sie können es unter den Bedingungen +# der GNU Affero General Public License, wie von der Free Software Foundation, +# Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren +# veröffentlichten Version, weiterverbreiten und/oder modifizieren. +# +# Dieses Programm wird in der Hoffnung, dass es nützlich sein wird, aber +# OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite +# Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. +# Siehe die GNU Affero General Public License für weitere Details. +# +# Sie sollten eine Kopie der GNU Affero General Public License zusammen mit diesem +# Programm erhalten haben. Wenn nicht, siehe . + + +import glob, os + +def discover_this_path(path, file_ending): + """ + Discover all matching files in this path. + + Return an iterator yielding the filepaths. + """ + + return (os.path.abspath(p) for p in glob.iglob(os.path.join(path, "*." + file_ending))) + + +def discover_all(path, file_ending, ignore_paths = []): + """ + Discover all matching files in this path and + all subpaths. + + Yield the filepaths. + """ + + for p in os.walk(path): + path = p[0] + splitted = path.split(os.sep) + + if(any( [ignore in splitted for ignore in ignore_paths ])): + continue + + for i in discover_this_path(path, file_ending): + yield i + + + diff --git a/licor/io.py b/licor/io.py new file mode 100644 index 0000000..b02dfa3 --- /dev/null +++ b/licor/io.py @@ -0,0 +1,69 @@ +import os + +# +# Copyright(c) Daniel Knüttel +# + +# This program is free software. +# Anyways if you think this program is worth it +# and we meet shout a drink for me. + + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# Dieses Programm ist Freie Software: Sie können es unter den Bedingungen +# der GNU Affero General Public License, wie von der Free Software Foundation, +# Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren +# veröffentlichten Version, weiterverbreiten und/oder modifizieren. +# +# Dieses Programm wird in der Hoffnung, dass es nützlich sein wird, aber +# OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite +# Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. +# Siehe die GNU Affero General Public License für weitere Details. +# +# Sie sollten eine Kopie der GNU Affero General Public License zusammen mit diesem +# Programm erhalten haben. Wenn nicht, siehe . + + +def check_file_perm(filename): + """ + Check wether this process can open this file for R/W. + """ + if(not os.path.exists(filename)): + raise IOError("File not Found: {}".format(filename)) + + if(not (os.access(filename, os.R_OK) and os.access(filename, os.W_OK))): + raise IOError("File not readable/writable: {}".format(filename)) + + +def insert_header(filename, header, chunk_size = 1024, encoding = "UTF-8"): + """ + Insert the header ``header`` into the file with the name ``filename``. + """ + check_file_perm(filename) + + with open(filename, encoding = encoding) as fin: + os.unlink(filename) + + with open(filename, "w", encoding = encoding) as fout: + fout.write(header) + + chunk = fin.read(chunk_size) + while(chunk): + fout.write(chunk) + chunk = fin.read(chunk_size) + + + + diff --git a/licor/main.py b/licor/main.py new file mode 100644 index 0000000..4faabdc --- /dev/null +++ b/licor/main.py @@ -0,0 +1,174 @@ +# +# Copyright(c) Daniel Knüttel +# + +# This program is free software. +# Anyways if you think this program is worth it +# and we meet shout a drink for me. + + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# Dieses Programm ist Freie Software: Sie können es unter den Bedingungen +# der GNU Affero General Public License, wie von der Free Software Foundation, +# Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren +# veröffentlichten Version, weiterverbreiten und/oder modifizieren. +# +# Dieses Programm wird in der Hoffnung, dass es nützlich sein wird, aber +# OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite +# Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. +# Siehe die GNU Affero General Public License für weitere Details. +# +# Sie sollten eine Kopie der GNU Affero General Public License zusammen mit diesem +# Programm erhalten haben. Wenn nicht, siehe . + +from .work import work_all, work_this_path +from .templates import get_license_meta, format_license_template, get_templates_available +from .comment import uncomment_line_based, uncomment_multiline_line_oriented, uncomment_multiline_block_oriented +import os +from .io import insert_header, check_file_perm + +db_filename = ".licor.list" +def get_ignored(path): + try: + with open(os.path.join(path, db_filename)) as f: + return f.read().split("\n") + except: + return [] + +def write_ignored(path, ignored): + with open(os.path.join(path, db_filename)) as f: + f.write("\n".join([i for i in ingored if i])) + +def get_confirmation(text): + while(True): + print(text, " ", end = "") + res = input("(y/n) ") + if(res in ("Y", "y")): + return True + if(res in ("n", "N")): + return False + print("please enter Y or N") + +def get_path_confirmation(path): + return not get_confirmation("use " + path) + + +def raw_insert_into_db(db_path, path): + ignored = get_ignored(db_path) + if(not path in ignored): + ignored.append(path) + write_ignored(db_path, ignored) + + + +def list_this_path(path, file_ending, ignore_db = False): + if(ignore_db): + ignore_files = get_ignored(path) + else: + ignore_files = [] + work_this_path(path, file_ending, [print], ignore_files = ignore_files) + +def list_all(path, file_ending, ignore_paths, ignore_db = False): + if(not ignore_db): + ignore_files = get_ignored(path) + else: + ignore_files = [] + work_all(path, file_ending, [print], ignore_paths = ignore_paths, ignore_files = ignore_files) + +def list_db(path): + with open(os.path.join(path, db_filename)) as f: + print(f.read()) + +def print_uncommented_line_based(license_name, modifiers, data, + comment_start, fancy = False, after_comment = " ", pad_to = 0): + data = format_license_template(license_name, data, modifiers) + print(uncomment_line_based(data, comment_start, fancy = fancy, + after_comment = after_comment, pad_to = pad_to)) + +def print_uncommented_block_based(license_name, modifiers, data, + comment_start, comment_stop, method = "line", border = "*", fancy = False, after_comment = " ", pad_to = 0): + data = format_license_template(license_name, data, modifiers) + + if(method == "block"): + print(uncomment_multiline_block_oriented(data, + comment_start, comment_stop, + after_comment = after_comment, + fancy = fancy, border = border, + pad_to = pad_to)) + else: + print(uncomment_multiline_line_oriented(data, + comment_start, comment_stop, + after_comment = after_comment, + fancy = fancy, border = border, + pad_to = pad_to)) + +def print_template_options(): + meta = get_templates_available() + for template_name, data in meta.items(): + print(template_name) + for modifier in data["modifiers"]: + print("\t\t", modifier) + + + +def insert_templates_all(path, file_ending, ignore_paths, license_name, modifiers, data, + comment_start, comment_stop, format_, method = "line", border = "*", + fancy = False, after_comment = " ", pad_to = 0, + ignore_db = False, confirm = False): + if(not ignore_db): + ignore_files = get_ignored(path) + else: + ignore_files = [] + + + # check for file permissions + try: + work_all(path, file_ending, [check_file_perm], ignore_paths = ignore_paths, ignore_files = ignore_files) + except Exception as e: + print(e) + sys.exit(1) + + + callbacks = [] + + data = format_license_template(license_name, data, modifiers) + if(format_ == "line"): + text = uncomment_line_based(data, comment_start, fancy = fancy, + after_comment = after_comment, pad_to = pad_to) + else: + if(method == "block"): + text = uncomment_multiline_block_oriented(data, + comment_start, comment_stop, + after_comment = after_comment, + fancy = fancy, border = border, + pad_to = pad_to) + else: + text = uncomment_multiline_line_oriented(data, + comment_start, comment_stop, + after_comment = after_comment, + fancy = fancy, border = border, + pad_to = pad_to) + + if(confirm): + callbacks.append(get_path_confirmation) + + callbacks.append(lambda x: insert_header(x, text)) + + if(not ignore_db): + callback.append(lambda x: raw_insert_into_db(path, x)) + + work_all(path, file_ending, callbacks, ignore_paths = ignore_paths, ignore_files = ignore_files) + + diff --git a/licor/templates.py b/licor/templates.py new file mode 100644 index 0000000..5e6558b --- /dev/null +++ b/licor/templates.py @@ -0,0 +1,156 @@ +# +# Copyright(c) Daniel Knüttel +# + +# This program is free software. +# Anyways if you think this program is worth it +# and we meet shout a drink for me. + + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# Dieses Programm ist Freie Software: Sie können es unter den Bedingungen +# der GNU Affero General Public License, wie von der Free Software Foundation, +# Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren +# veröffentlichten Version, weiterverbreiten und/oder modifizieren. +# +# Dieses Programm wird in der Hoffnung, dass es nützlich sein wird, aber +# OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite +# Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. +# Siehe die GNU Affero General Public License für weitere Details. +# +# Sie sollten eine Kopie der GNU Affero General Public License zusammen mit diesem +# Programm erhalten haben. Wenn nicht, siehe . + +import pkg_resources, json +from itertools import permutations +from string import Template + +class TemplateException(Exception): + pass + +def get_resource_string(name): + """ + Return the resource string with the given name UTF-8 encoded. + """ + return pkg_resources.resource_string(__name__,"templates/" + name).decode("UTF-8") + + +def get_license_template(name, modifiers = []): + """ + Return a ``dict`` containing all necessary information for + filling a license template:: + + { + "name": , + "keywords": ["author", "date", ...], + "text":