Last active
December 19, 2019 22:58
-
-
Save gastrodon/cc6d7a00345c9374a11624d25f76453c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse, os, re, json | |
def get_args(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument("-r", "--recurse", action = "store_true", help = "look recursively through source dirs") | |
parser.add_argument("-a", "--append", action = "store_true", help = "append contents to the outfile") | |
parser.add_argument("-v", "--value-spacing", action = "store_true", help = "put spaces around the value delimiter") | |
parser.add_argument("-o", "--out", type = str, default = "", help = "Writable destination. Writes to stdout if none") | |
parser.add_argument("-p", "--pattern", type = str, default = ".+", help = "Regex pattern to filter files by. Default is .+") | |
parser.add_argument("-d", "--dir-pattern", type = str, default = "^(?!.git).*$", help = "Regex pattern to filter dirs by. Default is ^(?!.git).*$ (ignores .git)") | |
parser.add_argument("-c", "--comment-spacing", type = int, default = 2, help = "spacing before comments") | |
parser.add_argument("sources", type = str, nargs = "*", help = "file sources, files or directories with files") | |
args = parser.parse_args() | |
return args | |
def main(): | |
args = get_args() | |
if not len(args.sources): | |
raise Exception("No sources given") | |
filenames = [gather_filenames(source, pattern = args.pattern, dir_pattern = args.dir_pattern, recurse = args.recurse) for source in args.sources] | |
filenames = [name for buf in filenames for name in buf] | |
content_sum = "\n".join(concat_files(filenames).values()) | |
sections_parsed = dict_parse_ini(content_sum) | |
ini_formatted = write_parsed_ini(sections_parsed, tabs_before_comment = args.comment_spacing, value_delim = " = " if args.value_spacing else "=") | |
if args.out: | |
with open(args.out, "a" if args.append else "w") as stream: | |
stream.write(ini_formatted) | |
return | |
print(ini_formatted) | |
def gather_filenames(path, *, pattern, dir_pattern, recurse): | |
if os.path.isfile(path): | |
return [path] | |
pattern = re.compile(pattern) | |
dir_pattern = re.compile(dir_pattern) | |
if recurse: | |
buf = [] | |
for root, dirs, files in os.walk(path): | |
dirs[:] = [dir for dir in dirs if dir_pattern.match(dir)] | |
buf = [*buf, *[f"{root}/{file}" for file in files if pattern.match(f"{file}")]] | |
return buf | |
return [f"{path}/{file}" for file in os.listdir(path) if os.path.isfile(f"{path}/{file}") and pattern.match(f"^{file}$")] | |
def comment_source(ini_content, filename): | |
pattern = re.compile("^[^\[#]+[^\]]$") | |
lines = [line.strip() for line in ini_content.split("\n")] | |
lines = [f"{line}\t # from {filename}" if pattern.match(line) else line for line in lines if len(line)] | |
return "\n".join(lines) | |
def concat_files(filenames): | |
map = {} | |
for name in filenames: | |
with open(name) as stream: | |
try: | |
map[name] = comment_source(stream.read(), name) | |
except UnicodeDecodeError: | |
raise Exception(f"Could not decode non-unicode contents of {name}") | |
return map | |
def parse_value_line(line): | |
field, value, comment = "", "", "" | |
comment_split = re.split("[;#]", line, 1) | |
if len(comment_split) - 1: | |
line, comment = comment_split | |
value_split = re.split("[=:]", line, 1) | |
field, value = value_split if len(value_split) - 1 else [line, ""] | |
return { | |
"field": field.strip(), | |
"value": value.strip(), | |
"comment": comment.strip() | |
} | |
def dict_parse_ini(ini_content): | |
section_pattern = re.compile("^\[.+\]$") | |
sections = {} | |
active = "" | |
for line in ini_content.split("\n"): | |
line = line.strip() | |
if not len(line): | |
continue | |
if section_pattern.match(line): | |
active = line | |
if not active in sections: | |
sections[active] = {} | |
continue | |
parsed_line = parse_value_line(line) | |
sections[active][parsed_line["field"]] = parsed_line | |
return sections | |
def parse_section(header, values): | |
values = "\n".join(values) | |
return f"{header}\n{values}" | |
def write_parsed_line(line, *, value_delim = "=", comment_delim = "#", tabs_before_comment = 2): | |
buf = line["field"] | |
if line["value"]: | |
buf = f"{buf}{value_delim}{line['value']}" | |
if line["comment"]: | |
tabs = "\t" * tabs_before_comment # fstrings do not like \ inside expression part | |
buf = f"{buf}{tabs}{comment_delim}{line['comment']}" | |
return buf | |
def write_parsed_ini(ini_data, *, tabs_before_comment = 2, value_delim = "="): | |
buf = [] | |
for head_key in ini_data.keys(): | |
lines_joined = "\n".join([ | |
write_parsed_line( | |
line, tabs_before_comment = tabs_before_comment, | |
value_delim = value_delim | |
) for line in ini_data[head_key].values() | |
]) | |
buf.append(f"{head_key}\n{lines_joined}") | |
return "\n\n".join(buf) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment