diff --git a/Makefile b/Makefile index 0577b37..af2d4f6 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,8 @@ TEMPLATES := $(shell find src/ -type f) msg = $(call tprint,{a.bold}==>{a.end} {a.magenta}$(1){a.end} {a.bold}<=={a.end}) +### task.mk development | args: --divider --align center + ## bootstrap | generate local dev environment .PHONY: bootstrap bootstrap: @@ -12,9 +14,10 @@ bootstrap: @mamba run -p ./env pip install yartsu @git config core.hooksPath .githooks -## lint | lint the python -.PHONY: lint -lint: +## l, lint | lint the python +.PHONY: l lint +l lint: + $(call msg,Linting) @black generate.py @black src/*.py --fast @@ -23,16 +26,6 @@ lint: assets: yartsu -o assets/help.svg -t "make help" -- make --no-print-directory help -define list_files_py -from pathlib import Path -print("files in $(2)") -print([f.name for f in (Path("$(2)").iterdir())]) -endef - -## list-% | use pathlib.Path to list files -list-%: - $(call py,list_files_py,$*) - ## release | release new version of task.mk .PHONY: release release: version-check @@ -47,6 +40,19 @@ release: version-check .PHONY: clean c clean: @rm -f task.mk .task.mk +### | args: --divider +### +### examples of task.mk features | args: --divider --align center +define list_files_py +from pathlib import Path +print("files in $(2)") +print([f.name for f in (Path("$(2)").iterdir())]) +endef + +## list-% | use pathlib.Path to list files +### name the directory in rule (make list-src) | args: --align sep +list-%: + $(call py,list_files_py,$*) .PHONY: version-check version-check: @@ -70,7 +76,6 @@ endef test-bash: $(call tbash,bash_script,test bash multiline) - define mlmsg {a.b_yellow} It can even be multiline!{a.end} @@ -87,6 +92,8 @@ info: $(call tprint,$(mlmsg)) $(call tprint,{a.custom(fg=(148, 255, 15),bg=(103, 2, 15))}Custom Colors TOO!{a.end}) +### | args: --divider + task.mk: $(TEMPLATES) generate.py ./generate.py $(VERSION) > task.mk diff --git a/src/ansi.py b/src/ansi.py index b4c9a3d..87d959a 100644 --- a/src/ansi.py +++ b/src/ansi.py @@ -33,6 +33,8 @@ class Ansi: def __init__(self): self.setcode("end", "\033[0m") + self.setcode("default", "\033[38m") + self.setcode("bg_default", "\033[48m") for name, byte in color2byte.items(): self.setcode(name, f"\033[{fg(byte)}m") self.setcode(f"b_{name}", f"\033[1;{fg(byte)}m") diff --git a/src/config.mk b/src/config.mk index 824a546..4fba464 100644 --- a/src/config.mk +++ b/src/config.mk @@ -4,8 +4,8 @@ PARAMS_COLOR ?= b_magenta ACCENT_COLOR ?= b_yellow GOAL_COLOR ?= $(ACCENT_COLOR) MSG_COLOR ?= faint +DIVIDER_COLOR ?= default HELP_SEP ?= | -HELP_SORT ?= # sort goals alphabetically # python f-string literals EPILOG ?= diff --git a/src/help.py b/src/help.py index 0810bb6..ccba3c2 100644 --- a/src/help.py +++ b/src/help.py @@ -1,40 +1,95 @@ #% extends "py-script.mk" %# #% block name %#help#% endblock %# #% block script %# +import argparse +from collections import namedtuple import os import re ##- '$(ansi_py)' -## -pattern = re.compile(r"^## (.*) \| (.*)") +MaxLens = namedtuple("MaxLens", "goal msg") -makefile = "" -for file in os.getenv("MAKEFILE_LIST").split(): - with open(file, "r") as f: - makefile += f.read() + "\n\n" +# double dollar signs to prevent make escaping them +pattern = re.compile( + r"^## (?P.*) \| (?P.*)|^### (?P.*?)?(?:\s?\| args: (?P.*?))?$$" +) -def get_help(file): +def rawargs(argstring): + parser = argparse.ArgumentParser() + parser.add_argument("-a", "--align") + parser.add_argument("-d", "--divider", action="store_true") + return parser.parse_known_args(argstring.split()) + + +def gen_makefile(): + makefile = "" + for file in os.getenv("MAKEFILE_LIST").split(): + with open(file, "r") as f: + makefile += f.read() + "\n\n" + return makefile + + +def parse_make(file): for line in file.splitlines(): match = pattern.search(line) if match: - if not os.getenv("SHOW_HIDDEN") and match.groups()[0].startswith("_"): - continue + if not os.getenv("SHOW_HIDDEN") and str( + match.groupdict().get("goal") + ).startswith("_"): + pass else: - yield match.groups() + yield {k: v for k, v in match.groupdict().items() if v is not None} -print(f"""$(USAGE)""") - -goals = list(get_help(makefile)) -if os.getenv("SORT_HELP", False): - goals.sort(key=lambda i: i[0]) -goal_len = max(len(goal[0]) for goal in goals) - -for goal, msg in goals: +def print_goal(goal, msg, max_goal_len): print( - f" {ansi.$(GOAL_COLOR)}{goal:>{goal_len}}{ansi.end} $(HELP_SEP) {ansi.$(MSG_COLOR)}{msg}{ansi.end}" + f" {ansi.$(GOAL_COLOR)}{goal:>{max_goal_len}}{ansi.end}" + " $(HELP_SEP) " + f"{ansi.$(MSG_COLOR)}{msg}{ansi.end}" ) -print(f"""$(EPILOG)""") + +def print_rawmsg(msg, argstr, maxlens): + args, unknown = rawargs(argstr) + if msg: + if args.align == "sep": + print( + f"{' '*(maxlens.goal+len('$(HELP_SEP)')+4)}{ansi.$(MSG_COLOR)}{msg}{ansi.end}" + ) + elif args.align == "center": + print(f" {ansi.$(MSG_COLOR)}{msg.center(sum(maxlens))}{ansi.end}") + else: + print(f" {ansi.$(MSG_COLOR)}{msg}{ansi.end}") + if args.divider: + print( + f"{ansi.$(DIVIDER_COLOR)} {'─'*(len('$(HELP_SEP)')+sum(maxlens)+2)}{ansi.end}" + ) + + +def print_help(): + print(f"""$(USAGE)""") + + items = list(parse_make(gen_makefile())) + maxlens = MaxLens( + *(max((len(item[x]) for item in items if x in item)) for x in ["goal", "msg"]) + ) + + for item in items: + if "goal" in item: + print_goal(item["goal"], item["msg"], maxlens.goal) + if "rawmsg" in item: + print_rawmsg(item["rawmsg"], item.get("args", ""), maxlens) + if len(item) == 1 and "args" in item: + args, unknown = rawargs(item["args"]) + if args.divider: + print( + " " + "─" * (len("$(HELP_SEP)") + maxlens.goal + maxlens.msg + 2) + ) + + print(f"""$(EPILOG)""") + + +print_help() #% endblock %# diff --git a/src/print-ansi.py b/src/print-ansi.py index 685e9ae..2e60235 100644 --- a/src/print-ansi.py +++ b/src/print-ansi.py @@ -3,10 +3,7 @@ #% block script %# ##- '$(ansi_py)' -## -codes_names = { - getattr(ansi, attr): attr - for attr in ansi.__dict__ -} +codes_names = {getattr(ansi, attr): attr for attr in ansi.__dict__} for code in sorted(codes_names.keys(), key=lambda item: (len(item), item)): print("{:>20} {}".format(codes_names[code], code + "******" + ansi.end)) diff --git a/task.mk b/task.mk index 2f5e973..a01f9a1 100644 --- a/task.mk +++ b/task.mk @@ -1,7 +1,7 @@ # }> [github.com/daylinmorgan/task.mk] <{ # # Copyright (c) 2022 Daylin Morgan # MIT License -# version: v22.9.12-dev +# version: v22.9.12-3-gcf1233a-dev # # task.mk should be included at the bottom of your Makefile. # See below for the standard configuration options that should be set prior to including this file. @@ -13,8 +13,8 @@ PARAMS_COLOR ?= b_magenta ACCENT_COLOR ?= b_yellow GOAL_COLOR ?= $(ACCENT_COLOR) MSG_COLOR ?= faint +DIVIDER_COLOR ?= default HELP_SEP ?= | -HELP_SORT ?= # sort goals alphabetically # python f-string literals EPILOG ?= @@ -95,42 +95,97 @@ pysh = printf "$(call create_string,$($(1)))" | python3 define help_py +import argparse +from collections import namedtuple import os import re $(ansi_py) -pattern = re.compile(r"^## (.*) \| (.*)") +MaxLens = namedtuple("MaxLens", "goal msg") -makefile = "" -for file in os.getenv("MAKEFILE_LIST").split(): - with open(file, "r") as f: - makefile += f.read() + "\n\n" +# double dollar signs to prevent make escaping them +pattern = re.compile( + r"^## (?P.*) \| (?P.*)|^### (?P.*?)?(?:\s?\| args: (?P.*?))?$$" +) -def get_help(file): +def rawargs(argstring): + parser = argparse.ArgumentParser() + parser.add_argument("-a", "--align") + parser.add_argument("-d", "--divider", action="store_true") + return parser.parse_known_args(argstring.split()) + + +def gen_makefile(): + makefile = "" + for file in os.getenv("MAKEFILE_LIST").split(): + with open(file, "r") as f: + makefile += f.read() + "\n\n" + return makefile + + +def parse_make(file): for line in file.splitlines(): match = pattern.search(line) if match: - if not os.getenv("SHOW_HIDDEN") and match.groups()[0].startswith("_"): - continue + if not os.getenv("SHOW_HIDDEN") and str( + match.groupdict().get("goal") + ).startswith("_"): + pass else: - yield match.groups() + yield {k: v for k, v in match.groupdict().items() if v is not None} -print(f"""$(USAGE)""") - -goals = list(get_help(makefile)) -if os.getenv("SORT_HELP", False): - goals.sort(key=lambda i: i[0]) -goal_len = max(len(goal[0]) for goal in goals) - -for goal, msg in goals: +def print_goal(goal, msg, max_goal_len): print( - f" {ansi.$(GOAL_COLOR)}{goal:>{goal_len}}{ansi.end} $(HELP_SEP) {ansi.$(MSG_COLOR)}{msg}{ansi.end}" + f" {ansi.$(GOAL_COLOR)}{goal:>{max_goal_len}}{ansi.end}" + " $(HELP_SEP) " + f"{ansi.$(MSG_COLOR)}{msg}{ansi.end}" ) -print(f"""$(EPILOG)""") + +def print_rawmsg(msg, argstr, maxlens): + args, unknown = rawargs(argstr) + if msg: + if args.align == "sep": + print( + f"{' '*(maxlens.goal+len('$(HELP_SEP)')+4)}{ansi.$(MSG_COLOR)}{msg}{ansi.end}" + ) + elif args.align == "center": + print(f" {ansi.$(MSG_COLOR)}{msg.center(sum(maxlens))}{ansi.end}") + else: + print(f" {ansi.$(MSG_COLOR)}{msg}{ansi.end}") + if args.divider: + print( + f"{ansi.$(DIVIDER_COLOR)} {'─'*(len('$(HELP_SEP)')+sum(maxlens)+2)}{ansi.end}" + ) + + +def print_help(): + print(f"""$(USAGE)""") + + items = list(parse_make(gen_makefile())) + maxlens = MaxLens( + *(max((len(item[x]) for item in items if x in item)) for x in ["goal", "msg"]) + ) + + for item in items: + if "goal" in item: + print_goal(item["goal"], item["msg"], maxlens.goal) + if "rawmsg" in item: + print_rawmsg(item["rawmsg"], item.get("args", ""), maxlens) + if len(item) == 1 and "args" in item: + args, unknown = rawargs(item["args"]) + if args.divider: + print( + " " + "─" * (len("$(HELP_SEP)") + maxlens.goal + maxlens.msg + 2) + ) + + print(f"""$(EPILOG)""") + + +print_help() endef @@ -168,6 +223,8 @@ class Ansi: def __init__(self): self.setcode("end", "\033[0m") + self.setcode("default", "\033[38m") + self.setcode("bg_default", "\033[48m") for name, byte in color2byte.items(): self.setcode(name, f"\033[{fg(byte)}m") self.setcode(f"b_{name}", f"\033[1;{fg(byte)}m") @@ -230,10 +287,7 @@ define print_ansi_py $(ansi_py) -codes_names = { - getattr(ansi, attr): attr - for attr in ansi.__dict__ -} +codes_names = {getattr(ansi, attr): attr for attr in ansi.__dict__} for code in sorted(codes_names.keys(), key=lambda item: (len(item), item)): print("{:>20} {}".format(codes_names[code], code + "******" + ansi.end))