Do not verify details about RPM package when checking installation status
[pas2tir.git] / pas2tir.sh
bloba49b5c92b9878b1d905175480a968d9dcff06767
1 #!/bin/sh
2 # Author: Julien Rische <julien.rische@laposte.net>
3 # Created: 2020-04-22
4 # Modified: 2021-12-27
5 # Version:
7 LOG_LEVEL="${LOG_LEVEL:-4}"
9 prog="$(basename "$0")"
11 #### COMMON ####
13 getv() {
14 eval 'echo -n "$'"$1"'"'
17 setv() {
18 eval "${1}='$(echo -n "$2" | sed "s/'/'\\\\''/g")'"
21 push() {
22 if [ $# -ge 2 ]; then
23 local var="$1"
24 shift
25 local val="$(getv "$var")"
26 if [ "$val" ]
27 then setv "$var" "${val} ${@}"
28 else setv "$var" "$@"
33 contain() {
34 local v="$1"
35 shift
36 for i in "$@"
38 [ "$i" = "$v" ] && return 0
39 done
40 false
43 #### FORMATTING ####
45 fmt_tty() {
46 while [ $# -gt 1 ]
48 local i
49 for i in $(echo -n "$1" | sed -e 's/\(.\)/\1\n/g')
51 case "$i" in
52 (h) tput bold ;;
53 (u) tput smul ;;
54 (r) tput setaf 1 ;;
55 (g) tput setaf 2 ;;
56 (y) tput setaf 3 ;;
57 (b) tput setaf 4 ;;
58 esac
59 done
60 shift
61 done
62 echo -n "$@"
63 tput sgr0
66 fmt() {
67 local str
68 local arg
69 local opt
70 local echoopt
71 local noopt=false
72 while [ $# -gt 0 ]
74 arg="$1"
75 if "$noopt"; then
76 str="${str}${@}"
77 break
78 else
79 case "$arg" in
80 (--) noopt=true ;;
81 (-n) echoopt='-n' ;;
82 (-*) opt="$(echo -n "$arg" | sed -ne 's/^-\(.\+\)$/\1/p')" ;;
83 (*)
84 if [ "$opt" ] && [ -t 1 ]; then
85 str="${str}$(fmt_tty "$opt" "$arg")"
86 else
87 str="${str}${arg}"
89 opt=
91 esac
93 shift
94 done
95 echo $echoopt "$str"
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' ': ' "$@"; }
105 error() {
106 [ "$LOG_LEVEL" -ge 1 ] && fmt >&2 "${prog}: " -hr 'error' ': ' "$@"
107 false
110 fatal() {
111 [ "$LOG_LEVEL" -ge 1 ] && fmt >&2 "${prog}: " -hr 'fatal' ': ' "$@"
112 exit 1
115 #### JQLIB ####
117 jqlib='
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;
124 def is_block:
125 type=="object" and (has("class") | not) and has("block")
126 and (.block | type)=="array";
128 def is_fact:
129 type=="object" and has("class") and .class=="fact";
131 def flatten_blocks:
132 [ .[] | if is_block then .block | flatten_blocks[] else . end ];
134 def set_class_names:
135 map(if (type=="object") and (has("class") | not)
136 then keys_unsorted[0] as $class
137 | .class=$class | .name=.[$class] | del(.[$class])
138 else . end);
140 def to_final_hierarchy:
141 def filter_facts:
142 if is_fact then
143 .[$facts[.name]] // empty | to_final_hierarchy[]
144 else . end;
145 def filter_targets:
146 if is_class("targets") then
147 if $targets | contains(["all"]) then
148 to_entries[] | select(.key!="class") | .value
149 else
150 .[$targets[]] // empty
151 end | to_final_hierarchy[]
152 else . end;
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(
162 $i.path;
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(" ");
171 def remove_comments:
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)))
177 else . end);
179 def merge_first($class):
181 { class: $class, name: ([.[] | select(is_class($class)) | .name ] | flatten | unique)},
182 (.[] | select(is_class($class) | not))
185 def escape_filename:
186 (split(" ") | join("\\ "));
188 def make_path_absolute:
189 if .[0:1] == "/" then "" else "~/" end + .;
191 def escape_file_names:
192 .name as $name |
193 .from as $from |
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)) ;
200 def make_link:
201 if is_class("link")
202 then @sh "make_link \(.location) \(.target) ;"
203 else . end;
205 def rule_indices($class):
206 map(.class) | indices($class) | join(" ");
208 def get_template_attr($rule_ind; $attr):
209 .[$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):
215 "\n" +
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" +
224 " fi\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" +
228 " else \\\n" +
229 " echo '\''[ ] file: '\''\(.sh_esc_name) ; \\\n" +
230 " fi";
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 "$@"; }
245 jq_v() {
246 local v="$1"
247 shift
248 echo -n "$v" | jq_json "$@"
251 jq_raw_v() {
252 local v="$1"
253 shift
254 echo -n "$v" | jq_raw "$@"
257 jq_update() {
258 local var="$1"
259 shift
260 setv "$var" "$(getv "$var" | jq_json "$@")"
263 #### MAKE ####
265 make_start() {
266 if [ "$facts_distro" = arch ]; then
267 local pkgs_extra='status-aurs'
269 cat <<EOF
271 # Makefile generated by pas2tir
272 # Date: $(date --rfc-3339=seconds)
274 # For:
275 # Distribution: ${facts_distro}
276 # Architecture: ${facts_arch}
277 # Graphics: ${facts_graphics}
278 # Hostname: ${facts_hostname}
281 all:
283 status: status-pkgs ${pkgs_extra} status-files status-templates
287 #1: class
288 #@: packages
289 make_packages() {
290 local class="$1"
291 local class_caps="$(echo -n "$1" | tr '[:lower:]' '[:upper:]')"
292 shift
293 case "$facts_distro" in
294 (arch)
295 case "$class" in
296 (pkg)
297 local pkgtest='pacman -Qq $* || pacman -Qqg $*'
298 local pkginstall='pacman -S --needed'
300 (aur)
301 local pkgtest='pacman -Qq $* || pacman -Qqg $*'
302 local pkginstall='yay -S --needed'
304 esac
306 (fedora)
307 case "$class" in
308 (pkg)
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'
312 esac
315 local pkgtest='false'
316 local pkginstall='false'
318 esac
319 cat <<EOF
321 ${class_caps}S = ${@}
322 ${class_caps}_RULES = \$(${class_caps}S:%=${class}-%)
324 install-${class}s:
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
332 #1: file
333 #2: location
334 #3: sed expressions
335 make_template_rule() {
336 local cache="cache/${2}"
337 cat <<EOF
339 .PHONY: ${cache}
340 ${cache}: ${1}
341 @${bin_mkdir} -p $(dirname "$cache") && ${bin_sed} ${3} \$^ > \$@
345 #1: config
346 make_templates() {
347 local cfg="$1"
348 local list
349 local i
350 echo
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"
361 done
362 cat <<EOF
364 TMPLS = ${list}
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: \$*'; \\
376 else \\
377 echo '[ ] template: \$*'; \\
379 \$(DIFF_TMPLS): diff-%: cache/%
380 @${bin_diff} --color=auto \$* \$^ ; true
382 templates: \$(TMPLS)
383 status-templates: \$(STATUS_TMPLS)
387 #1: config
388 make_files() {
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)'
394 #### FACTS ####
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'
403 echo -n 'unknown'
406 get_distro_fact() {
407 local distro="$("$bin_lsb_release" -is | tr '[:upper:]' '[:lower:]')"
408 info 'Linux distribution is ' -h "$distro"
409 echo -n "$distro"
412 get_arch_fact() {
413 local arch="$("$bin_uname" -m)"
414 info 'architecture is ' -h "$arch"
415 echo -n "$arch"
418 get_hostname_fact() {
419 local hostname="$("$bin_hostname")"
420 info 'hostname is ' -h "$hostname"
421 echo -n "$hostname"
424 set_facts_env() {
425 facts_distro="$(get_distro_fact)"
426 facts_arch="$(get_arch_fact)"
427 facts_graphics="$(get_graphics_fact)"
428 facts_hostname="$(get_hostname_fact)"
431 get_json_facts()
433 "$bin_jq" -cjn \
434 --arg distro "$facts_distro" \
435 --arg arch "$facts_arch" \
436 --arg graphics "$facts_graphics" \
437 --arg hostname "$facts_hostname" \
440 distro: $distro,
441 arch: $arch,
442 graphics: $graphics,
443 hostname: $hostname
448 #### DEPENDENCIES ####
450 dependencies='
452 diff
453 grep
454 hostname
457 lsb_release
458 lspci
459 make
460 mkdir
463 tput
464 uname
467 set_dependencies() {
468 local bin
469 local ok
470 for i in $@
472 bin="$(command -v "$i")"
473 ok="$?"
474 setv "bin_${i}" "$bin"
475 if [ "$ok" -eq 0 ]; then
476 debug -h "$i" ' is ' -hg 'installed'
477 setv "dep_${i}" true
478 else
479 debug -h "$i" ' is ' -hr 'not installed'
480 setv "dep_${i}" false
482 done
485 process_includes() {
486 local cfg="$(cat)"
488 local incl_it_c=1
489 local incl_idx
490 local includes
491 while {
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" != '[]' ]
498 includes="$incl_idx"
499 local c=0
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"
506 c=$(($c + 1))
507 done
509 debug 'insert included files'
510 jq_update cfg --argjson includes "$includes" "$jqlib"'apply_includes($includes)'
511 done
513 debug 'all files included'
514 echo -n "$cfg"
517 #1: facts
518 #2: targets
519 prepare_config() {
520 process_includes | {
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"'
528 to_final_hierarchy
529 | remove_comments
530 | merge_template_params
531 | merge_first("user")
532 | merge_first("aur")
533 | merge_first("pkg")'
537 main() {
538 local i
540 set_dependencies $dependencies
541 set_facts_env
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"
549 make_start
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"
557 make_files "$cfg"
561 main "$@"