Compare commits

..

No commits in common. "b19ddbae68570e865b124574713860a120fd60b4" and "234e3cee35ae0046b9b660314ef00c8621d0d415" have entirely different histories.

21 changed files with 381 additions and 592 deletions

View file

@ -1,22 +1,20 @@
VERSION ?= $(shell git describe --tags --always --dirty | sed s'/dirty/dev/') VERSION ?= $(shell git describe --tags --always --dirty | sed s'/dirty/dev/')
TEMPLATES := $(shell find src/ -type f) TEMPLATES := $(shell find src/ -type f)
.DEFAULT_GOAL := help .DEFAULT_GOAL := help
SHELL := /bin/zsh
INHERIT_SHELL = true
msg = $(if $(tprint),$(call tprint,{a.bold}==> {a.magenta}$(1){a.end}),@echo '==> $(1)') msg = $(if $(tprint),$(call tprint,{a.bold}==> {a.magenta}$(1){a.end}),@echo '==> $(1)')
### task.mk development | args: -d -ms b_green --align center ### task.mk development | args: -d -ms b_green --align center
## bootstrap | generate local dev environment ## bootstrap | generate local dev environment
.PHONY: bootstrap env hooks .PHONY: bootstrap
bootstrap: env hooks bootstrap:
env: $(call msg,Bootstrap Environment)
$(call msg,Bootstrapping Environment)
@mamba create -p ./env python jinja2 black -y @mamba create -p ./env python jinja2 black -y
@mamba run -p ./env pip install yartsu @mamba run -p ./env pip install yartsu
hooks:
@git config core.hooksPath .githooks @git config core.hooksPath .githooks
docs-env:
@mamba run -p ./env pip install mkdocs-material mkdocs-git-revision-date-localized-plugin
## l, lint | lint the python ## l, lint | lint the python
.PHONY: l lint .PHONY: l lint
l lint: l lint:
@ -69,7 +67,7 @@ task.mk: $(TEMPLATES) generate.py
./generate.py $(VERSION) > task.mk ./generate.py $(VERSION) > task.mk
define USAGE define USAGE
{a.style('usage','header')}:\n make <recipe>\n {a.$(HEADER_STYLE)}usage:{a.end}\n make <recipe>\n
Turn your {a.style('`Makefile`','b_magenta')} into Turn your {a.style('`Makefile`','b_magenta')} into
the {a.italic}{a.underline}task runner{a.end} you always needed. the {a.italic}{a.underline}task runner{a.end} you always needed.
See the example output below.\n See the example output below.\n

View file

@ -1,7 +1,7 @@
MAKEFLAGS += --no-print-directory MAKEFLAGS += --no-print-directory
COLS ?= 60 COLS ?= 60
ROWS ?= 20 ROWS ?= 20
EXAMPLES := check embedded recipe-help EXAMPLES := check embedded
CASTS := $(addsuffix /demo.cast, $(EXAMPLES)) CASTS := $(addsuffix /demo.cast, $(EXAMPLES))
all: $(CASTS) all: $(CASTS)

View file

@ -1,84 +1,129 @@
{"version": 2, "width": 48, "height": 12, "timestamp": 1664060067, "env": {"SHELL": "/usr/bin/zsh", "TERM": "xterm-256color"}} {"version": 2, "width": 48, "height": 12, "timestamp": 1663712361, "env": {"SHELL": "/usr/bin/zsh", "TERM": "xterm-256color"}}
[0.008028, "o", "\u001b[H\u001b[2J\u001b[3J"] [0.008114, "o", "\u001b[H\u001b[2J\u001b[3J"]
[0.008412, "o", "bash >> "] [0.00852, "o", "bash >> "]
[0.008649, "o", "ma"] [0.008755, "o", "m"]
[0.189116, "o", "ke"] [0.189398, "o", "a"]
[0.279092, "o", " -"] [0.279744, "o", "k"]
[0.369181, "o", "f "] [0.369775, "o", "e"]
[0.459296, "o", "ch"] [0.459919, "o", " -"]
[0.549434, "o", "ec"] [0.549908, "o", "f"]
[0.639704, "o", "k/"] [0.639982, "o", " "]
[0.729786, "o", "ch"] [0.730339, "o", "c"]
[0.81994, "o", "ec"] [0.820338, "o", "h"]
[0.910178, "o", "k."] [0.910414, "o", "ec"]
[1.090415, "o", "mk"] [1.090651, "o", "k"]
[1.180666, "o", " h"] [1.18077, "o", "/"]
[1.270767, "o", "el"] [1.271098, "o", "c"]
[1.360807, "o", "p\r\n"] [1.361098, "o", "h"]
[2.392643, "o", "\u001b[1;36musage:\u001b[0m\r\n\tmake <recipe>\r\n\t\r\n\tinteractivity w/ task.mk\r\n\r\n\u001b[1;33m check\u001b[0m │ \u001b[2mget user confirmation or exit\u001b[0m\r\n\u001b[1;33m h, help\u001b[0m │ \u001b[2mshow this help\u001b[0m\r\n\r\n"] [1.451216, "o", "ec"]
[4.398843, "o", "\u001b[H\u001b[2J\u001b[3J"] [1.541276, "o", "k"]
[4.398948, "o", "bash >> "] [1.631388, "o", "."]
[4.399576, "o", "ma"] [1.721603, "o", "m"]
[4.580421, "o", "ke"] [1.811718, "o", "k"]
[4.670533, "o", " -"] [1.992079, "o", " h"]
[4.760674, "o", "f "] [2.082138, "o", "e"]
[4.850908, "o", "ch"] [2.172379, "o", "l"]
[4.940859, "o", "ec"] [2.262403, "o", "p"]
[5.031095, "o", "k/"] [2.352473, "o", "\r\n"]
[5.121228, "o", "ch"] [3.381969, "o", "\u001b[1;36musage:\u001b[0m\r\n\tmake <recipe>\r\n\t\r\n\tinteractivity w/ task.mk\r\n\r\n\u001b[1;33m check\u001b[0m │ \u001b[2mget user confirmation or exit\u001b[0m\r\n\u001b[1;33m h, help\u001b[0m │ \u001b[2mshow this help\u001b[0m\r\n\r\n"]
[5.211271, "o", "ec"] [5.387545, "o", "\u001b[H\u001b[2J\u001b[3J"]
[5.301383, "o", "k."] [5.387892, "o", "bash >> "]
[5.481587, "o", "mk"] [5.390047, "o", "m"]
[5.571714, "o", " c"] [5.570645, "o", "a"]
[5.662002, "o", "he"] [5.660579, "o", "k"]
[5.752104, "o", "ck"] [5.750852, "o", "e"]
[5.842305, "o", "\r\n"] [5.841071, "o", " -"]
[6.864798, "o", "Would you like to proceed? \u001b[1;31m[Y/n]\u001b[0m "] [5.931225, "o", "f"]
[7.488194, "o", "y"] [6.021286, "o", " "]
[7.830894, "o", "\r\n"] [6.111373, "o", "c"]
[7.836313, "o", "you said yes!\r\n"] [6.201586, "o", "h"]
[9.840217, "o", "# "] [6.291778, "o", "ec"]
[10.020704, "o", "Le"] [6.472054, "o", "k"]
[10.110897, "o", "t'"] [6.562124, "o", "/"]
[10.201077, "o", "s "] [6.652262, "o", "c"]
[10.291171, "o", "tr"] [6.74248, "o", "h"]
[10.381382, "o", "y "] [6.832675, "o", "ec"]
[10.471532, "o", "ag"] [6.922668, "o", "k"]
[10.5617, "o", "ai"] [7.012809, "o", "."]
[10.651984, "o", "n "] [7.102973, "o", "m"]
[10.742177, "o", "bu"] [7.193151, "o", "k"]
[10.922348, "o", "t "] [7.373458, "o", " c"]
[11.012569, "o", "in"] [7.463783, "o", "h"]
[11.102605, "o", "st"] [7.553761, "o", "e"]
[11.192785, "o", "ea"] [7.643931, "o", "c"]
[11.282913, "o", "d "] [7.734197, "o", "k"]
[11.373109, "o", "sa"] [7.824356, "o", "\r\n"]
[11.463146, "o", "y "] [8.857505, "o", "Would you like to proceed? \u001b[1;31m[Y/n]\u001b[0m "]
[11.553266, "o", "no"] [9.54018, "o", "y"]
[11.6436, "o", " t"] [9.595198, "o", "\r\n"]
[11.823731, "o", "hi"] [9.598684, "o", "you said yes!\r\n"]
[11.913841, "o", "s "] [11.602689, "o", "#"]
[12.003886, "o", "ti"] [11.783071, "o", " "]
[12.094101, "o", "me"] [11.873235, "o", "L"]
[12.184197, "o", "\r\n"] [11.963465, "o", "e"]
[13.185929, "o", "\u001b[H\u001b[2J\u001b[3J"] [12.053444, "o", "t'"]
[13.185977, "o", "bash >> "] [12.143548, "o", "s"]
[13.186676, "o", "ma"] [12.233881, "o", " "]
[13.367108, "o", "ke"] [12.323915, "o", "t"]
[13.457234, "o", " -"] [12.41414, "o", "r"]
[13.547276, "o", "f "] [12.50428, "o", "y "]
[13.637696, "o", "ch"] [12.684471, "o", "a"]
[13.727919, "o", "ec"] [12.774773, "o", "g"]
[13.817921, "o", "k/"] [12.864864, "o", "a"]
[13.908217, "o", "ch"] [12.954989, "o", "i"]
[13.998375, "o", "ec"] [13.045011, "o", "n "]
[14.08855, "o", "k."] [13.135204, "o", "b"]
[14.268802, "o", "mk"] [13.225451, "o", "u"]
[14.359106, "o", " c"] [13.315649, "o", "t"]
[14.449085, "o", "he"] [13.405687, "o", " "]
[14.539194, "o", "ck"] [13.585943, "o", "in"]
[14.629387, "o", "\r\n"] [13.676051, "o", "s"]
[15.652945, "o", "Would you like to proceed? \u001b[1;31m[Y/n]\u001b[0m "] [13.766346, "o", "t"]
[16.544875, "o", "n"] [13.856324, "o", "e"]
[17.098374, "o", "\r\n"] [13.946542, "o", "a"]
[17.103306, "o", "make[1]: *** [check/check.mk:4: check] Error 1\r\n"] [14.036697, "o", "d "]
[14.126782, "o", "s"]
[14.216944, "o", "a"]
[14.307128, "o", "y"]
[14.487468, "o", " "]
[14.577551, "o", "no"]
[14.667739, "o", " "]
[14.757899, "o", "t"]
[14.848007, "o", "h"]
[14.93806, "o", "i"]
[15.0283, "o", "s "]
[15.118379, "o", "t"]
[15.208471, "o", "i"]
[15.388757, "o", "m"]
[15.47897, "o", "e"]
[15.569031, "o", "\r\n"]
[16.571969, "o", "\u001b[H\u001b[2J\u001b[3J"]
[16.572047, "o", "bash >> "]
[16.574017, "o", "m"]
[16.754499, "o", "a"]
[16.844612, "o", "k"]
[16.93468, "o", "e"]
[17.024972, "o", " -"]
[17.115137, "o", "f"]
[17.205244, "o", " "]
[17.295415, "o", "c"]
[17.385554, "o", "h"]
[17.475882, "o", "ec"]
[17.655989, "o", "k"]
[17.746279, "o", "/"]
[17.83643, "o", "c"]
[17.926644, "o", "h"]
[18.016673, "o", "ec"]
[18.106718, "o", "k"]
[18.197045, "o", "."]
[18.287031, "o", "m"]
[18.377151, "o", "k"]
[18.55735, "o", " c"]
[18.647478, "o", "h"]
[18.737616, "o", "e"]
[18.827879, "o", "c"]
[18.917992, "o", "k"]
[19.008133, "o", "\r\n"]
[20.034923, "o", "Would you like to proceed? \u001b[1;31m[Y/n]\u001b[0m "]
[22.08808, "o", "n"]
[22.227223, "o", "\r\n"]

View file

@ -1,83 +1,126 @@
{"version": 2, "width": 60, "height": 20, "timestamp": 1664060088, "env": {"SHELL": "/usr/bin/zsh", "TERM": "xterm-256color"}} {"version": 2, "width": 60, "height": 20, "timestamp": 1663712193, "env": {"SHELL": "/usr/bin/zsh", "TERM": "xterm-256color"}}
[0.008131, "o", "\u001b[H\u001b[2J\u001b[3J"] [0.008386, "o", "\u001b[H\u001b[2J\u001b[3J"]
[0.008522, "o", "bash >> "] [0.008986, "o", "bash >> "]
[0.008683, "o", "ma"] [0.009151, "o", "m"]
[0.189166, "o", "ke"] [0.189701, "o", "a"]
[0.279481, "o", " -"] [0.280162, "o", "k"]
[0.369776, "o", "f "] [0.37038, "o", "e"]
[0.460021, "o", "em"] [0.460683, "o", " -"]
[0.550229, "o", "be"] [0.551001, "o", "f"]
[0.640359, "o", "dd"] [0.641267, "o", " "]
[0.730591, "o", "ed"] [0.731609, "o", "e"]
[0.820572, "o", "/e"] [0.821777, "o", "m"]
[0.910745, "o", "mb"] [0.912103, "o", "be"]
[1.091087, "o", "ed"] [1.092658, "o", "d"]
[1.181312, "o", "de"] [1.182705, "o", "d"]
[1.271441, "o", "d."] [1.273152, "o", "e"]
[1.361501, "o", "mk"] [1.363457, "o", "d"]
[1.451615, "o", " h"] [1.453758, "o", "/e"]
[1.541783, "o", "el"] [1.544059, "o", "m"]
[1.63204, "o", "p\r\n"] [1.634282, "o", "b"]
[2.664242, "o", "\u001b[1;36musage:\u001b[0m\r\n\tmake <recipe>\r\n\t\r\n\texamples of embedded scripts in `\u001b[35mMakefile\u001b[0m`\r\n\r\n \u001b[1;31m examples of task.mk features \u001b[0m\r\n\u001b[38m ─────────────────────────────────────────────────────\u001b[0m\r\n\u001b[1;33m list-%\u001b[0m │ \u001b[2muse pathlib.Path to list files\u001b[0m\r\n \u001b[2mname the directory in rule (make list-src)\u001b[0m\r\n\u001b[1;33m embedded-bash\u001b[0m │ \u001b[2mbash script with pipes and make input\u001b[0m\r\n\u001b[1;33m h, help\u001b[0m │ \u001b[2mshow this help\u001b[0m\r\n\r\n"] [1.724452, "o", "e"]
[4.671801, "o", "\u001b[H\u001b[2J\u001b[3Jbash >> "] [1.814601, "o", "d"]
[4.67392, "o", "ma"] [1.995155, "o", "de"]
[4.854455, "o", "ke"] [2.085437, "o", "d"]
[4.944728, "o", " -"] [2.175546, "o", "."]
[5.034921, "o", "f "] [2.265856, "o", "m"]
[5.124982, "o", "em"] [2.356192, "o", "k"]
[5.215103, "o", "be"] [2.446468, "o", " h"]
[5.305188, "o", "dd"] [2.536781, "o", "e"]
[5.395353, "o", "ed"] [2.627101, "o", "l"]
[5.485457, "o", "/e"] [2.717423, "o", "p"]
[5.576246, "o", "mb"] [2.897934, "o", "\r\n"]
[5.75589, "o", "ed"] [3.931131, "o", "\u001b[1;36musage:\u001b[0m\r\n\tmake <recipe>\r\n\t\r\n\texamples of embedded scripts in `\u001b[35mMakefile\u001b[0m`\r\n\r\n \u001b[1;31m examples of task.mk features \u001b[0m\r\n\u001b[38m ─────────────────────────────────────────────────────\u001b[0m\r\n\u001b[1;33m list-%\u001b[0m │ \u001b[2muse pathlib.Path to list files\u001b[0m\r\n \u001b[2mname the directory in rule (make list-src)\u001b[0m\r\n\u001b[1;33m embedded-bash\u001b[0m │ \u001b[2mbash script with pipes and make input\u001b[0m\r\n\u001b[1;33m h, help\u001b[0m │ \u001b[2mshow this help\u001b[0m\r\n\r\n"]
[5.846014, "o", "de"] [5.936874, "o", "\u001b[H\u001b[2J\u001b[3J"]
[5.93612, "o", "d."] [5.937167, "o", "bash >> "]
[6.026298, "o", "mk"] [5.939502, "o", "m"]
[6.11644, "o", " l"] [6.120169, "o", "a"]
[6.206464, "o", "is"] [6.210505, "o", "k"]
[6.296631, "o", "t-"] [6.300671, "o", "e"]
[6.386781, "o", "em"] [6.390788, "o", " -"]
[6.477052, "o", "be"] [6.480981, "o", "f"]
[6.657201, "o", "dd"] [6.571285, "o", " "]
[6.747412, "o", "ed"] [6.661457, "o", "e"]
[6.837566, "o", "\r\n"] [6.751758, "o", "m"]
[7.867599, "o", "files in embedded\r\n['embedded.mk', 'demo.cast', 'record.sh', 'index.md']\r\n"] [6.842048, "o", "be"]
[9.872436, "o", "\u001b[H\u001b[2J\u001b[3J"] [7.022576, "o", "d"]
[9.872754, "o", "bash >> "] [7.112852, "o", "d"]
[9.874651, "o", "ma"] [7.203064, "o", "e"]
[10.055031, "o", "ke"] [7.293421, "o", "d"]
[10.145171, "o", " -"] [7.383397, "o", "/e"]
[10.235256, "o", "f "] [7.473873, "o", "m"]
[10.325347, "o", "em"] [7.564152, "o", "b"]
[10.415525, "o", "be"] [7.65431, "o", "e"]
[10.505721, "o", "dd"] [7.744607, "o", "d"]
[10.595814, "o", "ed"] [7.925168, "o", "de"]
[10.685931, "o", "/e"] [8.015472, "o", "d"]
[10.776068, "o", "mb"] [8.105767, "o", "."]
[10.956362, "o", "ed"] [8.195936, "o", "m"]
[11.046547, "o", "de"] [8.286184, "o", "k"]
[11.136692, "o", "d."] [8.376537, "o", " l"]
[11.226851, "o", "mk"] [8.466624, "o", "i"]
[11.316894, "o", " e"] [8.557058, "o", "s"]
[11.406996, "o", "mb"] [8.64739, "o", "t"]
[11.497205, "o", "ed"] [8.827942, "o", "-"]
[11.58725, "o", "de"] [8.91803, "o", "em"]
[11.677471, "o", "d-"] [9.008446, "o", "b"]
[11.85761, "o", "ba"] [9.098742, "o", "e"]
[11.947917, "o", "sh"] [9.189059, "o", "d"]
[12.038071, "o", "\r\n"] [9.279328, "o", "d"]
[13.045213, "o", "Is the process running bash? We can check with ps\r\n"] [9.36965, "o", "ed"]
[13.051057, "o", "bash\r\n"] [9.459574, "o", "\r\n"]
[13.051385, "o", "What text to figlet? \r\n"] [10.492826, "o", "files in embedded\r\n"]
[14.434504, "o", "t"] [10.492943, "o", "['embedded.mk', 'demo.cast', 'record.sh', 'index.md']\r\n"]
[14.503239, "o", "a"] [12.498865, "o", "\u001b[H\u001b[2J\u001b[3J"]
[14.605384, "o", "s"] [12.498984, "o", "bash >> "]
[14.745978, "o", "k"] [12.501382, "o", "m"]
[14.946139, "o", "."] [12.681906, "o", "a"]
[15.080651, "o", "m"] [12.7722, "o", "k"]
[15.228545, "o", "k"] [12.862501, "o", "e"]
[15.617669, "o", "\r\n"] [12.952783, "o", " -"]
[15.618495, "o", " _ _ _ \r\n| |_ __ _ ___| | __ _ __ ___ | | __\r\n| __/ _` / __| |/ / | '_ ` _ \\| |/ /\r\n| || (_| \\__ \\ < _| | | | | | < \r\n \\__\\__,_|___/_|\\_(_)_| |_| |_|_|\\_\\\r\n \r\n"] [13.043086, "o", "f"]
[15.618636, "o", "the argument below as given in the makefile itself\r\n"] [13.13339, "o", " "]
[15.618695, "o", "it's expanded before the script is passed to bash\r\nbash multiline is probably working\r\n"] [13.223546, "o", "e"]
[13.313887, "o", "m"]
[13.40413, "o", "be"]
[13.584464, "o", "d"]
[13.674721, "o", "d"]
[13.764931, "o", "e"]
[13.855143, "o", "d"]
[13.945476, "o", "/e"]
[14.035801, "o", "m"]
[14.126086, "o", "b"]
[14.216402, "o", "e"]
[14.306688, "o", "d"]
[14.487263, "o", "de"]
[14.577566, "o", "d"]
[14.667572, "o", "."]
[14.757974, "o", "m"]
[14.848201, "o", "k"]
[14.938383, "o", " e"]
[15.02867, "o", "m"]
[15.118961, "o", "b"]
[15.2091, "o", "e"]
[15.38968, "o", "d"]
[15.479924, "o", "de"]
[15.570276, "o", "d"]
[15.66027, "o", "-"]
[15.750729, "o", "b"]
[15.84091, "o", "a"]
[15.931277, "o", "sh"]
[16.021429, "o", "\r\n"]
[17.044626, "o", "Is the process running bash? We can check with ps\r\n"]
[17.063304, "o", "bash\r\n"]
[17.063756, "o", "What text to figlet? \r\n"]
[20.107984, "o", "t"]
[20.125262, "o", "a"]
[20.276406, "o", "s"]
[20.454083, "o", "k"]
[20.705234, "o", "."]
[20.931947, "o", "m"]
[21.010096, "o", "k"]
[21.288327, "o", "\r\n"]
[21.292384, "o", " _ _ _ \r\n| |_ __ _ ___| | __ _ __ ___ | | __\r\n| __/ _` / __| |/ / | '_ ` _ \\| |/ /\r\n| || (_| \\__ \\ < _| | | | | | < \r\n \\__\\__,_|___/_|\\_(_)_| |_| |_|_|\\_\\\r\n \r\n"]
[21.292754, "o", "the argument below as given in the makefile itself\r\n"]
[21.292898, "o", "it's expanded before the script is passed to bash\r\n"]
[21.292925, "o", "bash multiline is probably working\r\n"]

View file

@ -5,5 +5,4 @@ source "$(dirname "${BASH_SOURCE[0]}")/../functions.sh"
cmd 'make -f embedded/embedded.mk help' cmd 'make -f embedded/embedded.mk help'
cmd 'make -f embedded/embedded.mk list-embedded' cmd 'make -f embedded/embedded.mk list-embedded'
cmd 'make -f embedded/embedded.mk embedded-bash' cmd 'make -f embedded/embedded.mk embedded-bash'
sleep 1

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
msg() { msg() {
printf '%s\n' "$1" | pv -qL 20 printf '%s\n' "$1" | pv -qL 12
sleep 1 sleep 1
} }

View file

@ -1,14 +1,10 @@
# Examples # Examples
[Confirm](./check) [`Check`](./check)
: Perform a basic confirmation test with the user and exit with error code 1 if input is N/n. : Perform a basic confirmation test with the user and exit with error code 1 if input is N/n.
[Embedded Scripts](./embedded) [`Embedded`](./embedded)
: Use the builtin functions to write multi-line python/bash scripts directly in your `Makefile` : Use the builtin functions to write multi-line python/bash scripts directly in your `Makefile`
[Recipe Help](./recipe-help)
: Display the target, docstring and recipe for a given target then exit.

View file

@ -1,138 +0,0 @@
{"version": 2, "width": 60, "height": 20, "timestamp": 1664060106, "env": {"SHELL": "/usr/bin/zsh", "TERM": "xterm-256color"}}
[0.007806, "o", "\u001b[H\u001b[2J\u001b[3J"]
[0.008354, "o", "bash >> "]
[0.008532, "o", "ma"]
[0.189207, "o", "ke"]
[0.279458, "o", " -"]
[0.369697, "o", "f "]
[0.459892, "o", "re"]
[0.550111, "o", "ci"]
[0.640099, "o", "pe"]
[0.730309, "o", "-h"]
[0.820408, "o", "el"]
[0.910572, "o", "p/"]
[1.090837, "o", "re"]
[1.180944, "o", "ci"]
[1.271024, "o", "pe"]
[1.36131, "o", "-h"]
[1.451464, "o", "el"]
[1.541712, "o", "p."]
[1.631676, "o", "mk"]
[1.721815, "o", " h"]
[1.812133, "o", "el"]
[1.99248, "o", "p\r\n"]
[3.025715, "o", "\u001b[1;36musage:\u001b[0m\r\n\tmake <recipe>\r\n\tmake help <recipe>\r\n\r\n\u001b[1;33m deps-only\u001b[0m │ \u001b[2ma task/target with dependencies\u001b[0m\r\n\u001b[1;33m foo\u001b[0m │ \u001b[2ma dummy rule that depends on the local files\u001b[0m\r\n\u001b[1;33m h, help\u001b[0m │ \u001b[2mshow this help\u001b[0m\r\n\r\n"]
[5.032814, "o", "\u001b[H\u001b[2J\u001b[3Jbash >> "]
[5.034705, "o", "ma"]
[5.215256, "o", "ke"]
[5.305482, "o", " -"]
[5.395504, "o", "f "]
[5.485805, "o", "re"]
[5.576036, "o", "ci"]
[5.666095, "o", "pe"]
[5.756177, "o", "-h"]
[5.846563, "o", "el"]
[5.93671, "o", "p/"]
[6.11689, "o", "re"]
[6.206935, "o", "ci"]
[6.297128, "o", "pe"]
[6.387205, "o", "-h"]
[6.477454, "o", "el"]
[6.567496, "o", "p."]
[6.657713, "o", "mk"]
[6.747729, "o", " h"]
[6.838006, "o", "el"]
[7.018242, "o", "p "]
[7.108261, "o", "he"]
[7.198405, "o", "lp"]
[7.288809, "o", "\r\n"]
[8.318829, "o", "\u001b[1;36mtask.mk recipe help\u001b[0m\r\n\r\n"]
[8.325694, "o", " \u001b[1;33mh help\u001b[0m\r\n\u001b[38m ────────────────────────────────────────────────────────\u001b[0m\r\n $(call py,help_py) || { echo \"exiting early!\"; exit 1; }\r\n\r\n"]
[8.329695, "o", "exiting early!\r\nmake[1]: *** [/home/daylin/dev/github/mine/task.mk/task.mk:30: help] Error 1\r\n"]
[10.332715, "o", "\u001b[H\u001b[2J\u001b[3J"]
[10.333056, "o", "bash >> "]
[10.335005, "o", "ma"]
[10.515605, "o", "ke"]
[10.605896, "o", " -"]
[10.696226, "o", "f "]
[10.78629, "o", "re"]
[10.876669, "o", "ci"]
[10.966704, "o", "pe"]
[11.056858, "o", "-h"]
[11.146831, "o", "el"]
[11.236969, "o", "p/"]
[11.417281, "o", "re"]
[11.507468, "o", "ci"]
[11.597442, "o", "pe"]
[11.687589, "o", "-h"]
[11.777832, "o", "el"]
[11.867969, "o", "p."]
[11.958105, "o", "mk"]
[12.048177, "o", " h"]
[12.138291, "o", "el"]
[12.318676, "o", "p "]
[12.408796, "o", "de"]
[12.498823, "o", "ps"]
[12.588985, "o", "-o"]
[12.679133, "o", "nl"]
[12.769237, "o", "y\r\n"]
[13.807661, "o", "\u001b[1;36mtask.mk recipe help\u001b[0m\r\n\r\n"]
[13.816448, "o", "\u001b[1;33m deps-only\u001b[0m │ \u001b[2ma task/target with dependencies\u001b[0m\r\n \u001b[38mdeps\u001b[0m: \u001b[2mfoo\u001b[0m\r\n\r\n"]
[13.820255, "o", "exiting early!\r\n"]
[13.820461, "o", "make[1]: *** [/home/daylin/dev/github/mine/task.mk/task.mk:30: help] Error 1\r\n"]
[15.823763, "o", "\u001b[H\u001b[2J\u001b[3J"]
[15.824036, "o", "bash >> "]
[15.826016, "o", "ma"]
[16.006673, "o", "ke"]
[16.09685, "o", " -"]
[16.186936, "o", "f "]
[16.277228, "o", "re"]
[16.367339, "o", "ci"]
[16.45744, "o", "pe"]
[16.547483, "o", "-h"]
[16.637622, "o", "el"]
[16.727742, "o", "p/"]
[16.907989, "o", "re"]
[16.99817, "o", "ci"]
[17.088391, "o", "pe"]
[17.178352, "o", "-h"]
[17.268476, "o", "el"]
[17.358766, "o", "p."]
[17.448842, "o", "mk"]
[17.539039, "o", " h"]
[17.629197, "o", "el"]
[17.809354, "o", "p "]
[17.899578, "o", "fo"]
[17.989694, "o", "o\r\n"]
[19.020047, "o", "\u001b[1;36mtask.mk recipe help\u001b[0m\r\n\r\n"]
[19.03039, "o", "\u001b[1;33m foo\u001b[0m │ \u001b[2ma dummy rule that depends on the local files\u001b[0m\r\n \u001b[38mdeps\u001b[0m: \u001b[2mcheck embedded functions.sh index.md Makefile recipe-help\u001b[0m\r\n\u001b[38m ────────────────────────────\u001b[0m\r\n @echo 'this is a dummy rule'\r\n\r\n"]
[19.035496, "o", "exiting early!\r\n"]
[19.035706, "o", "make[1]: *** [/home/daylin/dev/github/mine/task.mk/task.mk:30: help] Error 1\r\n"]
[21.039168, "o", "\u001b[H\u001b[2J\u001b[3J"]
[21.03927, "o", "bash >> "]
[21.041378, "o", "ma"]
[21.221916, "o", "ke"]
[21.31205, "o", " -"]
[21.402214, "o", "f "]
[21.492482, "o", "re"]
[21.582638, "o", "ci"]
[21.672609, "o", "pe"]
[21.762748, "o", "-h"]
[21.853045, "o", "el"]
[21.943154, "o", "p/"]
[22.123286, "o", "re"]
[22.2134, "o", "ci"]
[22.303509, "o", "pe"]
[22.393685, "o", "-h"]
[22.483815, "o", "el"]
[22.574005, "o", "p."]
[22.664056, "o", "mk"]
[22.754229, "o", " h"]
[22.844432, "o", "el"]
[23.02464, "o", "p "]
[23.114692, "o", "ba"]
[23.205123, "o", "r\r\n"]
[24.260098, "o", "\u001b[1;36mtask.mk recipe help\u001b[0m\r\n\r\n"]
[24.267129, "o", " \u001b[1;33mbar\u001b[0m\r\n\u001b[38m ─────────────────────────────────────\u001b[0m\r\n @echo 'some rule with no help string'\r\n\r\n"]
[24.271004, "o", "exiting early!\r\n"]
[24.271137, "o", "make[1]: *** [/home/daylin/dev/github/mine/task.mk/task.mk:30: help] Error 1\r\n"]

View file

@ -1,12 +0,0 @@
---
asciinema: true
---
# Recipe Help
<div id="demo-cast"></div>
```make title="recipe-help.mk"
--8<-- "docs/examples/recipe-help/recipe-help.mk"
```

View file

@ -1,24 +0,0 @@
## deps-only | a task/target with dependencies
.PHONY: deps-only
deps-only: foo
## foo | a dummy rule that depends on the local files
.PHONY: foo
foo: $(wildcard *)
@echo 'this is a dummy rule'
# bar but no docstring
.PHONY: bar
bar:
@echo 'some rule with no help string'
define USAGE
{a.header}usage:{a.end}
make <recipe>
make help <recipe>
endef
.DEFAULT_GOAL = help
include $(shell git rev-parse --show-toplevel)/task.mk

View file

@ -1,11 +0,0 @@
#!/usr/bin/env bash
source "$(dirname "${BASH_SOURCE[0]}")/../functions.sh"
cmd 'make -f recipe-help/recipe-help.mk help'
cmd 'make -f recipe-help/recipe-help.mk help help'
cmd 'make -f recipe-help/recipe-help.mk help deps-only'
cmd 'make -f recipe-help/recipe-help.mk help foo'
cmd 'make -f recipe-help/recipe-help.mk help bar'
sleep 1

View file

@ -4,14 +4,7 @@ import sys
from pathlib import Path from pathlib import Path
import jinja2 import jinja2
py_script_names = [ py_script_names = ["help", "ansi", "info", "print-ansi", "vars", "confirm"]
"help",
"info",
"print-ansi",
"vars",
"confirm",
"utils",
]
def get_jinja_env(): def get_jinja_env():

View file

@ -1,19 +1,8 @@
#% extends "py-script.mk" %# #% extends "py-script.mk" %#
#% block name %#utils#% endblock %# #% block name %#ansi#% endblock %#
#% block script %# #% block script %#
import os import os
import sys import sys
from dataclasses import dataclass
@dataclass
class Config:
div: str
sep: str
epilog: str
usage: str
wrap: int
color2byte = dict( color2byte = dict(
black=0, black=0,
@ -51,7 +40,6 @@ class Ansi:
) )
for name, byte in state2byte.items(): for name, byte in state2byte.items():
self.setcode(name, f"\033[{byte}m") self.setcode(name, f"\033[{byte}m")
self.add_cfg()
def setcode(self, name, escape_code): def setcode(self, name, escape_code):
"""create attr for style and escape code""" """create attr for style and escape code"""
@ -89,18 +77,6 @@ class Ansi:
return code + end return code + end
def add_cfg(self):
cfg_styles = {
"header": "$(HEADER_STYLE)",
"accent": "$(ACCENT_STYLE)",
"params": "$(PARAMS_STYLE)",
"goal": "$(GOAL_STYLE)",
"msg": "$(MSG_STYLE)",
"div_style": "$(DIVIDER_STYLE)",
}
for name, style in cfg_styles.items():
self.setcode(name, getattr(self, style))
def style(self, text, style): def style(self, text, style):
if style not in self.__dict__: if style not in self.__dict__:
print(f"unknown style: {style}") print(f"unknown style: {style}")
@ -110,7 +86,4 @@ class Ansi:
a = ansi = Ansi() a = ansi = Ansi()
cfg = Config(
"$(DIVIDER)", "$(HELP_SEP)", f"""$(EPILOG)""", f"""$(USAGE)""",int('$(WRAP)'))
#% endblock %# #% endblock %#

View file

@ -4,8 +4,17 @@ ifeq (help,$(firstword $(MAKECMDGOALS)))
export HELP_ARGS export HELP_ARGS
endif endif
## h, help | show this help ## h, help | show this help
ifdef HELP_ARGS
help: help-args
$(error exiting early)
.PHONY: help-args
help-args:
$(call py,help_py)
else
.PHONY: help h
h help: h help:
$(call py,help_py) || { echo "exiting early!"; exit 1; } $(call py,help_py)
endif
.PHONY: _help .PHONY: _help
_help: export SHOW_HIDDEN=true _help: export SHOW_HIDDEN=true
_help: help _help: help
@ -29,8 +38,7 @@ tconfirm = $(call py,confirm_py,$(1))
_update-task.mk: _update-task.mk:
$(call tprint,{a.b_cyan}Updating task.mk{a.end}) $(call tprint,{a.b_cyan}Updating task.mk{a.end})
curl https://raw.githubusercontent.com/daylinmorgan/task.mk/main/task.mk -o .task.mk curl https://raw.githubusercontent.com/daylinmorgan/task.mk/main/task.mk -o .task.mk
TASK_MAKEFILE_LIST := $(filter-out $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)),$(MAKEFILE_LIST)) export MAKEFILE_LIST
export MAKEFILE_LIST MAKE TASK_MAKEFILE_LIST
ifndef INHERIT_SHELL ifndef INHERIT_SHELL
SHELL := $(shell which bash) SHELL := $(shell which bash)
endif endif

View file

@ -4,11 +4,10 @@ ACCENT_STYLE ?= b_yellow
PARAMS_STYLE ?= $(ACCENT_STYLE) PARAMS_STYLE ?= $(ACCENT_STYLE)
GOAL_STYLE ?= $(ACCENT_STYLE) GOAL_STYLE ?= $(ACCENT_STYLE)
MSG_STYLE ?= faint MSG_STYLE ?= faint
DIVIDER ?=
DIVIDER_STYLE ?= default DIVIDER_STYLE ?= default
DIVIDER ?=
HELP_SEP ?= HELP_SEP ?=
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_STYLE)}usage{ansi.end}:\n make <recipe>\n
INHERIT_SHELL ?= INHERIT_SHELL ?=

View file

@ -4,7 +4,7 @@
import sys import sys
##- '$(utils_py)' -## ##- '$(ansi_py)' -##
def confirm(): def confirm():

View file

@ -5,16 +5,8 @@ import argparse
from collections import namedtuple from collections import namedtuple
import os import os
import re import re
import subprocess
import sys
from textwrap import wrap
##- '$(utils_py)' -##
###-
# this is just to trick the LSP during development
from utils import ansi, cfg
# -###
##- '$(ansi_py)' -##
MaxLens = namedtuple("MaxLens", "goal msg") MaxLens = namedtuple("MaxLens", "goal msg")
@ -36,13 +28,9 @@ def parseargs(argstring):
return parser.parse_args(argstring.split()) return parser.parse_args(argstring.split())
def divider(len):
return ansi.style(f" {cfg.div*len}", "div_style")
def gen_makefile(): def gen_makefile():
makefile = "" makefile = ""
for file in os.getenv("MAKEFILE_LIST", "").split(): for file in os.getenv("MAKEFILE_LIST").split():
with open(file, "r") as f: with open(file, "r") as f:
makefile += f.read() + "\n\n" makefile += f.read() + "\n\n"
return makefile return makefile
@ -76,61 +64,35 @@ def recipe_help_header(goal):
item[0].get("msgargs", ""), item[0].get("msgargs", ""),
) )
else: else:
return f" {ansi.style(goal,'goal')}" return f" {ansi.style(goal,'$(GOAL_STYLE)')}:"
def get_goal_deps(goal="task.mk"):
make = os.getenv("MAKE", "make")
cmd = [make, "-p", "-n", "-i"]
for file in os.getenv("TASK_MAKEFILE_LIST", "").split():
cmd.extend(["-f", file])
database = subprocess.check_output(cmd, universal_newlines=True)
dep_pattern = re.compile(r"^" + goal + ":(.*)?")
for line in database.splitlines():
match = dep_pattern.search(line)
if match and match.groups()[0]:
return wrap(
f"{ansi.style('deps','default')}: {ansi.style(match.groups()[0].strip(),'msg')}",
width=cfg.wrap,
initial_indent=" ",
subsequent_indent=" ",
)
def parse_goal(file, goal): def parse_goal(file, goal):
goals = goal_pattern.findall(file) goals = goal_pattern.findall(file)
matched_goal = [i for i in goals if goal in i.split()] matched_goal = [i for i in goals if goal in i.split()]
output = [] output = []
if matched_goal: if matched_goal:
output.append(recipe_help_header(matched_goal[0])) output.append(recipe_help_header(matched_goal[0]))
deps = get_goal_deps(matched_goal[0])
if deps:
output.extend(deps)
lines = file.splitlines() lines = file.splitlines()
loc = [n for n, l in enumerate(lines) if l.startswith(f"{matched_goal[0]}:")][0] loc = [n for n, l in enumerate(lines) if l.startswith(f"{matched_goal[0]}:")][0]
recipe = [] recipe = []
for line in lines[loc + 1 :]: for line in lines[loc + 1 :]:
if not line.startswith("\t"): if not line.startswith("\t"):
break break
recipe.append(f" {line.strip()}") recipe.append(line)
output.append(divider(max((len(l.strip()) for l in recipe)))) output.append(divider(max((len(l) for l in recipe)) + 5))
output.append("\n".join(recipe)) output.append("\n".join(recipe) + "\n")
else: else:
deps = get_goal_deps(goal) output.append(f"{ansi.b_red}ERROR{ansi.end} Failed to find goal: {goal}")
if deps:
output.append(recipe_help_header(goal))
output.extend(deps)
if not output:
output.append(f"{ansi.style('ERROR','b_red')}: Failed to find goal: {goal}")
return output return output
def fmt_goal(goal, msg, max_goal_len, argstr): 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_STYLE)"
msg_style = args.msg_style.strip() if args.msg_style else "msg" msg_style = args.msg_style.strip() if args.msg_style else "$(MSG_STYLE)"
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) "
@ -138,23 +100,27 @@ 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): def fmt_rawmsg(msg, argstr, maxlens):
args = parseargs(argstr) args = parseargs(argstr)
lines = [] lines = []
msg_style = args.msg_style.strip() if args.msg_style else "msg" msg_style = args.msg_style.strip() if args.msg_style else "$(MSG_STYLE)"
if not os.getenv("SHOW_HIDDEN") and args.hidden: if not os.getenv("SHOW_HIDDEN") and args.hidden:
return [] return []
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('$(HELP_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("$(HELP_SEP)") + sum(maxlens) + 2))
if args.whitespace: if args.whitespace:
lines.append("\n") lines.append("\n")
@ -162,7 +128,7 @@ def fmt_rawmsg(msg, argstr, maxlens):
def print_help(): def print_help():
lines = [cfg.usage] lines = [f"""$(USAGE)"""]
items = list(parse_help(gen_makefile())) items = list(parse_help(gen_makefile()))
maxlens = MaxLens( maxlens = MaxLens(
@ -177,27 +143,25 @@ def print_help():
) )
if "rawmsg" in item: if "rawmsg" in item:
lines.extend(fmt_rawmsg(item["rawmsg"], item.get("rawargs", ""), maxlens)) lines.extend(fmt_rawmsg(item["rawmsg"], item.get("rawargs", ""), maxlens))
lines.append(cfg.epilog) lines.append(f"""$(EPILOG)""")
print("\n".join(lines)) print("\n".join(lines))
def print_arg_help(help_args): def print_arg_help(help_args):
print(f"{ansi.style('task.mk recipe help','header')}\n")
for arg in help_args.split(): 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))) print("\n".join(parse_goal(gen_makefile(), arg)))
print()
def main(): def main():
help_args = os.getenv("HELP_ARGS") help_args = os.getenv("HELP_ARGS")
if help_args: if help_args:
print_arg_help(help_args) print_arg_help(help_args)
sys.exit(1) print(a.faint)
else: else:
print_help() print_help()
if __name__ == "__main__": if __name__ == "__main__":
main() main()
#% endblock %# #% endblock %#

View file

@ -1,7 +1,7 @@
#% extends "py-script.mk" %# #% extends "py-script.mk" %#
#% block name %#info#% endblock %# #% block name %#info#% endblock %#
#% block script %# #% block script %#
##- '$(utils_py)' -## ##- '$(ansi_py)' -##
print(f"""$(2)""") print(f"""$(2)""")
#% endblock %# #% endblock %#

View file

@ -1,7 +1,7 @@
#% extends "py-script.mk" %# #% extends "py-script.mk" %#
#% block name %#print_ansi#% endblock %# #% block name %#print_ansi#% endblock %#
#% block script %# #% block script %#
##- '$(utils_py)' -## ##- '$(ansi_py)' -##
sep = f"$(HELP_SEP)" sep = f"$(HELP_SEP)"
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)): for code in sorted(codes_names.keys(), key=lambda item: (len(item), item)):

View file

@ -3,15 +3,15 @@
#% block script %# #% block script %#
import os import os
##- '$(utils_py)' -## ##- '$(ansi_py)' -##
vars = "$2".split() vars = "$2".split()
length = max((len(v) for v in vars)) length = max((len(v) for v in vars))
print(f"{ansi.header}vars{ansi.end}:\n") print(f"{ansi.$(HEADER_STYLE)}vars:{ansi.end}\n")
for v in vars: for v in vars:
print(f" {ansi.params}{v:<{length}}{ansi.end} = {os.getenv(v)}") print(f" {ansi.b_magenta}{v:<{length}}{ansi.end} = {os.getenv(v)}")
print() print()
#% endblock %# #% endblock %#

190
task.mk
View file

@ -1,7 +1,7 @@
# }> [github.com/daylinmorgan/task.mk] <{ # # }> [github.com/daylinmorgan/task.mk] <{ #
# Copyright (c) 2022 Daylin Morgan # Copyright (c) 2022 Daylin Morgan
# MIT License # MIT License
# version: v22.9.19-20-g32110dd # version: v22.9.19-5-g5f593e3-dev
# #
# 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.
@ -12,13 +12,12 @@ ACCENT_STYLE ?= b_yellow
PARAMS_STYLE ?= $(ACCENT_STYLE) PARAMS_STYLE ?= $(ACCENT_STYLE)
GOAL_STYLE ?= $(ACCENT_STYLE) GOAL_STYLE ?= $(ACCENT_STYLE)
MSG_STYLE ?= faint MSG_STYLE ?= faint
DIVIDER ?=
DIVIDER_STYLE ?= default DIVIDER_STYLE ?= default
DIVIDER ?=
HELP_SEP ?= HELP_SEP ?=
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_STYLE)}usage{ansi.end}:\n make <recipe>\n
INHERIT_SHELL ?= INHERIT_SHELL ?=
# ---- [builtin recipes] ---- # # ---- [builtin recipes] ---- #
ifeq (help,$(firstword $(MAKECMDGOALS))) ifeq (help,$(firstword $(MAKECMDGOALS)))
@ -26,8 +25,9 @@ ifeq (help,$(firstword $(MAKECMDGOALS)))
export HELP_ARGS export HELP_ARGS
endif endif
## h, help | show this help ## h, help | show this help
h help: .PHONY: help h
$(call py,help_py) || { echo "exiting early!"; exit 1; } help h:
$(call py,help_py)
.PHONY: _help .PHONY: _help
_help: export SHOW_HIDDEN=true _help: export SHOW_HIDDEN=true
_help: help _help: help
@ -51,10 +51,9 @@ tconfirm = $(call py,confirm_py,$(1))
_update-task.mk: _update-task.mk:
$(call tprint,{a.b_cyan}Updating task.mk{a.end}) $(call tprint,{a.b_cyan}Updating task.mk{a.end})
curl https://raw.githubusercontent.com/daylinmorgan/task.mk/main/task.mk -o .task.mk curl https://raw.githubusercontent.com/daylinmorgan/task.mk/main/task.mk -o .task.mk
TASK_MAKEFILE_LIST := $(filter-out $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)),$(MAKEFILE_LIST)) export MAKEFILE_LIST
export MAKEFILE_LIST MAKE TASK_MAKEFILE_LIST
ifndef INHERIT_SHELL ifndef INHERIT_SHELL
SHELL := $(shell which bash) SHELL := /bin/bash
endif endif
# ---- [python/bash script runner] ---- # # ---- [python/bash script runner] ---- #
define _newline define _newline
@ -85,10 +84,8 @@ import argparse
from collections import namedtuple from collections import namedtuple
import os import os
import re import re
import subprocess $(ansi_py)
import sys $(quit_make_py)
from textwrap import wrap
$(utils_py)
MaxLens = namedtuple("MaxLens", "goal msg") MaxLens = namedtuple("MaxLens", "goal msg")
pattern = re.compile( pattern = re.compile(
r"^## (?P<goal>.*?) \| (?P<msg>.*?)(?:\s?\| args: (?P<msgargs>.*?))?$$|^### (?P<rawmsg>.*?)?(?:\s?\| args: (?P<rawargs>.*?))?$$" r"^## (?P<goal>.*?) \| (?P<msg>.*?)(?:\s?\| args: (?P<msgargs>.*?))?$$|^### (?P<rawmsg>.*?)?(?:\s?\| args: (?P<rawargs>.*?))?$$"
@ -103,11 +100,9 @@ def parseargs(argstring):
parser.add_argument("-gs", "--goal-style", type=str) parser.add_argument("-gs", "--goal-style", type=str)
parser.add_argument("--hidden", action="store_true") parser.add_argument("--hidden", action="store_true")
return parser.parse_args(argstring.split()) return parser.parse_args(argstring.split())
def divider(len):
return ansi.style(f" {cfg.div*len}", "div_style")
def gen_makefile(): def gen_makefile():
makefile = "" makefile = ""
for file in os.getenv("MAKEFILE_LIST", "").split(): for file in os.getenv("MAKEFILE_LIST").split():
with open(file, "r") as f: with open(file, "r") as f:
makefile += f.read() + "\n\n" makefile += f.read() + "\n\n"
return makefile return makefile
@ -137,80 +132,58 @@ def recipe_help_header(goal):
item[0].get("msgargs", ""), item[0].get("msgargs", ""),
) )
else: else:
return f" {ansi.style(goal,'goal')}" return f" {ansi.style(goal,'$(GOAL_STYLE)')}:"
def get_goal_deps(goal="task.mk"):
make = os.getenv("MAKE", "make")
cmd = [make, "-p", "-n", "-i"]
for file in os.getenv("TASK_MAKEFILE_LIST", "").split():
cmd.extend(["-f", file])
database = subprocess.check_output(cmd, universal_newlines=True)
dep_pattern = re.compile(r"^" + goal + ":(.*)?")
for line in database.splitlines():
match = dep_pattern.search(line)
if match and match.groups()[0]:
return wrap(
f"{ansi.style('deps','default')}: {ansi.style(match.groups()[0].strip(),'msg')}",
width=cfg.wrap,
initial_indent=" ",
subsequent_indent=" ",
)
def parse_goal(file, goal): def parse_goal(file, goal):
goals = goal_pattern.findall(file) goals = goal_pattern.findall(file)
matched_goal = [i for i in goals if goal in i.split()] matched_goal = [i for i in goals if goal in i.split()]
output = [] output = []
if matched_goal: if matched_goal:
output.append(recipe_help_header(matched_goal[0])) output.append(recipe_help_header(matched_goal[0]))
deps = get_goal_deps(matched_goal[0])
if deps:
output.extend(deps)
lines = file.splitlines() lines = file.splitlines()
loc = [n for n, l in enumerate(lines) if l.startswith(f"{matched_goal[0]}:")][0] loc = [n for n, l in enumerate(lines) if l.startswith(f"{matched_goal[0]}:")][0]
recipe = [] recipe = []
for line in lines[loc + 1 :]: for line in lines[loc + 1 :]:
if not line.startswith("\t"): if not line.startswith("\t"):
break break
recipe.append(f" {line.strip()}") recipe.append(line)
output.append(divider(max((len(l.strip()) for l in recipe)))) output.append(divider(max((len(l) for l in recipe)) + 5))
output.append("\n".join(recipe)) output.append("\n".join(recipe) + "\n")
else: else:
deps = get_goal_deps(goal) output.append(f"{ansi.b_red}ERROR{ansi.end} Failed to find goal: {goal}")
if deps:
output.append(recipe_help_header(goal))
output.extend(deps)
if not output:
output.append(f"{ansi.style('ERROR','b_red')}: Failed to find goal: {goal}")
return output return output
def fmt_goal(goal, msg, max_goal_len, argstr): 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_STYLE)"
msg_style = args.msg_style.strip() if args.msg_style else "msg" msg_style = args.msg_style.strip() if args.msg_style else "$(MSG_STYLE)"
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) "
+ ansi.style(msg, msg_style) + ansi.style(msg, msg_style)
) )
def divider(len):
return ansi.style(f" {'$(DIVIDER)'*len}", "$(DIVIDER_STYLE)")
def fmt_rawmsg(msg, argstr, maxlens): def fmt_rawmsg(msg, argstr, maxlens):
args = parseargs(argstr) args = parseargs(argstr)
lines = [] lines = []
msg_style = args.msg_style.strip() if args.msg_style else "msg" msg_style = args.msg_style.strip() if args.msg_style else "$(MSG_STYLE)"
if not os.getenv("SHOW_HIDDEN") and args.hidden: if not os.getenv("SHOW_HIDDEN") and args.hidden:
return [] return []
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('$(HELP_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("$(HELP_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 = [f"""$(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)) for x in ["goal", "msg"]) *(max((len(item[x]) for item in items if x in item)) for x in ["goal", "msg"])
@ -224,73 +197,25 @@ def print_help():
) )
if "rawmsg" in item: if "rawmsg" in item:
lines.extend(fmt_rawmsg(item["rawmsg"], item.get("rawargs", ""), maxlens)) lines.extend(fmt_rawmsg(item["rawmsg"], item.get("rawargs", ""), maxlens))
lines.append(cfg.epilog) lines.append(f"""$(EPILOG)""")
print("\n".join(lines)) print("\n".join(lines))
def print_arg_help(help_args): def print_arg_help(help_args):
print(f"{ansi.style('task.mk recipe help','header')}\n")
for arg in help_args.split(): 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))) print("\n".join(parse_goal(gen_makefile(), arg)))
print()
def main(): def main():
help_args = os.getenv("HELP_ARGS") help_args = os.getenv("HELP_ARGS")
if help_args: if help_args:
quit_make()
print_arg_help(help_args) print_arg_help(help_args)
sys.exit(1)
else: else:
print_help() print_help()
if __name__ == "__main__": if __name__ == "__main__":
main() main()
endef endef
define info_py define ansi_py
$(utils_py)
print(f"""$(2)""")
endef
define print_ansi_py
$(utils_py)
sep = f"$(HELP_SEP)"
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(f"{codes_names[code]:>20} {sep} {code+'*****'+ansi.end} {sep} {repr(code)}")
endef
define vars_py
import os
$(utils_py)
vars = "$2".split()
length = max((len(v) for v in vars))
print(f"{ansi.header}vars{ansi.end}:\n")
for v in vars:
print(f" {ansi.params}{v:<{length}}{ansi.end} = {os.getenv(v)}")
print()
endef
define confirm_py
import sys
$(utils_py)
def confirm():
"""
Ask user to enter Y or N (case-insensitive).
:return: True if the answer is Y.
:rtype: bool
"""
answer = ""
while answer not in ["y", "n"]:
answer = input(f"""$(2) {a.b_red}[Y/n]{a.end} """).lower()
return answer == "y"
if confirm():
sys.exit()
else:
sys.exit(1)
endef
define utils_py
import os import os
import sys import sys
from dataclasses import dataclass
@dataclass
class Config:
div: str
sep: str
epilog: str
usage: str
wrap: int
color2byte = dict( color2byte = dict(
black=0, black=0,
red=1, red=1,
@ -322,7 +247,6 @@ class Ansi:
) )
for name, byte in state2byte.items(): for name, byte in state2byte.items():
self.setcode(name, f"\033[{byte}m") self.setcode(name, f"\033[{byte}m")
self.add_cfg()
def setcode(self, name, escape_code): def setcode(self, name, escape_code):
"""create attr for style and escape code""" """create attr for style and escape code"""
if not sys.stdout.isatty() or os.getenv("NO_COLOR", False): if not sys.stdout.isatty() or os.getenv("NO_COLOR", False):
@ -353,17 +277,6 @@ class Ansi:
print("Expected one or three values for bg as a list") print("Expected one or three values for bg as a list")
sys.exit(1) sys.exit(1)
return code + end return code + end
def add_cfg(self):
cfg_styles = {
"header": "$(HEADER_STYLE)",
"accent": "$(ACCENT_STYLE)",
"params": "$(PARAMS_STYLE)",
"goal": "$(GOAL_STYLE)",
"msg": "$(MSG_STYLE)",
"div_style": "$(DIVIDER_STYLE)",
}
for name, style in cfg_styles.items():
self.setcode(name, getattr(self, style))
def style(self, text, style): def style(self, text, style):
if style not in self.__dict__: if style not in self.__dict__:
print(f"unknown style: {style}") print(f"unknown style: {style}")
@ -371,6 +284,49 @@ class Ansi:
else: else:
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( endef
"$(DIVIDER)", "$(HELP_SEP)", f"""$(EPILOG)""", f"""$(USAGE)""",int('$(WRAP)')) define info_py
$(ansi_py)
print(f"""$(2)""")
endef
define print_ansi_py
$(ansi_py)
sep = f"$(HELP_SEP)"
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(f"{codes_names[code]:>20} {sep} {code+'*****'+ansi.end} {sep} {repr(code)}")
endef
define vars_py
import os
$(ansi_py)
vars = "$2".split()
length = max((len(v) for v in vars))
print(f"{ansi.$(HEADER_STYLE)}vars:{ansi.end}\n")
for v in vars:
print(f" {ansi.b_magenta}{v:<{length}}{ansi.end} = {os.getenv(v)}")
print()
endef
define confirm_py
import sys
$(ansi_py)
$(quit_make_py)
def confirm():
"""
Ask user to enter Y or N (case-insensitive).
:return: True if the answer is Y.
:rtype: bool
"""
answer = ""
while answer not in ["y", "n"]:
answer = input(f"""$(2) {a.b_red}[Y/n]{a.end} """).lower()
return answer == "y"
if confirm():
sys.exit()
else:
quit_make()
endef
define quit_make_py
import os, signal
def quit_make():
os.kill(os.getppid(), signal.SIGQUIT)
endef endef