#!/usr/bin/env bash # ------------------------------------------------------------------------------ # Copyright (c) 2013-2026 Geoffray Levasseur # Protected by the BSD3 license. Please read bellow for details. # # * Redistribution and use in source and binary forms, # * with or without modification, are permitted provided # * that the following conditions are met: # * # * Redistributions of source code must retain the above # * copyright notice, this list of conditions and the # * following disclaimer. # * # * Redistributions in binary form must reproduce the above # * copyright notice, this list of conditions and the following # * disclaimer in the documentation and/or other materials # * provided with the distribution. # * # * Neither the name of the copyright holder nor the names # * of any other contributors may be used to endorse or # * promote products derived from this software without # * specific prior written permission. # * # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND # * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, # * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR # * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # * OF SUCH DAMAGE. # ------------------------------------------------------------------------------ # Common helpers (helpers/common.sh) declare -F _profile_load_helpers >/dev/null || . "$(dirname -- "${BASH_SOURCE[0]}")/../helpers/common.sh" load_conf disp # ------------------------------------------------------------------------------ # Color definitions set_colors() { # Standard 16 colors display declaration export DEFAULTFG='\e[0;39m' export DEFAULTBG='\e[0;49m' export DEFAULTCOL="${DEFAULTBG}${DEFAULTFG}" export RESETCOL='\e[0m' # Regular Colors export Black='\e[0;30m' export Red='\e[0;31m' export Green='\e[0;32m' export Yellow='\e[0;33m' export Blue='\e[0;34m' export Purple='\e[0;35m' export Cyan='\e[0;36m' export White='\e[0;37m' # Bold export BBlack='\e[1;30m' export BRed='\e[1;31m' export BGreen='\e[1;32m' export BYellow='\e[1;33m' export BBlue='\e[1;34m' export BPurple='\e[1;35m' export BCyan='\e[1;36m' export BWhite='\e[1;37m' # Underline export UBlack='\e[4;30m' export URed='\e[4;31m' export UGreen='\e[4;32m' export UYellow='\e[4;33m' export UBlue='\e[4;34m' export UPurple='\e[4;35m' export UCyan='\e[4;36m' export UWhite='\e[4;37m' # Background export On_Black='\e[40m' export On_Red='\e[41m' export On_Green='\e[42m' export On_Yellow='\e[43m' export On_Blue='\e[44m' export On_Purple='\e[45m' export On_Cyan='\e[46m' export On_White='\e[47m' # High Intensity export IBlack='\e[0;90m' export IRed='\e[0;91m' export IGreen='\e[0;92m' export IYellow='\e[0;93m' export IBlue='\e[0;94m' export IPurple='\e[0;95m' export ICyan='\e[0;96m' export IWhite='\e[0;97m' # Bold High Intensity export BIBlack='\e[1;90m' export BIRed='\e[1;91m' export BIGreen='\e[1;92m' export BIYellow='\e[1;93m' export BIBlue='\e[1;94m' export BIPurple='\e[1;95m' export BICyan='\e[1;96m' export BIWhite='\e[1;97m' # High Intensity backgrounds export On_IBlack='\e[0;100m' export On_IRed='\e[0;101m' export On_IGreen='\e[0;102m' export On_IYellow='\e[0;103m' export On_IBlue='\e[0;104m' export On_IPurple='\e[0;105m' export On_ICyan='\e[0;106m' export On_IWhite='\e[0;107m' } export -f set_colors # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # Display a message # Usage: disp # Types: # I : info (green), stdout # W : warning (yellow), stderr # E : error (red), stderr # D : debug (cyan), stderr, shown only when DEBUG is an integer > 1 # Without a recognised type (case-insensitive), the arguments are the message # itself, printed unprefixed on stdout like an info. # # Environment: # NO_COLOR non-empty: no colour or reset sequence is emitted # QUIET=1 hides info and unprefixed messages (warnings/errors remain) # COLUMNS terminal width used for wrapping (tput cols otherwise, 80 fallback) # # The message is wrapped to the terminal width on spaces, ANSI sequences count # for no width, and continuation lines are aligned after the prefix. Backslash # escapes (\t, \e, ...) are interpreted when printing (printf %b), but wrapping # is computed before that, so a literal "\t" counts as two characters. # A message hidden by QUIET/DEBUG still returns 0. disp() { local _disp_print_wrapped _disp_print_wrapped() { local prefix="$1" local target_fd="$2" shift 2 local message="$*" local cols="${COLUMNS:-}" if [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]]; then cols=$(tput cols 2>/dev/null) fi [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]] && cols=80 # Match ANSI CSI sequences in real-ESC, literal \e and literal \033 forms # so their on-screen width (zero) is excluded from all width calculations. local ansi_re=$'\e\\[[0-9;]*[A-Za-z]' ansi_re+='|\\e\[[0-9;]*[A-Za-z]' ansi_re+='|\\033\[[0-9;]*[A-Za-z]' # The hanging-indent width is the prefix's VISIBLE length (ANSI stripped), # so callers no longer need to pass a separate plain-text length. local _pv=$prefix while [[ $_pv =~ $ansi_re ]]; do _pv=${_pv/"${BASH_REMATCH[0]}"/}; done local indent_len=0 (( ${#_pv} > 0 )) && indent_len=$(( ${#_pv} + 1 )) local width=$((cols - indent_len)) (( width < 10 )) && width=10 # ANSI-aware soft wrap (fold -s semantics): break at the last space that # keeps a line within `width` VISIBLE columns, hard-breaking only when a # run has no space. ANSI escapes are copied through at zero width so they # no longer inflate the measured width and cause premature wrapping. local wrapped="" line="" line_vis=0 last_space=-1 local _i=0 _n=${#message} while (( _i < _n )); do local _c=${message:_i:1} # Only a real ESC or a literal backslash can start an ANSI sequence; # testing this first avoids copying the message tail (and running # the regex) for every ordinary character. if [[ $_c == $'\e' || $_c == '\' ]]; then local _sub=${message:_i} if [[ $_sub =~ ^($ansi_re) ]]; then line+=${BASH_REMATCH[0]} _i=$(( _i + ${#BASH_REMATCH[0]} )) continue fi fi _i=$(( _i + 1 )) line+=$_c (( ++line_vis )) [[ $_c == ' ' ]] && last_space=${#line} if (( line_vis > width )); then if (( last_space > 0 )); then wrapped+=${line:0:last_space-1}$'\n' line=${line:last_space} local _st=$line while [[ $_st =~ $ansi_re ]]; do _st=${_st/"${BASH_REMATCH[0]}"/}; done line_vis=${#_st} else local _keep=${line: -1} wrapped+=${line:0:${#line}-1}$'\n' line=$_keep line_vis=1 fi last_space=-1 fi done wrapped+=$line # RESETCOL closes any colour left open by the message. It is skipped # under NO_COLOR so that no escape sequence at all is emitted. local reset="${RESETCOL:-}" [[ -n ${NO_COLOR:-} ]] && reset="" # >&"$target_fd" sends the output to stdout (1) or stderr (2). The # first line carries the prefix, continuation lines are indented to # line up with the message body. local first_line=1 local line while IFS= read -r line || [[ -n "$line" ]]; do if (( first_line )); then if [[ -n "$prefix" ]]; then printf "%b\n" "${prefix} ${line}${reset}" >&"$target_fd" else printf "%b\n" "${line}${reset}" >&"$target_fd" fi first_line=0 else printf "%*s%b\n" "$indent_len" "" "${line}${reset}" >&"$target_fd" fi done <<< "$wrapped" } # Handle NO_COLOR: disable colors if set local color_enabled=1 [[ -n ${NO_COLOR:-} ]] && color_enabled=0 # Each branch ends with an "if" (never a bare "[[ ]] && cmd") so that a # message hidden by QUIET/DEBUG still returns 0: a non-zero status would # trip "set -e", ERR traps, and "disp ... && next" chains. case ${1^^} in "I") if [[ $color_enabled -eq 1 ]]; then local heads="[ ${IGreen}info${DEFAULTFG} ]" else local heads="[ info ]" fi shift if [[ ${QUIET:-} != 1 ]]; then _disp_print_wrapped "$heads" 1 "$*" fi ;; "W") if [[ $color_enabled -eq 1 ]]; then local heads="[ ${IYellow}Warning${DEFAULTFG} ]" else local heads="[ Warning ]" fi shift _disp_print_wrapped "$heads" 2 "$*" ;; "E") if [[ $color_enabled -eq 1 ]]; then local heads="[ ${IRed}ERROR${DEFAULTFG} ]" else local heads="[ ERROR ]" fi shift _disp_print_wrapped "$heads" 2 "$*" ;; "D") if [[ $color_enabled -eq 1 ]]; then local heads="[ ${ICyan}debug${DEFAULTFG} ]" else local heads="[ debug ]" fi shift # Shown only when DEBUG is an integer greater than 1; sent to stderr # like the other diagnostics, so it never pollutes captured output. if [[ ${DEBUG:-} =~ ^[0-9]+$ && $DEBUG -gt 1 ]]; then _disp_print_wrapped "$heads" 2 "$*" fi ;; * ) # No (known) type: the whole argument list is the message, as for info if [[ ${QUIET:-} != 1 ]]; then _disp_print_wrapped "" 1 "$*" fi ;; esac } export -f disp # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # Render Markdown files with terminal formatting # Usage: mdcat [file] mdcat() { local _mdcat_style_inline _mdcat_style_inline() { local text="$1" local bold_on="" italic_on="" code_on="" style_off="" local link_text_on="" link_url_on="" if [[ -z $NO_COLOR ]]; then bold_on=$'\e[1m' italic_on=$'\e[3m' code_on="${On_IBlack}${BIWhite}" link_text_on=$'\e[4;96m' link_url_on="$IBlack" style_off="$RESETCOL" fi # Inline transforms. Code spans and backslash-escaped punctuation # (\<, \>, \_, \*, ...) are lifted out first and replaced by numbered # placeholders (\x01N\x02 and \x03N\x04), so that nothing inside them is # reinterpreted (links, emphasis); they are put back, code spans styled, # at the very end. Then links, bare URLs and emphasis. local -a spans=() escs=() local ph_open=$'\x01' ph_close=$'\x02' esc_open=$'\x03' esc_close=$'\x04' while [[ "$text" =~ \`([^\`]+)\` ]]; do local match="${BASH_REMATCH[0]}" local before="${text%%"$match"*}" local after="${text#*"$match"}" spans+=("${BASH_REMATCH[1]}") text="${before}${ph_open}$(( ${#spans[@]} - 1 ))${ph_close}${after}" done while [[ "$text" =~ \\([[:punct:]]) ]]; do local match="${BASH_REMATCH[0]}" local before="${text%%"$match"*}" local after="${text#*"$match"}" escs+=("${BASH_REMATCH[1]}") text="${before}${esc_open}$(( ${#escs[@]} - 1 ))${esc_close}${after}" done # Links: [label](url) while [[ "$text" =~ \[([^][]+)\]\(([^()]+)\) ]]; do local match="${BASH_REMATCH[0]}" local label="${BASH_REMATCH[1]}" local url="${BASH_REMATCH[2]}" local before="${text%%"$match"*}" local after="${text#*"$match"}" if [[ "$label" == "$url" ]]; then if [[ -z $NO_COLOR ]]; then text="${before}${link_text_on}${label}${style_off}${after}" else text="${before}${label}${after}" fi elif [[ -z $NO_COLOR ]]; then text="${before}${link_text_on}${label}${style_off} ${link_url_on}(${url})${style_off}${after}" else text="${before}${label} (${url})${after}" fi done # Style bare URLs without re-matching the same URL forever. # The prefix capture keeps progress monotonic and prevents freeze loops. # Skip this transformation in NO_COLOR mode. if [[ -z $NO_COLOR ]]; then while [[ "$text" =~ (^|[[:space:]\(])(https?://[^[:space:]\)]+) ]]; do local match="${BASH_REMATCH[0]}" local pre="${BASH_REMATCH[1]}" local url="${BASH_REMATCH[2]}" local before="${text%%"$match"*}" local after="${text#*"$match"}" text="${before}${pre}${link_text_on}${url}${style_off}${after}" done fi while [[ "$text" =~ \*\*([^*]+)\*\* ]]; do local match="${BASH_REMATCH[0]}" local val="${BASH_REMATCH[1]}" local before="${text%%"$match"*}" local after="${text#*"$match"}" text="${before}${bold_on}${val}${style_off}${after}" done while [[ "$text" =~ __([^_]+)__ ]]; do local match="${BASH_REMATCH[0]}" local val="${BASH_REMATCH[1]}" local before="${text%%"$match"*}" local after="${text#*"$match"}" text="${before}${bold_on}${val}${style_off}${after}" done while [[ "$text" =~ \*([^*]+)\* ]]; do local match="${BASH_REMATCH[0]}" local val="${BASH_REMATCH[1]}" local before="${text%%"$match"*}" local after="${text#*"$match"}" text="${before}${italic_on}${val}${style_off}${after}" done # Match _italic_ only when underscores are outside word-like identifiers. while [[ "$text" =~ (^|[[:space:][:punct:]])_([^[:space:]_][^_]*[^[:space:]_])_([[:space:][:punct:]]|$) ]]; do local match="${BASH_REMATCH[0]}" local pre="${BASH_REMATCH[1]}" local val="${BASH_REMATCH[2]}" local post="${BASH_REMATCH[3]}" local before="${text%%"$match"*}" local after="${text#*"$match"}" text="${before}${pre}${italic_on}${val}${style_off}${post}${after}" done # Put the styled code spans and the escaped characters back (see the # placeholders above). local idx for idx in "${!spans[@]}"; do text="${text/"${ph_open}${idx}${ph_close}"/"${code_on}${spans[idx]}${style_off}"}" done for idx in "${!escs[@]}"; do text="${text/"${esc_open}${idx}${esc_close}"/"${escs[idx]}"}" done printf "%s\n" "$text" } local _mdcat_print_hr _mdcat_print_hr() { local cols="${_mdcat_cols:-}" if [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]]; then cols=$(tput cols 2>/dev/null) fi [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]] && cols=80 local hr printf -v hr "%*s" "$cols" "" hr="${hr// /-}" if [[ -z $NO_COLOR ]]; then printf "%b%s%b\n" "$IBlack" "$hr" "$RESETCOL" else printf "%s\n" "$hr" fi } local _mdcat_print_code_block _mdcat_print_code_block() { local lang="$1" shift local -a lines=("$@") local cols="${_mdcat_cols:-}" if [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]]; then cols=$(tput cols 2>/dev/null) fi [[ -z "$cols" || ! "$cols" =~ ^[0-9]+$ || "$cols" -lt 20 ]] && cols=80 local max_inner=$((cols - 4)) (( max_inner < 16 )) && max_inner=16 local width=16 line for line in "${lines[@]}"; do local line_len=${#line} (( line_len > width )) && width=$line_len done (( width > max_inner )) && width=$max_inner local border printf -v border "+-%*s-+" "$width" "" border="${border// /-}" local frame_on="" code_on="" off="" if [[ -z $NO_COLOR ]]; then frame_on="$IBlack" code_on="$BIWhite" off="$RESETCOL" fi printf "%b%s%b\n" "$frame_on" "$border" "$off" if [[ -n "$lang" ]]; then local tag="language: $lang" local wrapped_tag wrapped_tag=$(printf "%s" "$tag" | fold -s -w "$width") while IFS= read -r line || [[ -n "$line" ]]; do printf "%b| %b%-*s%b |%b\n" "$frame_on" "$code_on" "$width" "$line" "$frame_on" "$off" done <<< "$wrapped_tag" printf "%b%s%b\n" "$frame_on" "$border" "$off" fi if [[ ${#lines[@]} -eq 0 ]]; then printf "%b| %b%-*s%b |%b\n" "$frame_on" "$code_on" "$width" "" "$frame_on" "$off" else for line in "${lines[@]}"; do local wrapped wrapped=$(printf "%s" "$line" | fold -s -w "$width") while IFS= read -r wline || [[ -n "$wline" ]]; do printf "%b| %b%-*s%b |%b\n" "$frame_on" "$code_on" "$width" "$wline" "$frame_on" "$off" done <<< "$wrapped" done fi printf "%b%s%b\n" "$frame_on" "$border" "$off" } local _mdcat_print_table # That function is a bit slow, we need to try to optimize it _mdcat_print_table() { local _mdcat_parse_table_row # Split one table row into cells, joined with $sep (set by the caller, # reached through dynamic scoping). Cells are separated by '|' only; an # escaped "\|" is not supported. Trimming uses parameter expansion # (strip leading, then trailing, whitespace) to avoid forking sed. _mdcat_parse_table_row() { local input="$1" j local line="$input" line="${line#"${line%%[![:space:]]*}"}" line="${line%"${line##*[![:space:]]}"}" line="${line#|}" line="${line%|}" # Parse Markdown cells using only '|' separators and preserve spaces inside cells. local -a cells=() local cell rest="$line" while :; do if [[ "$rest" == *'|'* ]]; then cell="${rest%%|*}" rest="${rest#*|}" else cell="$rest" rest="" fi cell="${cell#"${cell%%[![:space:]]*}"}" cell="${cell%"${cell##*[![:space:]]}"}" cells+=("$cell") [[ -z "$rest" ]] && break done if [[ ${#cells[@]} -eq 0 ]]; then printf '%s' "" return fi local joined="${cells[0]}" for ((j=1; j<${#cells[@]}; ++j)); do joined+="$sep${cells[j]}" done printf '%s' "$joined" } local -a lines=("$@") local -a table_rows=() local -a col_widths=() local -A cell_styled=() cell_vislen=() local i j ncols=0 orow=0 local sep=$'\x1f' # Parse header and data rows, skipping the Markdown separator row. # Each cell is styled exactly once here; the styled text and its visible # width (ANSI stripped) are cached and reused when drawing, so the costly # _mdcat_style_inline runs once per cell instead of twice. for ((i=0; i<${#lines[@]}; ++i)); do (( i == 1 )) && continue local parsed parsed=$(_mdcat_parse_table_row "${lines[i]}") table_rows+=("$parsed") local -a row=() IFS="$sep" read -r -a row <<< "$parsed" (( ncols < ${#row[@]} )) && ncols=${#row[@]} for ((j=0; j<${#row[@]}; ++j)); do local styled visible key="$orow:$j" styled=$(_mdcat_style_inline "${row[j]}") # Visible width: expand the literal \e sequences, then strip # every ANSI CSI sequence (no sed fork per cell). printf -v visible '%b' "$styled" while [[ $visible =~ $'\e'\[[0-9\;]*[A-Za-z] ]]; do visible=${visible/"${BASH_REMATCH[0]}"/} done cell_styled["$key"]="$styled" cell_vislen["$key"]=${#visible} [[ -z "${col_widths[j]}" ]] && col_widths[j]=0 (( col_widths[j] < ${#visible} )) && col_widths[j]=${#visible} done orow=$((orow + 1)) done # Ensure all width slots are initialized before drawing borders. for ((j=0; j/dev/null) fi [[ -z "$_mdcat_cols" || ! "$_mdcat_cols" =~ ^[0-9]+$ || "$_mdcat_cols" -lt 20 ]] && _mdcat_cols=80 # Single-line push-back buffer: table detection reads one line ahead, so a # non-separator look-ahead line must be reprocessed rather than discarded. local pending_line="" has_pending=0 while :; do if [[ $has_pending -eq 1 ]]; then raw="$pending_line" has_pending=0 elif ! IFS= read -r raw; then [[ -n "$raw" ]] || break fi line="${raw%$'\r'}" # Table detection: line with |, next line with | and --- # (never inside a fenced code block, where a table is just sample text) if [[ $in_code -eq 0 && $in_table -eq 0 && "$line" =~ ^[[:space:]]*\|.*\|[[:space:]]*$ ]]; then local next="" next_ok=1 IFS= read -r next || next_ok=0 # Accept: | --- | --- | or |:---|---:| etc. if [[ "$next" =~ ^[[:space:]]*\|[[:space:]]*:?[-]+:?([[:space:]]*\|[[:space:]]*:?[ -]+:?)*\|[[:space:]]*$ ]]; then in_table=1 table_lines=("$line" "$next") continue fi # Not a separator: push the look-ahead line back so it is not lost. if [[ $next_ok -eq 1 || -n "$next" ]]; then pending_line="$next" has_pending=1 fi fi if [[ $in_table -eq 1 ]]; then # Accept table row if it starts and ends with | if [[ "$line" =~ ^[[:space:]]*\|.*\|[[:space:]]*$ && ! "$line" =~ ^[[:space:]]*\|[[:space:]]*:?[-]+:?([[:space:]]*\|[[:space:]]*:?[ -]+:?)*\|[[:space:]]*$ ]]; then table_lines+=("$line") continue else _mdcat_print_table "${table_lines[@]}" in_table=0 table_lines=() fi fi if [[ $in_code -eq 1 ]]; then if [[ "$line" =~ ^\`\`\` ]]; then _mdcat_print_code_block "$code_lang" "${code_lines[@]}" in_code=0 code_lang="" code_lines=() else code_lines+=("$line") fi continue fi if [[ "$line" =~ ^\`\`\`[[:space:]]*([^[:space:]]*) ]]; then in_code=1 code_lang="${BASH_REMATCH[1]}" code_lines=() continue fi if [[ "$line" =~ ^(#{1,6})[[:space:]]+(.*)$ ]]; then local lvl=${#BASH_REMATCH[1]} local title="${BASH_REMATCH[2]}" local h_on="" if [[ -z $NO_COLOR ]]; then case "$lvl" in 1) h_on="$BBlue" ;; 2) h_on="$BCyan" ;; 3) h_on="$BGreen" ;; *) h_on="$BIWhite" ;; esac fi local h_off="$RESETCOL" [[ -n $NO_COLOR ]] && h_off="" printf "%b%s%b\n" "$h_on" "$title" "$h_off" continue fi if [[ "$line" =~ ^[[:space:]]*([\-*_])[[:space:]]*\1[[:space:]]*\1[\-*_[:space:]]*$ ]]; then _mdcat_print_hr continue fi if [[ "$line" =~ ^([[:space:]]*)\>[[:space:]]?(.*)$ ]]; then local quote="${BASH_REMATCH[2]}" quote=$(_mdcat_style_inline "$quote") if [[ -z $NO_COLOR ]]; then printf "%s%b|%b %b\n" "${BASH_REMATCH[1]}" "$ICyan" "$RESETCOL" "$quote" else printf "%s| %b\n" "${BASH_REMATCH[1]}" "$quote" fi continue fi if [[ "$line" =~ ^([[:space:]]*)[-+*][[:space:]]+(.*)$ ]]; then local item="${BASH_REMATCH[2]}" item=$(_mdcat_style_inline "$item") if [[ -z $NO_COLOR ]]; then printf "%s%b*%b %b\n" "${BASH_REMATCH[1]}" "$IGreen" "$RESETCOL" "$item" else printf "%s* %b\n" "${BASH_REMATCH[1]}" "$item" fi continue fi if [[ "$line" =~ ^([[:space:]]*)([0-9]+)\.[[:space:]]+(.*)$ ]]; then local nitem="${BASH_REMATCH[3]}" nitem=$(_mdcat_style_inline "$nitem") if [[ -z $NO_COLOR ]]; then printf "%s%b%s.%b %b\n" "${BASH_REMATCH[1]}" "$IGreen" "${BASH_REMATCH[2]}" "$RESETCOL" "$nitem" else printf "%s%s. %b\n" "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" "$nitem" fi continue fi printf "%b\n" "$(_mdcat_style_inline "$line")" done < "${input_file:-/dev/stdin}" # Flush any block still open at end-of-input. if [[ $in_code -eq 1 ]]; then _mdcat_print_code_block "$code_lang" "${code_lines[@]}" fi if [[ $in_table -eq 1 ]]; then _mdcat_print_table "${table_lines[@]}" fi } export -f mdcat # ------------------------------------------------------------------------------ # Load color codes set_colors # EOF