2 # Author: Julien Rische <julien.rische@laposte.net>
7 LOG_LEVEL
="${LOG_LEVEL:-4}"
9 prog
="$(basename "$0")"
14 eval 'echo -n "$'"$1"'"'
18 eval "${1}='$(echo -n "$2" | sed "s
/'/'\\\\''/g
")'"
25 local val
="$(getv "$var")"
27 then setv
"$var" "${val} ${@}"
38 [ "$i" = "$v" ] && return 0
49 for i
in $
(echo -n "$1" |
sed -e 's/\(.\)/\1\n/g')
82 (-*) opt
="$(echo -n "$arg" | sed -ne 's/^-\(.\+\)$/\1/p')" ;;
84 if [ "$opt" ] && [ -t 1 ]; then
85 str
="${str}$(fmt_tty "$opt" "$arg")"
98 debug_p
() { [ "$LOG_LEVEL" -ge 4 ] && fmt >&2 "$@"; }
99 debug
() { [ "$LOG_LEVEL" -ge 4 ] && fmt >&2 "${prog}: " -hg 'debug' ': ' "$@"; }
100 info_p
() { [ "$LOG_LEVEL" -ge 3 ] && fmt >&2 "$@"; }
101 info
() { [ "$LOG_LEVEL" -ge 3 ] && fmt >&2 "${prog}: " -hg 'info' ': ' "$@"; }
102 warn_p
() { [ "$LOG_LEVEL" -ge 2 ] && fmt >&2 "$@"; }
103 warn
() { [ "$LOG_LEVEL" -ge 2 ] && fmt >&2 "${prog}: " -hy 'warn' ': ' "$@"; }
106 [ "$LOG_LEVEL" -ge 1 ] && fmt >&2 "${prog}: " -hr 'error' ': ' "$@"
111 [ "$LOG_LEVEL" -ge 1 ] && fmt >&2 "${prog}: " -hr 'fatal' ': ' "$@"
118 def is_obj_val($k; $t):
119 type=="object" and has($k) and (.[$k] | type)==$t;
121 def is_class($class):
122 is_obj_val("class"; "string") and .class==$class;
125 type=="object" and (has("class") | not) and has("block")
126 and (.block | type)=="array";
129 type=="object" and has("class") and .class=="fact";
132 [ .[] | if is_block then .block | flatten_blocks[] else . end ];
135 map(if (type=="object") and (has("class") | not)
136 then keys_unsorted[0] as $class
137 | .class=$class | .name=.[$class] | del(.[$class])
140 def to_final_hierarchy:
143 .[$facts[.name]] // empty | to_final_hierarchy[]
146 if is_class("targets") then
147 if $targets | contains(["all"]) then
148 to_entries[] | select(.key!="class") | .value
150 .[$targets[]] // empty
151 end | to_final_hierarchy[]
153 flatten_blocks | set_class_names | map(filter_facts | filter_targets);
155 def get_include_index:
156 [ path(.. | if is_obj_val("include"; "string") then .include else empty end) ] as $paths
157 | [[getpath($paths[])], [$paths[] | .[:length-1]]]
158 | transpose | map({ file:.[0], path:.[1] });
160 def apply_includes($includes):
161 reduce $includes[] as $i (.; setpath(
163 if $i.content | type=="object" then $i.content else { block: $i.content } end));
165 def get_include_file_paths:
166 map(.file) | join("\n");
168 def get_names_from($class):
169 [.[] | select(is_class($class)) | .name] | flatten | unique | join(" ");
172 [ .[] | select(type!="string") ];
174 def merge_template_params:
175 map(if is_class("template") and (.params | type=="array")
176 then (.params=(reduce .params[] as $x ({}; . * $x)))
179 def merge_first($class):
181 { class: $class, name: ([.[] | select(is_class($class)) | .name ] | flatten | unique)},
182 (.[] | select(is_class($class) | not))
186 (split(" ") | join("\\ "));
188 def make_path_absolute:
189 if .[0:1] == "/" then "" else "~/" end + .;
191 def escape_file_names:
194 .esc_name |= ($name | escape_filename | make_path_absolute) |
195 .sh_esc_from |= ($from | @sh) |
196 .esc_from |= ($from | escape_filename) |
197 .sh_esc_name |= (if $name[0:1] == "/" then "" else "~/" end + ($name | @sh)) |
198 .sh_esc_dir |= (if $name[0:1] == "/" then "" else "~/" end + ($name | capture("^(?<dir>.*)/[^/]+/*$").dir | @sh)) ;
202 then @sh "make_link \(.location) \(.target) ;"
205 def rule_indices($class):
206 map(.class) | indices($class) | join(" ");
208 def get_template_attr($rule_ind; $attr):
211 def get_template_sed_exprs($rule_ind):
212 [ (.[$rule_ind].params | to_entries[] | ("-e", ("s|{{ \(.key) }}|\(.value | tostring)|g" | @sh))) ] | join(" ");
214 def make_file($diff; $mkdir; $cp; $rm):
216 "\(.esc_name): \(.esc_from)\n" +
217 " @if ! \($diff) -q \(.sh_esc_from) \(.sh_esc_name) >/dev/null 2>&1; then \\\n" +
218 " echo '\''\($rm) -rf '\''\(.sh_esc_name) && \\\n" +
219 " \($rm) -rf \(.sh_esc_name) && \\\n" +
220 " echo '\''\($mkdir) -p '\''\(.sh_esc_dir) && \\\n" +
221 " \($mkdir) -p \(.sh_esc_dir) && \\\n" +
222 " echo '\''\($cp) -R '\''\(.sh_esc_from) \(.sh_esc_name) && \\\n" +
223 " \($cp) -R \(.sh_esc_from) \(.sh_esc_name) ; \\\n" +
225 "status-\(.esc_name):\n" +
226 " @if \($diff) -q \(.sh_esc_from) \(.sh_esc_name) >/dev/null 2>&1; then \\\n" +
227 " echo '\''[X] file: '\''\(.sh_esc_name) ; \\\n" +
229 " echo '\''[ ] file: '\''\(.sh_esc_name) ; \\\n" +
232 def make_files($diff; $mkdir; $cp; $rm):
233 map(select(is_class("file")) | escape_file_names) | (
234 (.[] | make_file($diff;$mkdir;$cp;$rm)),
236 "\nfiles: \(map(.esc_name) | join(" "))\n" +
237 "status-files: " + (map("status-" + .esc_name) | join(" "))
242 jq_json
() { "$bin_jq" -cj "$@"; }
243 jq_raw
() { "$bin_jq" -r "$@"; }
248 echo -n "$v" | jq_json
"$@"
254 echo -n "$v" | jq_raw
"$@"
260 setv
"$var" "$(getv "$var" | jq_json "$@
")"
266 if [ "$facts_distro" = arch
]; then
267 local pkgs_extra
='status-aurs'
271 # Makefile generated by pas2tir
272 # Date: $(date --rfc-3339=seconds)
275 # Distribution: ${facts_distro}
276 # Architecture: ${facts_arch}
277 # Graphics: ${facts_graphics}
278 # Hostname: ${facts_hostname}
283 status: status-pkgs ${pkgs_extra} status-files status-templates
291 local class_caps
="$(echo -n "$1" | tr '[:lower:]' '[:upper:]')"
293 case "$facts_distro" in
297 local pkgtest
='pacman -Qq $* || pacman -Qqg $*'
298 local pkginstall
='pacman -S --needed'
301 local pkgtest
='pacman -Qq $* || pacman -Qqg $*'
302 local pkginstall
='yay -S --needed'
309 local pkgtest
='rpm -V --nodeps --nodigest --nofiles --noscripts --nosignature --nolinkto --nofiledigest --nosize --nouser --nogroup --nomtime --nomode --nordev --nocaps $*'
310 local pkginstall
='yum install -y'
315 local pkgtest
='false'
316 local pkginstall
='false'
321 ${class_caps}S = ${@}
322 ${class_caps}_RULES = \$(${class_caps}S:%=${class}-%)
325 ${pkginstall} \$(${class_caps}S)
326 status-${class}s: \$(${class_caps}_RULES)
327 \$(${class_caps}_RULES):${class}-%:
328 @if { ${pkgtest}; } >/dev/null 2>&1; then echo '[X] package: \$*'; else echo '[ ] package: \$*'; fi
335 make_template_rule
() {
336 local cache
="cache/${2}"
341 @${bin_mkdir} -p $(dirname "$cache") && ${bin_sed} ${3} \$^ > \$@
351 for i
in $
(jq_raw_v
"$cfg" "$jqlib"'rule_indices("template")')
353 debug
"process rule nº${i} (template)"
354 sed_exprs
="$(jq_raw_v "$cfg" --arg i "$i" "$jqlib"'get_template_sed_exprs($i | tonumber)')"
355 location
="$(jq_raw_v "$cfg" --arg i "$i" "$jqlib"'get_template_attr($i | tonumber; "name
") | make_path_absolute' \
356 | sed 's/^ *~/$(HOME)/' | eval "${bin_sed} ${sed_exprs}")"
357 file="$(jq_raw_v "$cfg" --arg i "$i" "$jqlib"'get_template_attr($i | tonumber; "from
")' \
358 | eval "${bin_sed} ${sed_exprs}")"
359 make_template_rule
"$file" "$location" "$sed_exprs"
360 push list
"$location"
365 STATUS_TMPLS = \$(TMPLS:%=status-%)
366 DIFF_TMPLS = \$(TMPLS:%=diff-%)
368 \$(TMPLS): %: cache/%
369 @if ! ${bin_diff} -q \$^ \$@ >/dev/null 2>&1; then \\
370 echo '${bin_mkdir} -p \$(dir \$@) && ${bin_cp} \$^ \$@'; \\
371 ${bin_mkdir} -p \$(dir \$@) && ${bin_cp} \$^ \$@; \\
373 \$(STATUS_TMPLS): status-%: cache/%
374 @if ${bin_diff} -q \$^ \$* >/dev/null 2>/dev/null; then \\
375 echo '[X] template: \$*'; \\
377 echo '[ ] template: \$*'; \\
379 \$(DIFF_TMPLS): diff-%: cache/%
380 @${bin_diff} --color=auto \$* \$^ ; true
383 status-templates: \$(STATUS_TMPLS)
389 debug
'generate file rules'
390 jq_raw_v
"$1" --arg cp "$bin_cp" --arg diff "$bin_diff" --arg mkdir
"$bin_mkdir" --arg rm "$bin_rm" \
391 "$jqlib"'make_files($diff; $mkdir; $cp; $rm)'
396 get_graphics_fact
() {
397 "$bin_lspci" |
"$bin_grep" -Eq ' 3D .+ NVIDIA ' \
398 && "$bin_lspci" |
"$bin_grep" -Eq ' VGA .+ Intel ' \
399 && { info
'GPU is ' -hy 'NVIDIA Optimus'; echo -n 'optimus'; return; }
400 "$bin_lspci" |
"$bin_grep" -Eq ' VGA .+ Radeon ' \
401 && { info
'GPU is ' -hy 'ATI'; echo -n 'ati'; return; }
402 debug
'GPU is ' -hy 'unknown'
407 local distro
="$("$bin_lsb_release" -is | tr '[:upper:]' '[:lower:]')"
408 info
'Linux distribution is ' -h "$distro"
413 local arch
="$("$bin_uname" -m)"
414 info
'architecture is ' -h "$arch"
418 get_hostname_fact
() {
419 local hostname
="$("$bin_hostname")"
420 info
'hostname is ' -h "$hostname"
425 facts_distro
="$(get_distro_fact)"
426 facts_arch
="$(get_arch_fact)"
427 facts_graphics
="$(get_graphics_fact)"
428 facts_hostname
="$(get_hostname_fact)"
434 --arg distro
"$facts_distro" \
435 --arg arch
"$facts_arch" \
436 --arg graphics
"$facts_graphics" \
437 --arg hostname
"$facts_hostname" \
448 #### DEPENDENCIES ####
472 bin
="$(command -v "$i")"
474 setv
"bin_${i}" "$bin"
475 if [ "$ok" -eq 0 ]; then
476 debug
-h "$i" ' is ' -hg 'installed'
479 debug
-h "$i" ' is ' -hr 'not installed'
480 setv
"dep_${i}" false
492 debug
"include lookup iteration nº${incl_it_c}"
493 incl_idx
="$(jq_v "$cfg" "$jqlib"get_include_index)"
494 incl_it_c
=$
((incl_it_c
+ 1))
495 [ "$incl_idx" ] && [ "$incl_idx" != '[]' ]
500 for i
in $
(jq_raw_v
"$incl_idx" "$jqlib"get_include_file_paths
)
502 debug
'load file ' -h "$i"
503 jq_update includes
--argjson c
"$c" --slurpfile content
"$i" \
504 'setpath([$c, "content"]; $content[])' \
505 || fatal
"failed to import JSON file: " -h "$i"
509 debug
'insert included files'
510 jq_update cfg
--argjson includes
"$includes" "$jqlib"'apply_includes($includes)'
513 debug
'all files included'
521 debug
'flatten blocks hierarchy'
522 debug
'filter configuration tree according to facts'
523 debug
'filter configuration tree according to targets'
524 debug
'remove class syntactic sugar'
525 debug
'prepare template parameter lists'
526 debug
'group "pkg" and "user" rules'
527 jq_json
--argjson facts
"$1" --argjson targets
"$2" "$jqlib"'
530 | merge_template_params
531 | merge_first("user")
533 | merge_first("pkg")'
540 set_dependencies
$dependencies
542 facts
="$(get_json_facts)"
544 local targets
="$(jq_json -n --args '$ARGS.positional' "$@
")"
545 local cfg
="$(prepare_config "$facts" "$targets")"
547 [ "$DEBUGFILE" ] && echo -n "$cfg" |
"$bin_jq" .
> "$DEBUGFILE"
551 make_packages pkg $
(jq_raw_v
"$cfg" "$jqlib"'get_names_from("pkg")')
552 if [ "$facts_distro" = arch
]; then
553 make_packages aur $
(jq_raw_v
"$cfg" "$jqlib"'get_names_from("aur")')
556 make_templates
"$cfg"