Skip to content

Instantly share code, notes, and snippets.

@gastrodon
Last active December 19, 2019 22:58
Show Gist options
  • Save gastrodon/cc6d7a00345c9374a11624d25f76453c to your computer and use it in GitHub Desktop.
Save gastrodon/cc6d7a00345c9374a11624d25f76453c to your computer and use it in GitHub Desktop.
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