diff --git a/generate.py b/generate.py index e25488d..619614f 100755 --- a/generate.py +++ b/generate.py @@ -2,10 +2,9 @@ import sys from pathlib import Path - import jinja2 -py_script_names = ["help", "ansi", "info", "print-ansi", "vars", "confirm"] +py_script_names = ["help", "ansi", "info", "print-ansi", "vars", "confirm", "quit_make"] def get_jinja_env(): diff --git a/src/builtins.mk b/src/builtins.mk index 2684ebb..145e056 100644 --- a/src/builtins.mk +++ b/src/builtins.mk @@ -1,4 +1,8 @@ # ---- [builtin recipes] ---- # +ifeq (help,$(firstword $(MAKECMDGOALS))) + HELP_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) + export HELP_ARGS +endif ## h, help | show this help .PHONY: help h help h: diff --git a/src/confirm.py b/src/confirm.py index 3c60880..0ed0875 100644 --- a/src/confirm.py +++ b/src/confirm.py @@ -5,6 +5,7 @@ import sys ##- '$(ansi_py)' -## +##- '$(quit_make_py)' -## def confirm(): @@ -20,7 +21,7 @@ def confirm(): if confirm(): - sys.exit(0) + sys.exit() else: - sys.exit(1) + quit_make() #% endblock %# diff --git a/src/help.py b/src/help.py index 459912f..1f22030 100644 --- a/src/help.py +++ b/src/help.py @@ -7,6 +7,7 @@ import os import re ##- '$(ansi_py)' -## +##- '$(quit_make_py)' -## MaxLens = namedtuple("MaxLens", "goal msg") @@ -14,6 +15,7 @@ MaxLens = namedtuple("MaxLens", "goal msg") pattern = re.compile( r"^## (?P.*?) \| (?P.*?)(?:\s?\| args: (?P.*?))?$$|^### (?P.*?)?(?:\s?\| args: (?P.*?))?$$" ) +goal_pattern = re.compile(r"""^(?!#|\t)(.*):.*\n\t""", re.MULTILINE) def parseargs(argstring): @@ -35,11 +37,11 @@ def gen_makefile(): return makefile -def parse_make(file): +def parse_help(file,hidden=False): for line in file.splitlines(): match = pattern.search(line) if match: - if not os.getenv("SHOW_HIDDEN") and str( + if not hidden and not os.getenv("SHOW_HIDDEN") and str( match.groupdict().get("goal") ).startswith("_"): pass @@ -47,6 +49,43 @@ def parse_make(file): yield {k: v for k, v in match.groupdict().items() if v is not None} +def recipe_help_header(goal): + item = [ + i for i in list(parse_help(gen_makefile(),hidden=True)) if "goal" in i and goal == i["goal"] + ] + if item: + return fmt_goal( + item[0]["goal"], + item[0]["msg"], + len(item[0]["goal"]), + item[0].get("msgargs", ""), + ) + else: + return f" {ansi.style(goal,'$(GOAL_STYLE)')}:" + + +def parse_goal(file, goal): + goals = goal_pattern.findall(file) + matched_goal = [i for i in goals if goal in i.split()] + output = [] + + if matched_goal: + output.append(recipe_help_header(matched_goal[0])) + lines = file.splitlines() + loc = [n for n, l in enumerate(lines) if l.startswith(f"{matched_goal[0]}:")][0] + recipe = [] + for line in lines[loc + 1 :]: + if not line.startswith("\t"): + break + recipe.append(line) + output.append(divider(max((len(l) for l in recipe)) + 5)) + output.append("\n".join(recipe) + "\n") + else: + output.append(f"{ansi.b_red}ERROR{ansi.end} Failed to find goal: {goal}") + + return output + + def fmt_goal(goal, msg, max_goal_len, argstr): args = parseargs(argstr) goal_style = args.goal_style.strip() if args.goal_style else "$(GOAL_STYLE)" @@ -58,6 +97,10 @@ def fmt_goal(goal, msg, max_goal_len, argstr): ) +def divider(len): + return ansi.style(f" {'$(DIVIDER)'*len}", "$(DIVIDER_STYLE)") + + def fmt_rawmsg(msg, argstr, maxlens): args = parseargs(argstr) lines = [] @@ -74,12 +117,7 @@ def fmt_rawmsg(msg, argstr, maxlens): else: lines.append(f" {ansi.style(msg,msg_style)}") if args.divider: - lines.append( - ansi.style( - f" {'$(DIVIDER)'*(len('$(HELP_SEP)')+sum(maxlens)+2)}", - "$(DIVIDER_STYLE)", - ) - ) + lines.append(divider(len("$(HELP_SEP)") + sum(maxlens) + 2)) if args.whitespace: lines.append("\n") @@ -89,7 +127,7 @@ def fmt_rawmsg(msg, argstr, maxlens): def print_help(): lines = [f"""$(USAGE)"""] - items = list(parse_make(gen_makefile())) + items = list(parse_help(gen_makefile())) maxlens = MaxLens( *(max((len(item[x]) for item in items if x in item)) for x in ["goal", "msg"]) ) @@ -106,5 +144,21 @@ def print_help(): print("\n".join(lines)) -print_help() +def print_arg_help(help_args): + for arg in help_args.split(): + print(f"{ansi.style('task.mk recipe help','$(HEADER_STYLE)')}\n") + print("\n".join(parse_goal(gen_makefile(), arg))) + + +def main(): + help_args = os.getenv("HELP_ARGS") + if help_args: + quit_make() + print_arg_help(help_args) + else: + print_help() + + +if __name__ == "__main__": + main() #% endblock %# diff --git a/src/quit_make.py b/src/quit_make.py new file mode 100644 index 0000000..7ff6fa4 --- /dev/null +++ b/src/quit_make.py @@ -0,0 +1,16 @@ +#% extends "py-script.mk" %# +#% block name %#quit_make#% endblock %# +#% block script %# + +import os, signal, sys + + +def quit_make(): + old_stdout = sys.stdout + with open(os.devnull, "w") as f: + sys.stdout = f + os.kill(os.getppid(), signal.SIGQUIT) + sys.stdout = old_stdout + + +#% endblock %# diff --git a/task.mk b/task.mk index 64d734e..436ce7c 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.19-1-g031e38a-dev +# version: v22.9.19-3-g81a0031-dev # # task.mk should be included at the bottom of your Makefile with `-include .task.mk` # See below for the standard configuration options that should be set prior to including this file. @@ -78,7 +78,8 @@ pysh = python3 <(printf "$(call _create_string,$($(1)))") define help_py import argparse from collections import namedtuple -import os, re, signal, sys +import os +import re $(ansi_py) $(quit_make_py) MaxLens = namedtuple("MaxLens", "goal msg") @@ -101,11 +102,11 @@ def gen_makefile(): with open(file, "r") as f: makefile += f.read() + "\n\n" return makefile -def parse_help(file): +def parse_help(file,hidden=False): for line in file.splitlines(): match = pattern.search(line) if match: - if not os.getenv("SHOW_HIDDEN") and str( + if not hidden and not os.getenv("SHOW_HIDDEN") and str( match.groupdict().get("goal") ).startswith("_"): pass @@ -113,7 +114,7 @@ def parse_help(file): yield {k: v for k, v in match.groupdict().items() if v is not None} def recipe_help_header(goal): item = [ - i for i in list(parse_help(gen_makefile())) if "goal" in i and goal == i["goal"] + i for i in list(parse_help(gen_makefile(),hidden=True)) if "goal" in i and goal == i["goal"] ] if item: return fmt_goal( @@ -123,12 +124,13 @@ def recipe_help_header(goal): item[0].get("msgargs", ""), ) else: - return f" {ansi.style(matched_goal[0],'$(GOAL_STYLE)')}:" + return f" {ansi.style(goal,'$(GOAL_STYLE)')}:" def parse_goal(file, goal): goals = goal_pattern.findall(file) matched_goal = [i for i in goals if goal in i.split()] + output = [] if matched_goal: - output = [recipe_help_header(matched_goal[0])] + output.append(recipe_help_header(matched_goal[0])) lines = file.splitlines() loc = [n for n, l in enumerate(lines) if l.startswith(f"{matched_goal[0]}:")][0] recipe = [] @@ -194,9 +196,9 @@ def print_arg_help(help_args): print(f"{ansi.style('task.mk recipe help','$(HEADER_STYLE)')}\n") print("\n".join(parse_goal(gen_makefile(), arg))) def main(): - quit_make() help_args = os.getenv("HELP_ARGS") if help_args: + quit_make() print_arg_help(help_args) else: print_help()