Compare commits

...

12 commits

21 changed files with 592 additions and 381 deletions

View file

@ -1,20 +1,22 @@
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 .PHONY: bootstrap env hooks
bootstrap: bootstrap: env hooks
$(call msg,Bootstrap Environment) env:
$(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:
@ -67,7 +69,7 @@ task.mk: $(TEMPLATES) generate.py
./generate.py $(VERSION) > task.mk ./generate.py $(VERSION) > task.mk
define USAGE define USAGE
{a.$(HEADER_STYLE)}usage:{a.end}\n make <recipe>\n {a.style('usage','header')}:\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 EXAMPLES := check embedded recipe-help
CASTS := $(addsuffix /demo.cast, $(EXAMPLES)) CASTS := $(addsuffix /demo.cast, $(EXAMPLES))
all: $(CASTS) all: $(CASTS)

View file

@ -1,129 +1,84 @@
{"version": 2, "width": 48, "height": 12, "timestamp": 1663712361, "env": {"SHELL": "/usr/bin/zsh", "TERM": "xterm-256color"}} {"version": 2, "width": 48, "height": 12, "timestamp": 1664060067, "env": {"SHELL": "/usr/bin/zsh", "TERM": "xterm-256color"}}
[0.008114, "o", "\u001b[H\u001b[2J\u001b[3J"] [0.008028, "o", "\u001b[H\u001b[2J\u001b[3J"]
[0.00852, "o", "bash >> "] [0.008412, "o", "bash >> "]
[0.008755, "o", "m"] [0.008649, "o", "ma"]
[0.189398, "o", "a"] [0.189116, "o", "ke"]
[0.279744, "o", "k"] [0.279092, "o", " -"]
[0.369775, "o", "e"] [0.369181, "o", "f "]
[0.459919, "o", " -"] [0.459296, "o", "ch"]
[0.549908, "o", "f"] [0.549434, "o", "ec"]
[0.639982, "o", " "] [0.639704, "o", "k/"]
[0.730339, "o", "c"] [0.729786, "o", "ch"]
[0.820338, "o", "h"] [0.81994, "o", "ec"]
[0.910414, "o", "ec"] [0.910178, "o", "k."]
[1.090651, "o", "k"] [1.090415, "o", "mk"]
[1.18077, "o", "/"] [1.180666, "o", " h"]
[1.271098, "o", "c"] [1.270767, "o", "el"]
[1.361098, "o", "h"] [1.360807, "o", "p\r\n"]
[1.451216, "o", "ec"] [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.541276, "o", "k"] [4.398843, "o", "\u001b[H\u001b[2J\u001b[3J"]
[1.631388, "o", "."] [4.398948, "o", "bash >> "]
[1.721603, "o", "m"] [4.399576, "o", "ma"]
[1.811718, "o", "k"] [4.580421, "o", "ke"]
[1.992079, "o", " h"] [4.670533, "o", " -"]
[2.082138, "o", "e"] [4.760674, "o", "f "]
[2.172379, "o", "l"] [4.850908, "o", "ch"]
[2.262403, "o", "p"] [4.940859, "o", "ec"]
[2.352473, "o", "\r\n"] [5.031095, "o", "k/"]
[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.121228, "o", "ch"]
[5.387545, "o", "\u001b[H\u001b[2J\u001b[3J"] [5.211271, "o", "ec"]
[5.387892, "o", "bash >> "] [5.301383, "o", "k."]
[5.390047, "o", "m"] [5.481587, "o", "mk"]
[5.570645, "o", "a"] [5.571714, "o", " c"]
[5.660579, "o", "k"] [5.662002, "o", "he"]
[5.750852, "o", "e"] [5.752104, "o", "ck"]
[5.841071, "o", " -"] [5.842305, "o", "\r\n"]
[5.931225, "o", "f"] [6.864798, "o", "Would you like to proceed? \u001b[1;31m[Y/n]\u001b[0m "]
[6.021286, "o", " "] [7.488194, "o", "y"]
[6.111373, "o", "c"] [7.830894, "o", "\r\n"]
[6.201586, "o", "h"] [7.836313, "o", "you said yes!\r\n"]
[6.291778, "o", "ec"] [9.840217, "o", "# "]
[6.472054, "o", "k"] [10.020704, "o", "Le"]
[6.562124, "o", "/"] [10.110897, "o", "t'"]
[6.652262, "o", "c"] [10.201077, "o", "s "]
[6.74248, "o", "h"] [10.291171, "o", "tr"]
[6.832675, "o", "ec"] [10.381382, "o", "y "]
[6.922668, "o", "k"] [10.471532, "o", "ag"]
[7.012809, "o", "."] [10.5617, "o", "ai"]
[7.102973, "o", "m"] [10.651984, "o", "n "]
[7.193151, "o", "k"] [10.742177, "o", "bu"]
[7.373458, "o", " c"] [10.922348, "o", "t "]
[7.463783, "o", "h"] [11.012569, "o", "in"]
[7.553761, "o", "e"] [11.102605, "o", "st"]
[7.643931, "o", "c"] [11.192785, "o", "ea"]
[7.734197, "o", "k"] [11.282913, "o", "d "]
[7.824356, "o", "\r\n"] [11.373109, "o", "sa"]
[8.857505, "o", "Would you like to proceed? \u001b[1;31m[Y/n]\u001b[0m "] [11.463146, "o", "y "]
[9.54018, "o", "y"] [11.553266, "o", "no"]
[9.595198, "o", "\r\n"] [11.6436, "o", " t"]
[9.598684, "o", "you said yes!\r\n"] [11.823731, "o", "hi"]
[11.602689, "o", "#"] [11.913841, "o", "s "]
[11.783071, "o", " "] [12.003886, "o", "ti"]
[11.873235, "o", "L"] [12.094101, "o", "me"]
[11.963465, "o", "e"] [12.184197, "o", "\r\n"]
[12.053444, "o", "t'"] [13.185929, "o", "\u001b[H\u001b[2J\u001b[3J"]
[12.143548, "o", "s"] [13.185977, "o", "bash >> "]
[12.233881, "o", " "] [13.186676, "o", "ma"]
[12.323915, "o", "t"] [13.367108, "o", "ke"]
[12.41414, "o", "r"] [13.457234, "o", " -"]
[12.50428, "o", "y "] [13.547276, "o", "f "]
[12.684471, "o", "a"] [13.637696, "o", "ch"]
[12.774773, "o", "g"] [13.727919, "o", "ec"]
[12.864864, "o", "a"] [13.817921, "o", "k/"]
[12.954989, "o", "i"] [13.908217, "o", "ch"]
[13.045011, "o", "n "] [13.998375, "o", "ec"]
[13.135204, "o", "b"] [14.08855, "o", "k."]
[13.225451, "o", "u"] [14.268802, "o", "mk"]
[13.315649, "o", "t"] [14.359106, "o", " c"]
[13.405687, "o", " "] [14.449085, "o", "he"]
[13.585943, "o", "in"] [14.539194, "o", "ck"]
[13.676051, "o", "s"] [14.629387, "o", "\r\n"]
[13.766346, "o", "t"] [15.652945, "o", "Would you like to proceed? \u001b[1;31m[Y/n]\u001b[0m "]
[13.856324, "o", "e"] [16.544875, "o", "n"]
[13.946542, "o", "a"] [17.098374, "o", "\r\n"]
[14.036697, "o", "d "] [17.103306, "o", "make[1]: *** [check/check.mk:4: check] Error 1\r\n"]
[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,126 +1,83 @@
{"version": 2, "width": 60, "height": 20, "timestamp": 1663712193, "env": {"SHELL": "/usr/bin/zsh", "TERM": "xterm-256color"}} {"version": 2, "width": 60, "height": 20, "timestamp": 1664060088, "env": {"SHELL": "/usr/bin/zsh", "TERM": "xterm-256color"}}
[0.008386, "o", "\u001b[H\u001b[2J\u001b[3J"] [0.008131, "o", "\u001b[H\u001b[2J\u001b[3J"]
[0.008986, "o", "bash >> "] [0.008522, "o", "bash >> "]
[0.009151, "o", "m"] [0.008683, "o", "ma"]
[0.189701, "o", "a"] [0.189166, "o", "ke"]
[0.280162, "o", "k"] [0.279481, "o", " -"]
[0.37038, "o", "e"] [0.369776, "o", "f "]
[0.460683, "o", " -"] [0.460021, "o", "em"]
[0.551001, "o", "f"] [0.550229, "o", "be"]
[0.641267, "o", " "] [0.640359, "o", "dd"]
[0.731609, "o", "e"] [0.730591, "o", "ed"]
[0.821777, "o", "m"] [0.820572, "o", "/e"]
[0.912103, "o", "be"] [0.910745, "o", "mb"]
[1.092658, "o", "d"] [1.091087, "o", "ed"]
[1.182705, "o", "d"] [1.181312, "o", "de"]
[1.273152, "o", "e"] [1.271441, "o", "d."]
[1.363457, "o", "d"] [1.361501, "o", "mk"]
[1.453758, "o", "/e"] [1.451615, "o", " h"]
[1.544059, "o", "m"] [1.541783, "o", "el"]
[1.634282, "o", "b"] [1.63204, "o", "p\r\n"]
[1.724452, "o", "e"] [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.814601, "o", "d"] [4.671801, "o", "\u001b[H\u001b[2J\u001b[3Jbash >> "]
[1.995155, "o", "de"] [4.67392, "o", "ma"]
[2.085437, "o", "d"] [4.854455, "o", "ke"]
[2.175546, "o", "."] [4.944728, "o", " -"]
[2.265856, "o", "m"] [5.034921, "o", "f "]
[2.356192, "o", "k"] [5.124982, "o", "em"]
[2.446468, "o", " h"] [5.215103, "o", "be"]
[2.536781, "o", "e"] [5.305188, "o", "dd"]
[2.627101, "o", "l"] [5.395353, "o", "ed"]
[2.717423, "o", "p"] [5.485457, "o", "/e"]
[2.897934, "o", "\r\n"] [5.576246, "o", "mb"]
[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.75589, "o", "ed"]
[5.936874, "o", "\u001b[H\u001b[2J\u001b[3J"] [5.846014, "o", "de"]
[5.937167, "o", "bash >> "] [5.93612, "o", "d."]
[5.939502, "o", "m"] [6.026298, "o", "mk"]
[6.120169, "o", "a"] [6.11644, "o", " l"]
[6.210505, "o", "k"] [6.206464, "o", "is"]
[6.300671, "o", "e"] [6.296631, "o", "t-"]
[6.390788, "o", " -"] [6.386781, "o", "em"]
[6.480981, "o", "f"] [6.477052, "o", "be"]
[6.571285, "o", " "] [6.657201, "o", "dd"]
[6.661457, "o", "e"] [6.747412, "o", "ed"]
[6.751758, "o", "m"] [6.837566, "o", "\r\n"]
[6.842048, "o", "be"] [7.867599, "o", "files in embedded\r\n['embedded.mk', 'demo.cast', 'record.sh', 'index.md']\r\n"]
[7.022576, "o", "d"] [9.872436, "o", "\u001b[H\u001b[2J\u001b[3J"]
[7.112852, "o", "d"] [9.872754, "o", "bash >> "]
[7.203064, "o", "e"] [9.874651, "o", "ma"]
[7.293421, "o", "d"] [10.055031, "o", "ke"]
[7.383397, "o", "/e"] [10.145171, "o", " -"]
[7.473873, "o", "m"] [10.235256, "o", "f "]
[7.564152, "o", "b"] [10.325347, "o", "em"]
[7.65431, "o", "e"] [10.415525, "o", "be"]
[7.744607, "o", "d"] [10.505721, "o", "dd"]
[7.925168, "o", "de"] [10.595814, "o", "ed"]
[8.015472, "o", "d"] [10.685931, "o", "/e"]
[8.105767, "o", "."] [10.776068, "o", "mb"]
[8.195936, "o", "m"] [10.956362, "o", "ed"]
[8.286184, "o", "k"] [11.046547, "o", "de"]
[8.376537, "o", " l"] [11.136692, "o", "d."]
[8.466624, "o", "i"] [11.226851, "o", "mk"]
[8.557058, "o", "s"] [11.316894, "o", " e"]
[8.64739, "o", "t"] [11.406996, "o", "mb"]
[8.827942, "o", "-"] [11.497205, "o", "ed"]
[8.91803, "o", "em"] [11.58725, "o", "de"]
[9.008446, "o", "b"] [11.677471, "o", "d-"]
[9.098742, "o", "e"] [11.85761, "o", "ba"]
[9.189059, "o", "d"] [11.947917, "o", "sh"]
[9.279328, "o", "d"] [12.038071, "o", "\r\n"]
[9.36965, "o", "ed"] [13.045213, "o", "Is the process running bash? We can check with ps\r\n"]
[9.459574, "o", "\r\n"] [13.051057, "o", "bash\r\n"]
[10.492826, "o", "files in embedded\r\n"] [13.051385, "o", "What text to figlet? \r\n"]
[10.492943, "o", "['embedded.mk', 'demo.cast', 'record.sh', 'index.md']\r\n"] [14.434504, "o", "t"]
[12.498865, "o", "\u001b[H\u001b[2J\u001b[3J"] [14.503239, "o", "a"]
[12.498984, "o", "bash >> "] [14.605384, "o", "s"]
[12.501382, "o", "m"] [14.745978, "o", "k"]
[12.681906, "o", "a"] [14.946139, "o", "."]
[12.7722, "o", "k"] [15.080651, "o", "m"]
[12.862501, "o", "e"] [15.228545, "o", "k"]
[12.952783, "o", " -"] [15.617669, "o", "\r\n"]
[13.043086, "o", "f"] [15.618495, "o", " _ _ _ \r\n| |_ __ _ ___| | __ _ __ ___ | | __\r\n| __/ _` / __| |/ / | '_ ` _ \\| |/ /\r\n| || (_| \\__ \\ < _| | | | | | < \r\n \\__\\__,_|___/_|\\_(_)_| |_| |_|_|\\_\\\r\n \r\n"]
[13.13339, "o", " "] [15.618636, "o", "the argument below as given in the makefile itself\r\n"]
[13.223546, "o", "e"] [15.618695, "o", "it's expanded before the script is passed to bash\r\nbash multiline is probably working\r\n"]
[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,4 +5,5 @@ 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 12 printf '%s\n' "$1" | pv -qL 20
sleep 1 sleep 1
} }

View file

@ -1,10 +1,14 @@
# Examples # Examples
[`Check`](./check) [Confirm](./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`](./embedded) [Embedded Scripts](./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

@ -0,0 +1,138 @@
{"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

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

View file

@ -0,0 +1,24 @@
## 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

@ -0,0 +1,11 @@
#!/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,7 +4,14 @@ import sys
from pathlib import Path from pathlib import Path
import jinja2 import jinja2
py_script_names = ["help", "ansi", "info", "print-ansi", "vars", "confirm"] py_script_names = [
"help",
"info",
"print-ansi",
"vars",
"confirm",
"utils",
]
def get_jinja_env(): def get_jinja_env():

View file

@ -4,17 +4,8 @@ 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) $(call py,help_py) || { echo "exiting early!"; exit 1; }
endif
.PHONY: _help .PHONY: _help
_help: export SHOW_HIDDEN=true _help: export SHOW_HIDDEN=true
_help: help _help: help
@ -38,7 +29,8 @@ 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
export MAKEFILE_LIST TASK_MAKEFILE_LIST := $(filter-out $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)),$(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,10 +4,11 @@ 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_STYLE ?= default
DIVIDER ?= DIVIDER ?=
DIVIDER_STYLE ?= default
HELP_SEP ?= HELP_SEP ?=
WRAP ?= 100
# python f-string literals # python f-string literals
EPILOG ?= EPILOG ?=
USAGE ?={ansi.$(HEADER_STYLE)}usage{ansi.end}:\n make <recipe>\n USAGE ?={ansi.header}usage{ansi.end}:\n make <recipe>\n
INHERIT_SHELL ?= INHERIT_SHELL ?=

View file

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

View file

@ -5,8 +5,16 @@ 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")
@ -28,9 +36,13 @@ 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
@ -64,35 +76,61 @@ def recipe_help_header(goal):
item[0].get("msgargs", ""), item[0].get("msgargs", ""),
) )
else: else:
return f" {ansi.style(goal,'$(GOAL_STYLE)')}:" return f" {ansi.style(goal,'goal')}"
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(line) recipe.append(f" {line.strip()}")
output.append(divider(max((len(l) for l in recipe)) + 5)) output.append(divider(max((len(l.strip()) for l in recipe))))
output.append("\n".join(recipe) + "\n") output.append("\n".join(recipe))
else: else:
output.append(f"{ansi.b_red}ERROR{ansi.end} Failed to find goal: {goal}") deps = get_goal_deps(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_STYLE)" goal_style = args.goal_style.strip() if args.goal_style else "goal"
msg_style = args.msg_style.strip() if args.msg_style else "$(MSG_STYLE)" msg_style = args.msg_style.strip() if args.msg_style else "msg"
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) "
@ -100,27 +138,23 @@ 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_STYLE)" msg_style = args.msg_style.strip() if args.msg_style else "msg"
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('$(HELP_SEP)')+4)}{ansi.style(msg,msg_style)}" f"{' '*(maxlens.goal+len(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("$(HELP_SEP)") + sum(maxlens) + 2)) lines.append(divider(len(cfg.sep) + sum(maxlens) + 2))
if args.whitespace: if args.whitespace:
lines.append("\n") lines.append("\n")
@ -128,7 +162,7 @@ def fmt_rawmsg(msg, argstr, maxlens):
def print_help(): def print_help():
lines = [f"""$(USAGE)"""] lines = [cfg.usage]
items = list(parse_help(gen_makefile())) items = list(parse_help(gen_makefile()))
maxlens = MaxLens( maxlens = MaxLens(
@ -143,25 +177,27 @@ 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(f"""$(EPILOG)""") lines.append(cfg.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)
print(a.faint) sys.exit(1)
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 %#
##- '$(ansi_py)' -## ##- '$(utils_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 %#
##- '$(ansi_py)' -## ##- '$(utils_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

@ -1,8 +1,19 @@
#% extends "py-script.mk" %# #% extends "py-script.mk" %#
#% block name %#ansi#% endblock %# #% block name %#utils#% 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,
@ -40,6 +51,7 @@ 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"""
@ -77,6 +89,18 @@ 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}")
@ -86,4 +110,7 @@ class Ansi:
a = ansi = Ansi() a = ansi = Ansi()
cfg = Config(
"$(DIVIDER)", "$(HELP_SEP)", f"""$(EPILOG)""", f"""$(USAGE)""",int('$(WRAP)'))
#% endblock %# #% endblock %#

View file

@ -3,15 +3,15 @@
#% block script %# #% block script %#
import os import os
##- '$(ansi_py)' -## ##- '$(utils_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_STYLE)}vars:{ansi.end}\n") print(f"{ansi.header}vars{ansi.end}:\n")
for v in vars: for v in vars:
print(f" {ansi.b_magenta}{v:<{length}}{ansi.end} = {os.getenv(v)}") print(f" {ansi.params}{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-5-g5f593e3-dev # version: v22.9.19-20-g32110dd
# #
# 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,12 +12,13 @@ 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_STYLE ?= default
DIVIDER ?= DIVIDER ?=
DIVIDER_STYLE ?= default
HELP_SEP ?= HELP_SEP ?=
WRAP ?= 100
# python f-string literals # python f-string literals
EPILOG ?= EPILOG ?=
USAGE ?={ansi.$(HEADER_STYLE)}usage{ansi.end}:\n make <recipe>\n USAGE ?={ansi.header}usage{ansi.end}:\n make <recipe>\n
INHERIT_SHELL ?= INHERIT_SHELL ?=
# ---- [builtin recipes] ---- # # ---- [builtin recipes] ---- #
ifeq (help,$(firstword $(MAKECMDGOALS))) ifeq (help,$(firstword $(MAKECMDGOALS)))
@ -25,9 +26,8 @@ ifeq (help,$(firstword $(MAKECMDGOALS)))
export HELP_ARGS export HELP_ARGS
endif endif
## h, help | show this help ## h, help | show this help
.PHONY: help h h help:
help h: $(call py,help_py) || { echo "exiting early!"; exit 1; }
$(call py,help_py)
.PHONY: _help .PHONY: _help
_help: export SHOW_HIDDEN=true _help: export SHOW_HIDDEN=true
_help: help _help: help
@ -51,9 +51,10 @@ 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
export MAKEFILE_LIST TASK_MAKEFILE_LIST := $(filter-out $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)),$(MAKEFILE_LIST))
export MAKEFILE_LIST MAKE TASK_MAKEFILE_LIST
ifndef INHERIT_SHELL ifndef INHERIT_SHELL
SHELL := /bin/bash SHELL := $(shell which bash)
endif endif
# ---- [python/bash script runner] ---- # # ---- [python/bash script runner] ---- #
define _newline define _newline
@ -84,8 +85,10 @@ import argparse
from collections import namedtuple from collections import namedtuple
import os import os
import re import re
$(ansi_py) import subprocess
$(quit_make_py) import sys
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>.*?))?$$"
@ -100,9 +103,11 @@ 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
@ -132,58 +137,80 @@ def recipe_help_header(goal):
item[0].get("msgargs", ""), item[0].get("msgargs", ""),
) )
else: else:
return f" {ansi.style(goal,'$(GOAL_STYLE)')}:" return f" {ansi.style(goal,'goal')}"
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(line) recipe.append(f" {line.strip()}")
output.append(divider(max((len(l) for l in recipe)) + 5)) output.append(divider(max((len(l.strip()) for l in recipe))))
output.append("\n".join(recipe) + "\n") output.append("\n".join(recipe))
else: else:
output.append(f"{ansi.b_red}ERROR{ansi.end} Failed to find goal: {goal}") deps = get_goal_deps(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_STYLE)" goal_style = args.goal_style.strip() if args.goal_style else "goal"
msg_style = args.msg_style.strip() if args.msg_style else "$(MSG_STYLE)" msg_style = args.msg_style.strip() if args.msg_style else "msg"
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_STYLE)" msg_style = args.msg_style.strip() if args.msg_style else "msg"
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('$(HELP_SEP)')+4)}{ansi.style(msg,msg_style)}" f"{' '*(maxlens.goal+len(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("$(HELP_SEP)") + sum(maxlens) + 2)) lines.append(divider(len(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 = [f"""$(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)) for x in ["goal", "msg"]) *(max((len(item[x]) for item in items if x in item)) for x in ["goal", "msg"])
@ -197,25 +224,73 @@ 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(f"""$(EPILOG)""") lines.append(cfg.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 ansi_py define info_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,
@ -247,6 +322,7 @@ 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):
@ -277,6 +353,17 @@ 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}")
@ -284,49 +371,6 @@ 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()
endef cfg = Config(
define info_py "$(DIVIDER)", "$(HELP_SEP)", f"""$(EPILOG)""", f"""$(USAGE)""",int('$(WRAP)'))
$(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