sistema_progs

Programas para customizar o meu entorno de traballo nos meus equipos persoais
Log | Files | Refs

pattern.bash (10187B)


      1 # ble/contrib/layer/pattern.bash (C) 2023, akinomyoga
      2 #
      3 # @fn ble/highlight/layer:{pattern}/declare name [type]
      4 #   Define a new layer named `name`.
      5 #
      6 #   @param[in] name
      7 #     The name of the layer.
      8 #   @param[in,opt] type
      9 #     This specifies the type of the pattern of this layer.  One of the
     10 #     following values.  The default is `regexp`.
     11 #
     12 #     regexp
     13 #       Regular expression
     14 #     glob
     15 #       Extended glob pattern
     16 #     glob-shortest
     17 #       Extended glob pattern.  This tries to find the shortest match.
     18 #
     19 # @fn ble/highlight/layer:{pattern}/register name pattern gspec
     20 #   Register a pattern to the layer specified by `name`.
     21 #
     22 #   @param[in] name
     23 #     The name of the layer.
     24 #   @param[in] pattern
     25 #     The pattern.  The type of the pattern is specified by the argument `type`
     26 #     when the layer is created by `ble/highlight/layer:{pattern}/declare`.
     27 #   @param[in] gspec
     28 #     A string specifying the graphic style.  See the description of
     29 #     ble/color/gspec2g.
     30 #
     31 #
     32 # ```bash
     33 # # blerc
     34 #
     35 # ble-import layer/pattern
     36 #
     37 # ble/highlight/layer:{pattern}/declare pattern1
     38 # ble/highlight/layer:{pattern}/register pattern1 'rm -rf [^;&|]*' 'fg=white,bold,bg=red'
     39 # ble/array#insert-after _ble_highlight_layer_list syntax pattern1
     40 #
     41 # ble/highlight/layer:{pattern}/declare pattern2
     42 # ble/highlight/layer:{pattern}/register pattern2 "$USER" 'fg=blue,bold'
     43 # ble/highlight/layer:{pattern}/register pattern2 "$HOSTNAME" 'fg=green,bold'
     44 # ble/highlight/layer:{pattern}/register pattern2 '[0-9]+' 'bg=216,fg=black'
     45 # ble/array#insert-after _ble_highlight_layer_list pattern1 pattern2
     46 # ```
     47 
     48 function ble/highlight/layer:{pattern}/declare {
     49   local layer_name=$1 type=${2-regexp}
     50   case $type in
     51   (regexp | glob) ;;
     52   (glob-shortest) type=sglob ;;
     53   (*)
     54     ble/util/print "$FUNCNAME: unrecognized pattern type '$type'." >&2
     55     return 2 ;;
     56   esac
     57 
     58   # define dynamic variables
     59   local layer_prefix=_ble_highlight_layer_${layer_name}_
     60   ble/highlight/layer:{selection}/declare "$layer_name"
     61   ble/array#push "${layer_prefix}VARNAMES" "${layer_prefix}text"
     62   ble/util/set "${layer_prefix}text" ''
     63 
     64   # define settings
     65   local keys=${layer_prefix}keys
     66   local dict=${layer_prefix}dict
     67   builtin eval -- "
     68     ${layer_prefix}type=$type
     69     $keys=()
     70     ${_ble_util_gdict_declare//NAME/$dict}"
     71 
     72   # define functions
     73   local _ble_local_script='
     74     function ble/highlight/layer:LAYER/initialize-vars {
     75       ble/highlight/layer:{pattern}/initialize-vars LAYER
     76     }
     77     function ble/highlight/layer:LAYER/update {
     78       ble/highlight/layer:{pattern}/update LAYER "$@"
     79     }
     80     function ble/highlight/layer:LAYER/getg {
     81       ble/highlight/layer:{pattern}/getg LAYER "$@"
     82     }'
     83   builtin eval -- "${_ble_local_script//LAYER/$layer_name}"
     84 }
     85 
     86 function ble/highlight/layer:{pattern}/initialize-vars {
     87   local layer_name=$1
     88   ble/highlight/layer:{selection}/initialize-vars "$layer_name"
     89   ble/util/set "_ble_highlight_layer_${layer_name}_text" ''
     90 }
     91 
     92 function ble/highlight/layer:{pattern}/register {
     93   local layer_name=$1 pattern=$2 spec=${3-}
     94   local keys=_ble_highlight_layer_${layer_name}_keys
     95   local dict=_ble_highlight_layer_${layer_name}_dict
     96   if [[ ${3+set} ]]; then
     97     local ret
     98     ble/color/gspec2g "$spec"
     99     ble/gdict#has "$dict" "$pattern" ||
    100       ble/array#push "$keys" "$pattern"
    101     ble/gdict#set "$dict" "$pattern" "$ret"
    102   else
    103     ble/array#remove "$keys" "$pattern"
    104     ble/gdict#unset "$dict" "$pattern"
    105   fi
    106 }
    107 
    108 ##-----------------------------------------------------------------------------
    109 ## Pattern types
    110 ##
    111 ## Each pattern layer instance is associated with a pattern type, and the
    112 ## pattern type defines how the pattern specified to layer:{pattern}/register
    113 ## should be treated.  Currently, three types `regexp`, `glob`, and `sglob` are
    114 ## defined.  To define a new pattern type, the following three functions should
    115 ## be prepared.
    116 ##
    117 ## @fn ble/highlight/layer:{pattern}/pattern:<TYPE>/create-gpat
    118 ##   This function composes a pattern matching any of the registered patterns
    119 ##
    120 ##   @arr[in] keys
    121 ##     The list of patterns registered to the current layer.
    122 ##   @var[out] gpat
    123 ##     Stores a pattern that matches any of the registered pattern.
    124 ##
    125 ## @fn ble/highlight/layer:{pattern}/pattern:<TYPE>/match text pat
    126 ##   This function tries to match PAT in TEXT and, if matching, stores the
    127 ##   matched range in [MBEG, MEND) and returns the unmatched suffix in
    128 ##   NEW_TAIL.
    129 ##
    130 ##   @param[in] text
    131 ##     The string where a matching substring is searched.
    132 ##   @param[in] pat
    133 ##     The pattern
    134 ##   @var[out] mbeg mend
    135 ##     Stores the matched range in TEXT.  MBEG and MEND are the beginning and
    136 ##     the end of the range, respectively.
    137 ##   @var[out] new_tail
    138 ##     Stores the unmatched remaining part of TEXT.  In particular, the
    139 ##     substring after MEND.
    140 ##   @exit
    141 ##     0 if a matching is found, or otherwise 1.
    142 ##
    143 ## @fn ble/highlight/layer:{pattern}/pattern:regexp/match1 str pat
    144 ##   This function tests if the specified string exactly matches the pattern.
    145 ##
    146 ##   @param[in] str
    147 ##     The string to be matched.
    148 ##   @param[in] pat
    149 ##     The pattern to matched STR.
    150 ##   @exit
    151 ##     0 if the string matches the pattern, or otherwise 1
    152 ##
    153 
    154 # pattern type: regexp
    155 
    156 function ble/highlight/layer:{pattern}/pattern:regexp/create-gpat {
    157   IFS='|' builtin eval -- 'gpat="(${keys[*]})"'
    158 }
    159 function ble/highlight/layer:{pattern}/pattern:regexp/match {
    160   ble/string#match "$1" "$2(.*)\$" || return 1
    161   new_tail=${BASH_REMATCH[${#BASH_REMATCH[@]}-1]}
    162   mbeg=$((${#1}-${#BASH_REMATCH}))
    163   mend=$((${#1}-${#new_tail}))
    164   return 0
    165 }
    166 function ble/highlight/layer:{pattern}/pattern:regexp/match1 {
    167   [[ $1 =~ ^($2)$ ]]
    168 }
    169 
    170 # pattern type: glob (longest extended glob matching)
    171 
    172 function ble/highlight/layer:{pattern}/pattern:glob/create-gpat {
    173   IFS='|' builtin eval -- 'gpat="@(${keys[*]})"'
    174 }
    175 function ble/highlight/layer:{pattern}/pattern:glob/match {
    176   local extglob=
    177   shopt -q extglob && extglob=1
    178   shopt -s extglob
    179   local prefix=${1%%$2*} ext=1
    180   if [[ $prefix != "$1" ]]; then
    181     mbeg=${#prefix}
    182     new_tail=${1:mbeg}
    183     new_tail=${new_tail##$2}
    184     ((mend=${#1}-${#new_tail}))
    185     ext=0
    186   fi
    187   [[ $extglob ]] || shopt -u extglob
    188   return "$ext"
    189 }
    190 function ble/highlight/layer:{pattern}/pattern:glob/match1 {
    191   [[ $1 == $2 ]]
    192 }
    193 
    194 # pattern type: sglob (shortest extended glob matching)
    195 
    196 function ble/highlight/layer:{pattern}/pattern:sglob/create-gpat {
    197   ble/highlight/layer:{pattern}/pattern:glob/create-gpat
    198 }
    199 function ble/highlight/layer:{pattern}/pattern:sglob/match {
    200   local extglob=
    201   shopt -q extglob && extglob=1
    202   shopt -s extglob
    203   local prefix=${1%%$2*} ext=1
    204   if [[ $prefix != "$1" ]]; then
    205     mbeg=${#prefix}
    206     new_tail=${1:mbeg}
    207     new_tail=${new_tail#$2}
    208     ((mend=${#1}-${#new_tail}))
    209     ext=0
    210   fi
    211   [[ $extglob ]] || shopt -u extglob
    212   return "$ext"
    213 }
    214 function ble/highlight/layer:{pattern}/pattern:sglob/match1 {
    215   ble/highlight/layer:{pattern}/pattern:glob/match1 "$@"
    216 }
    217 
    218 ##-----------------------------------------------------------------------------
    219 
    220 function ble/highlight/layer:{pattern}/.match {
    221   # If the text has the same content as the previous time, we skip the
    222   # matching.
    223   #
    224   # Note: Initially, ((DMIN<0)) was used for the condition but turned out to be
    225   # unusable for this purpose.  DMIN only changes when the full content
    226   # including the auto_complete insertion is changed.  Even if the substantial
    227   # part (excluding the auto_complete insertion) changes, DMIN can be negative
    228   # when the full content does not change.
    229   local rtext=_ble_highlight_layer_${1}_text
    230   local text=$2 otext=${!rtext}
    231   [[ $otext && $text == "$otext" ]] && return 0
    232   ble/util/set "$rtext" "$text"
    233 
    234   local ret
    235   local dict=_ble_highlight_layer_${1}_dict
    236   sel=() gflags=()
    237 
    238   # Retrieve regular expressions
    239   local keys type
    240   ble/util/restore-vars "_ble_highlight_layer_${1}_" keys type
    241   ((${#keys[@]})) || return 0
    242 
    243   local gpat
    244   ble/highlight/layer:{pattern}/pattern:"$type"/create-gpat
    245   local g0=
    246   if ((${#keys[@]}==1)); then
    247     ble/gdict#get "$dict" "${keys[0]}" && g0=$ret
    248   fi
    249 
    250   local offset=0 tail=$text new_tail mbeg mend m
    251   while [[ $tail ]] && ble/highlight/layer:{pattern}/pattern:"$type"/match "$tail" "$gpat"; do
    252     ((mbeg+=offset,mend+=offset))
    253     if ((mbeg<mend)); then
    254       # determine gflags of the selection
    255       local g1=$g0
    256       if [[ ! $g1 ]]; then
    257         local pat1 m=${tail:mbeg-offset:mend-mbeg}
    258         for pat1 in "${keys[@]}"; do
    259           if ble/highlight/layer:{pattern}/pattern:"$type"/match1 "$m" "$pat1"; then
    260             ble/gdict#get "$dict" "$pat1" && g1=$ret
    261             break
    262           fi
    263         done
    264       fi
    265 
    266       # add selection with the gflags
    267       if [[ $g1 ]]; then
    268         if ((mbeg==offset&&${#sel[@]})) && [[ ${gflags[${#gflags[@]}-1]} == $g1 ]]; then
    269           # extend the previous selection
    270           sel[${#sel[@]}-1]=$mend
    271         else
    272           # add a new selection
    273           ble/array#push sel "$mbeg" "$mend"
    274           ble/array#push gflags "$g1"
    275         fi
    276       fi
    277     fi
    278 
    279     if ((mend==offset)); then
    280       # step at least one character to avoid infinite matching
    281       ((offset++))
    282       tail=${tail:1}
    283     else
    284       offset=$mend
    285       tail=$new_tail
    286     fi
    287   done
    288 }
    289 
    290 function ble/highlight/layer:{pattern}/update {
    291   local layer_name=$1 text=$2
    292   local sel=-1 gflags=-1
    293 
    294   local text1=$text
    295   if [[ $_ble_edit_mark_active == auto_complete ]]; then
    296     # When there is an insertion by auto-complete, we exclude that part from
    297     # the matching target.
    298     local a=$_ble_edit_ind b=$_ble_edit_mark
    299     text1=${text::a}${text:b}
    300   fi
    301   ble/highlight/layer:{pattern}/.match "$layer_name" "$text1"
    302   if [[ $_ble_edit_mark_active == auto_complete ]]; then
    303     # The generated positions in the array "sel" is for "text1" where the
    304     # auto-complete insertion is excluded.  Here, we shift the positions in
    305     # "sel" to consider the excluded part.
    306     local i
    307     for i in "${!sel[@]}"; do
    308       ((sel[i]>a||sel[i]==a&&i%2==0)) && ((sel[i]+=b-a))
    309     done
    310   fi
    311 
    312   ble/highlight/layer:{selection}/update "$layer_name" "$text"
    313 }
    314 
    315 function ble/highlight/layer:{pattern}/getg {
    316   ble/highlight/layer:{selection}/getg "$@"
    317 }