chore: update .task.mk

This commit is contained in:
Daylin Morgan 2024-02-21 15:36:35 -06:00
parent 33ec203071
commit 28d60bffa0
Signed by: daylin
GPG Key ID: C1E52E7DD81DF79F
1 changed files with 164 additions and 118 deletions

282
.task.mk
View File

@ -1,10 +1,11 @@
# }> [github.com/daylinmorgan/task.mk] <{ # # }> [github.com/daylinmorgan/task.mk] <{ #
# Copyright (c) 2022 Daylin Morgan # Copyright (c) 2022 Daylin Morgan
# MIT License # MIT License
TASKMK_VERSION ?= 23.1.1 TASKMK_VERSION ?= 23.1.2
# task.mk should be included at the bottom of your Makefile with `-include .task.mk` # 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. # See below for the standard configuration options that should be set prior to including this file.
# You can update your .task.mk with `make _update-task.mk` # You can update your .task.mk with `make _update-task.mk`
# and initialize a repo with `bash <(curl -fsSL gh.dayl.in/task.mk/init)`.
# ---- [config] ---- # # ---- [config] ---- #
HEADER_STYLE ?= b_cyan HEADER_STYLE ?= b_cyan
ACCENT_STYLE ?= b_yellow ACCENT_STYLE ?= b_yellow
@ -18,122 +19,19 @@ WRAP ?= 100
# python f-string literals # python f-string literals
EPILOG ?= EPILOG ?=
USAGE ?={ansi.header}usage{ansi.end}:\n make <recipe>\n USAGE ?={ansi.header}usage{ansi.end}:\n make <recipe>\n
INHERIT_SHELL ?= PHONIFY ?=
# ---- [builtin recipes] ---- #
ifeq (help,$(firstword $(MAKECMDGOALS)))
HELP_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
export HELP_ARGS
endif
h help: ## show this help
$(call py,help_py)
_help: export SHOW_HIDDEN=true
_help: help
ifdef PRINT_VARS
TASKMK_VARS=$(subst $(eval ) ,<|>,$(foreach v,$(PRINT_VARS),$(v)=$($(v))))
.PHONY: vars v
v vars:
$(call py,vars_py,$(TASKMK_VARS))
endif
### |> -ws --hidden
### task.mk builtins: |> -d --hidden
_print-ansi: ## show all possible ansi color code combinations
$(call py,print_ansi_py)
# functions to take f-string literals and pass to python print
tprint = $(call py,print_py,$(1))
tprint-verbose= $(call py-verbose,print_py,$(1))
tconfirm = $(call py,confirm_py,$(1))
_update-task.mk: ## downloads version of task.mk (TASKMK_VERSION=)
$(call tprint,{a.b_cyan}Updating task.mk{a.end})
curl https://raw.githubusercontent.com/daylinmorgan/task.mk/$(TASKMK_VERSION)/task.mk -o .task.mk
.PHONY: h help _help _print-ansi _update-task.mk
TASK_MAKEFILE_LIST := $(filter-out $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)),$(MAKEFILE_LIST))
export MAKEFILE_LIST MAKE TASK_MAKEFILE_LIST
ifndef INHERIT_SHELL
SHELL := $(shell which bash)
endif
# ---- [python/bash script runner] ---- #
define _newline
endef
_escape_shellstring = $(subst `,\`,$(subst ",\",$(subst $$,\$$,$(subst \,\\,$1))))
_escape_printf = $(subst \,\\,$(subst %,%%,$1))
_create_string = $(subst $(_newline),\n,$(call _escape_shellstring,$(call _escape_printf,$1)))
_printline = printf -- "<----------------------------------->\n"
ifdef TASKMK_DEBUG
define _debug_runner
@printf "$(1) Script:\n";$(_printline);
@printf "$(call _create_string,$(3))\n" | cat -n
@$(_printline)
@$(2) <(printf "$(call _create_string,$(3))")
endef
py = $(call _debug_runner,Python,python3,$($(1)))
tbash = $(call _debug_runner,Bash,bash,$($(1)))
else
py = @python3 <(printf "$(call _create_string,$($(1)))")
tbash = @bash <(printf "$(call _create_string,$($(1)))")
endif
py-verbose = python3 <(printf "$(call _create_string,$($(1)))")
# ---- [python scripts] ---- # # ---- [python scripts] ---- #
define help_py define help_py
import argparse
from collections import namedtuple from collections import namedtuple
import os from pathlib import Path
import re
import subprocess import subprocess
import sys
from textwrap import wrap from textwrap import wrap
$(utils_py) $(utils_py)
$(parsers_py)
a = ansi = Ansi(target="stdout") a = ansi = Ansi(target="stdout")
MaxLens = namedtuple("MaxLens", "goal msg") MaxLens = namedtuple("MaxLens", "goal msg")
pattern = re.compile(
r"""
(?:
^\#\#\#\s+ # <- raw message
|
^(?:
(?:\#\#\s+)?
(?P<goal>.*?)(?:\s+\|>|:.*?\#\#)\s+
) # <- a custom goal or actual recipe
)
(?P<msg>.*?)?\s? # <- help text (optional)
(?:\|>\s+
(?P<msgargs>.*?)
)? # <- style args (optional)
$$
""",
re.X,
)
goal_pattern = re.compile(r"""^(?!#|\t)(.*):.*\n\t""", re.MULTILINE)
def parseargs(argstring):
parser = argparse.ArgumentParser()
parser.add_argument("--align")
parser.add_argument("-d", "--divider", action="store_true")
parser.add_argument("-ws", "--whitespace", action="store_true")
parser.add_argument("-ms", "--msg-style", type=str)
parser.add_argument("-gs", "--goal-style", type=str)
parser.add_argument("--hidden", action="store_true")
return parser.parse_args(argstring.split())
def divider(len): def divider(len):
return ansi.style(f" {cfg.div*len}", "div_style") return ansi.style(f" {cfg.div*len}", "div_style")
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_help(file, hidden=False):
for line in file.splitlines():
match = pattern.search(line)
if match:
if (
not hidden
and not os.getenv("SHOW_HIDDEN")
and str(match.groupdict().get("goal")).startswith("_")
):
pass
else:
yield {k: v for k, v in match.groupdict().items() if v is not None}
def recipe_help_header(goal): def recipe_help_header(goal):
item = [ item = [
i i
@ -149,10 +47,14 @@ def recipe_help_header(goal):
) )
else: else:
return f" {ansi.style(goal,'goal')}" return f" {ansi.style(goal,'goal')}"
def get_makefile_list():
pattern = re.compile(r'^\.?task.*?\.mk$$')
makefiles = os.getenv("MAKEFILE_LIST", "").split()
return (f for f in makefiles if not pattern.match(Path(f).name))
def get_goal_deps(goal="task.mk"): def get_goal_deps(goal="task.mk"):
make = os.getenv("MAKE", "make") make = os.getenv("MAKE", "make")
cmd = [make, "-p", "-n", "-i"] cmd = [make, "-p", "-n", "-i"]
for file in os.getenv("TASK_MAKEFILE_LIST", "").split(): for file in get_makefile_list():
cmd.extend(["-f", file]) cmd.extend(["-f", file])
database = subprocess.check_output(cmd, universal_newlines=True) database = subprocess.check_output(cmd, universal_newlines=True)
dep_pattern = re.compile(r"^" + goal + ":(.*)?") dep_pattern = re.compile(r"^" + goal + ":(.*)?")
@ -195,6 +97,9 @@ def fmt_goal(goal, msg, max_goal_len, argstr):
args = parseargs(argstr) args = parseargs(argstr)
goal_style = args.goal_style.strip() if args.goal_style else "goal" goal_style = args.goal_style.strip() if args.goal_style else "goal"
msg_style = args.msg_style.strip() if args.msg_style else "msg" msg_style = args.msg_style.strip() if args.msg_style else "msg"
if not os.getenv("SHOW_HIDDEN") and args.hidden:
return
return ( return (
ansi.style(f" {goal:>{max_goal_len}}", goal_style) ansi.style(f" {goal:>{max_goal_len}}", goal_style)
+ f" $(HELP_SEP) " + f" $(HELP_SEP) "
@ -209,20 +114,21 @@ def fmt_rawmsg(msg, argstr, maxlens):
if msg: if msg:
if args.align == "sep": if args.align == "sep":
lines.append( lines.append(
f"{' '*(maxlens.goal+len(cfg.sep)+4)}{ansi.style(msg,msg_style)}" f"{' '*(maxlens.goal+len(strip_ansi(cfg.sep))+4)}{ansi.style(msg,msg_style)}"
) )
elif args.align == "center": elif args.align == "center":
lines.append(f" {ansi.style(msg.center(sum(maxlens)),msg_style)}") lines.append(f" {ansi.style(msg.center(sum(maxlens)),msg_style)}")
else: else:
lines.append(f" {ansi.style(msg,msg_style)}") lines.append(f" {ansi.style(msg,msg_style)}")
if args.divider: if args.divider:
lines.append(divider(len(cfg.sep) + sum(maxlens) + 2)) lines.append(divider(len(strip_ansi(cfg.sep)) + sum(maxlens) + 2))
if args.whitespace: if args.whitespace:
lines.append("\n") lines.append("\n")
return lines return lines
def print_help(): def print_help():
lines = [cfg.usage] lines = [cfg.usage]
items = list(parse_help(gen_makefile())) items = list(parse_help(gen_makefile()))
maxlens = MaxLens( maxlens = MaxLens(
*( *(
max((*(len(item[x]) for item in items if x in item), 0)) max((*(len(item[x]) for item in items if x in item), 0))
@ -231,11 +137,11 @@ def print_help():
) )
for item in items: for item in items:
if "goal" in item: if "goal" in item:
lines.append( newgoal = fmt_goal(
fmt_goal( item["goal"], item["msg"], maxlens.goal, item.get("msgargs", "")
item["goal"], item["msg"], maxlens.goal, item.get("msgargs", "")
)
) )
if newgoal:
lines.append(newgoal)
else: else:
lines.extend(fmt_rawmsg(item["msg"], item.get("msgargs", ""), maxlens)) lines.extend(fmt_rawmsg(item["msg"], item.get("msgargs", ""), maxlens))
lines.append(cfg.epilog) lines.append(cfg.epilog)
@ -261,7 +167,6 @@ sys.stderr.write(f"""$(2)\n""")
endef endef
define print_ansi_py define print_ansi_py
$(utils_py) $(utils_py)
import sys
codes_names = { codes_names = {
getattr(ansi, attr): attr getattr(ansi, attr): attr
for attr in ansi.__dict__ for attr in ansi.__dict__
@ -285,10 +190,10 @@ define vars_py
import os import os
$(utils_py) $(utils_py)
ansi = Ansi(target="stdout") ansi = Ansi(target="stdout")
task_vars = tuple(v.split('=') for v in "$2".split('<|>')) task_vars = tuple(v.split("=") for v in "$2".split("<|>"))
length = max((len(v[0]) for v in task_vars)) length = max((len(v[0]) for v in task_vars))
rows = (f" {ansi.params}{v[0]:<{length}}{ansi.end} = {v[1]}" for v in task_vars) rows = (f" {ansi.params}{v[0]:<{length}}{ansi.end} = {v[1]}" for v in task_vars)
print('\n'.join((f"{ansi.header}vars{ansi.end}:\n", *rows,''))) print("\n".join((f"{ansi.header}vars{ansi.end}:\n", *rows, "")))
endef endef
define confirm_py define confirm_py
import sys import sys
@ -311,8 +216,16 @@ else:
endef endef
define utils_py define utils_py
import os import os
import re
import sys import sys
from dataclasses import dataclass from dataclasses import dataclass
def strip_ansi(txt):
"""
Removes ANSI escape codes, as defined by ECMA-048 in
http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
"""
pattern = re.compile(r"\x1B\[\d+(;\d+){0,2}m")
return pattern.sub("", txt)
@dataclass @dataclass
class Config: class Config:
div: str div: str
@ -410,6 +323,139 @@ class Ansi:
return f"{self.__dict__[style]}{text}{self.__dict__['end']}" return f"{self.__dict__[style]}{text}{self.__dict__['end']}"
a = ansi = Ansi() a = ansi = Ansi()
cfg = Config( cfg = Config(
"$(DIVIDER)", "$(HELP_SEP)", f"""$(EPILOG)""", f"""$(USAGE)""", int("$(WRAP)") "$(DIVIDER)", f"""$(HELP_SEP)""", f"""$(EPILOG)""", f"""$(USAGE)""", int("$(WRAP)")
) )
endef endef
define phonify_py
$(utils_py)
$(parsers_py)
def check_item(item):
if not "goal" in item:
return False
args = parseargs(item.get("msgargs", ""))
return not args.not_phony
def main():
items = " ".join(
(
i["goal"]
for i in parse_help(gen_makefile(), require_msg=False)
if check_item(i)
)
)
sys.stdout.write(".PHONY: " + items)
if __name__ == "__main__":
main()
endef
define parsers_py
import argparse
$(utils_py)
pattern = re.compile(
r"""
(?:
^\#\#\#\s+ # <- raw message
|
^(?:
(?:\#\#\s+)?
(?P<goal>.*?)(?:\s+\|>|:.*?\#\#)\s?
) # <- a custom goal or actual recipe
)
(?P<msg>.*?)?\s? # <- help text (optional)
(?:\|>\s+
(?P<msgargs>.*?)
)? # <- style args (optional)
$$
""",
re.X,
)
goal_pattern = re.compile(r"""^(?!#|\t)(.*):.*\n\t""", re.MULTILINE)
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_help(file, hidden=False, require_msg=True):
for line in file.splitlines():
match = pattern.search(line)
if match:
if (
not hidden
and not os.getenv("SHOW_HIDDEN")
and str(match.groupdict().get("goal")).startswith("_")
):
pass
elif not any(match.groupdict().get(k) for k in ("msg", "msgargs")):
if not require_msg:
yield {k: v for k, v in match.groupdict().items() if v is not None}
else:
yield {k: v for k, v in match.groupdict().items() if v is not None}
def parseargs(argstring):
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("--align")
parser.add_argument("-d", "--divider", action="store_true")
parser.add_argument("-ws", "--whitespace", action="store_true")
parser.add_argument("-ms", "--msg-style", type=str)
parser.add_argument("-gs", "--goal-style", type=str)
parser.add_argument("--hidden", action="store_true")
parser.add_argument("--not-phony", action="store_true")
return parser.parse_args(argstring.split())
endef
# ---- [python/bash script runner] ---- #
TASKMK_SHELL ?= $(shell cat /etc/shells | grep -E '/(bash|zsh)' | head -n 1)
ifndef TASKMK_SHELL
$(warning WARNING! task.mk features require bash or zsh)
endif
define _newline
endef
_escape_shellstring = $(subst ','\'',$(subst `,\`,$(subst ",\",$(subst $$,\$$,$(subst \,\\,$1)))))
_escape_printf = $(subst \,\\,$(subst %,%%,$1))
_create_string = $(subst $(_newline),\n,$(call _escape_shellstring,$(call _escape_printf,$1)))
_printline = printf -- "<----------------------------------->\n"
ifdef TASKMK_DEBUG
define _debug_runner
@printf "$(1) Script:\n";$(_printline);
@printf "$(call _create_string,$(3))\n" | cat -n
@$(_printline)
@$(2) <(printf "$(call _create_string,$(3))")
endef
py = $(call _debug_runner,Python,python3,$($(1)))
tbash = $(call _debug_runner,Bash,bash,$($(1)))
else
py = @$(TASKMK_SHELL) -c 'python3 <(printf "$(call _create_string,$($(1)))")'
tbash = @$(TASKMK_SHELL) -c '$(TASKMK_SHELL) <(printf "$(call _create_string,$($(1)))")'
endif
py-verbose = $(TASKMK_SHELL) -c 'python3 <(printf "$(call _create_string,$($(1)))")'
# ---- [builtin recipes] ---- #
ifeq (help,$(firstword $(MAKECMDGOALS)))
HELP_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
export HELP_ARGS
endif
h help: ## show this help
$(call py,help_py)
_help: export SHOW_HIDDEN=true
_help: help
ifdef PRINT_VARS
TASKMK_VARS=$(subst $(eval ) ,<|>,$(foreach v,$(PRINT_VARS),$(v)=$($(v))))
.PHONY: vars v
v vars:
$(call py,vars_py,$(TASKMK_VARS))
endif
### |> -ws --hidden
### task.mk builtins: |> -d --hidden
_print-ansi: ## show all possible ansi color code combinations
$(call py,print_ansi_py)
_update-task.mk: ## downloads version of task.mk (TASKMK_VERSION=)
$(call tprint,{a.b_cyan}Updating task.mk{a.end})
curl https://raw.githubusercontent.com/daylinmorgan/task.mk/$(TASKMK_VERSION)/task.mk -o .task.mk
# functions to take f-string literals and pass to python print
tprint = $(call py,print_py,$(1))
tprint-verbose= $(call py-verbose,print_py,$(1))
tconfirm = $(call py,confirm_py,$(1))
.PHONY: h help _help _print-ansi _update-task.mk
export MAKEFILE_LIST MAKE
ifdef PHONIFY
$(shell MAKEFILE_LIST='$(MAKEFILE_LIST)' $(call py-verbose,phonify_py))
endif