View Single Post
Old 01-27-2013, 10:27 PM   #194
twobob
( ͡° ͜ʖ ͡°){ʇlnɐɟ ƃǝs}Týr
twobob ought to be getting tired of karma fortunes by now.twobob ought to be getting tired of karma fortunes by now.twobob ought to be getting tired of karma fortunes by now.twobob ought to be getting tired of karma fortunes by now.twobob ought to be getting tired of karma fortunes by now.twobob ought to be getting tired of karma fortunes by now.twobob ought to be getting tired of karma fortunes by now.twobob ought to be getting tired of karma fortunes by now.twobob ought to be getting tired of karma fortunes by now.twobob ought to be getting tired of karma fortunes by now.twobob ought to be getting tired of karma fortunes by now.
 
twobob's Avatar
 
Posts: 6,552
Karma: 6015922
Join Date: Jun 2012
Location: uti gratia usura (Yao ying da ying; Mo ying da yieng)
Device: PW-WIFI|K5-3G+WIFI| K4|K3-3G|DXG|K2| Rooted Nook Touch
Hi. Due to the way they will be called as a resource from inside the kindlet.

Whilst quite frankly brilliant, it would be simpler to provide only a lowest-common-denominator script. The vast %age of startup slowdown is kindlet instantiation and though 350% faster the complications of knitting dynamically loaded scripts outweigh the gains. Also the overhead and delay in processing the creation and destruction of multiple temp files would be a negative.

So. Just a single, lowest common denominator script please. Sorry. I feel really bad saying this... But the way scripts are fired makes this impractical. Script Names are also created dynamically as an extra complication...

Excellent work though by the way. (super script skills)

EDIT: I just stuck one together from what you provided.
nice and clean code by the way.

./parse.sh (lowest performance only version, monolithic script)
Spoiler:
PHP Code:
#!/bin/ash -
# aloop.sh - last update 20130127,a,stepk
# Tested on KT 5.1.2 /bin/busybox ash (it's ash not (ba)sh!), version banner:
#   BusyBox v1.17.1 (2012-07-17 16:29:54 PDT) multi-call binary
# and on K3 /bin/busybox sh running on KT 5.1.2, version banner:
#   BusyBox v1.7.2 (2012-09-01 14:15:22 PDT) multi-call binary.
# UTF-8 support untested.

usage () {
echo "Usage: ${0##*/} [options]
  parse menu files in $EXTENSIONDIR
  system: `uname -rsnm`"
cat << 'EOT'

Options:
 -h | --help
 -c=MAX | --colors=MAX   : max cyclical index when -f=twolevel (default 0=off)
 -f=NAME | -format=NAME   : select output format, NAME is one of:
   default     default format, also when -f isn't specified, sortable
   debuginfo   dump xml_* and json_* variables
   touchrunner compatible with TouchRunner launcher, sortable
   twolevel    default + group name and color index, sortable, see also -c
 -l | --log    : enable logging to stderr
 -s | --sort   : sort output by label
 
Limitations:
. Supports json menus only
. Supports one- or two-level menus only
. A menu entry must not extend across multiple lines. Example of a valid entry:
  {"name": "a label", "priority": 3, "action" : "foo.sh", "params": "p1,p2"}
  with or without a traling comma

EOT
}

set -f # prevent pathname expansion

# dev can adjust these four variables:
PRODUCTNAME="Unified Kindle Launcher"
EXTENSIONDIR=/mnt/us/extensions
SEPARATOR=`printf "\x01"`
COLORMAX=0 # for two_level() when --colors

case " $* " in
  *" -l "* | *" --log "*)
     opt_log=1; 
     alias log='echo >&2'" ${0##*/}: " # enabled
  ;;
  *) alias log='echo >/dev/null ' # disabled
  ;;
esac
log "system `uname -rsnm`"

# knc1's magic with minimal busybox syntax:
# IFS settings used for string parsing and auto-fixing DOS line endings.
# Whitespace == :Space:Tab:Line Feed:Carriage Return:
WSP_IFS=`printf "\x20\x09\x0A\x0D"`
# No Whitespace == :Line Feed:Carriage Return:
NO_WSP=`printf "\x0A\x0D"`
# Whitespace == :Space:Tab:
WSP=`printf "\x20\x09"`
# Quote == :Double Quote:
QUOTE=`printf "\x22"`

SPACE=' '
LT='<'
GT='>'

# usage: script_full_path [-p]
script_full_path () {
  # no need to worry about symlinks are they aren't allowed in /mnt/us
  local pth=$(2>/dev/null cd "${0%/*}" >&2; pwd -P)
  [[ "-p" = "$1" ]] || pth=$pth/${0##*/}
  echo -n "$pth" 
}

# usage: result=`str_replacechars SRC CHARS CHR`
# replace all occurrences of characters of CHARS in SRC with character CHR
str_replacechars () {
  local - IFS src=$1 chars=$2 chr=$3
  set -f
  IFS="$chars"
  set -- $src
  IFS="$chr"
  echo -n "$*"
}

#BBVER=`busybox_version`
#[[ 0 = $? ]] || exit 1
#log running K$BBVER busybox binary
alias sort='/bin/busybox sort' # GNU sort needs setting LC_ALL to work the same
alias find='/bin/busybox find' # why not

# source model-specific compatibility layer. Caveat: ash forgets function
# definitions sourced from within functions, so don't
#load=`script_full_path -p`/compat-K$BBVER.sh
#. "$load"
# NOW SOURCED INLINE...########################################################
#
# compat-K3.sh - SOURCED INLINE - last update 20130127,a,stepk
# Compatibility layer for busybox ash version
#   BusyBox v1.7.2 (2012-09-01 14:15:22 PDT) multi-call binary.
#
# json_var creates sh variable json_NAME from $1
json_var () {
  local IFS=${NO_WSP} x=$*
#echo "json_var_1($#)($x)"
  x=json_${x#?}
  x=`echo -n "$x" | sed "s/${QUOTE}//; s/[${WSP}]\+:/:/; s/:[${WSP}]\+/:/; s/:/=/;"`
#echo "json_var_2 x($x)"
  eval $x
}
#
#
# sanitize and shorten labels, improve readability
sanitize() {
  local r=$*
  r=`echo -n "$r" | sed "s/[\|${SEPARATOR}]//g; s/[:;]/ /g; s/[${WSP}]+/ /;"`
  echo -n "$r"
}
###################################################################################



# json_oline parses $1, a json object consisting of key/value pairs on a single
# line, like {"id":"value",...} 
json_oline () {
  local IFS implode prev s x v line=$1
    unset implode prev s
    line=${line#[\{\[]} # ltrim { and [ - [ isn't valid json but I've seen this typo in helper/menu.json
    until [[ "$s" = "$line" ]]; do
      s=$line
      line=${s%%[${WSP}\}\],]} # rtrim
    done
    # process comma-separated list of key/value pairs
    IFS=,
    for x in $line; do
      x=${x## } # cases '"id":"v"' / '"id":"v1' / 'v2"' (last 2 for "id":"v1,v2")
#echo "X($x)"
      case "$x" in
      ${QUOTE}*)
        [[ "$implode" ]] && json_var $implode && unset implode
        [[ "$prev" ]] && json_var $prev
        prev=$x
#echo "PREV($x)"
      ;;
      *)
        implode=${implode}${implode:+,}$x
#echo "IMP($implode)"
      ;;
      esac
    done
    if [[ "$prev" -a "$implode" ]]; then
#echo "FINPREVIMP($prev,$implode)"
      json_var $prev,$implode
    elif [[ "$implode" ]]; then
#echo "FINIMP($implode)"
      json_var $implode
    elif [[ "$prev" ]]; then
#echo "FINPREV($prev)"
      json_var $prev
    fi
}

# usage: json_parse /path/to/menu.json [PROC]
# stdout: PROC's formatted menu items
# return: # of successful PROC calls
# Note: unset variables json_* before calling json_parse
# For each input line that matches "action" this function creates a set of
# sh variables named json_N1, json_N2, ... where N1, N2, etc. are json key
# names.
# And for input line that matches "name" but not "action" it creates sh
# variable json_name_ (mind the dangling underscore), which is the top level
# menu name.
# Finally it calls function PROC, which outputs a formatted combination of
# of json_* (and previously-defined) xml_* variables to stdout.
json_parse () {
  local IFS=${WSP_IFS} line menu=$1 proc=$2 count=0
  shift 2     
  while read line; do
    line=${line##[${SPC}]}
    line=${line%%[${SPC}]}
    case $line in
    *"action"*)
      IFS=${NO_WSP}
      json_oline $line
      IFS=${WSP_IFS}
      # at this point any nice json file has already entered "name" below, so
      # we can call $proc with all variables defined
      $proc $* && count=$((++count))
    ;;
    # "name" must follow "action", it's the top menu name
    *"name"*)
      IFS=${NO_WSP}
      json_oline "{${line%,}}"
      IFS=${WSP_IFS}
      json_name_=$json_name
    ;;          
    esac
done < $menu
return $count
}

# usage: xml_var /path/to/config.xml NAME [NAME ...]
# xml_var creates one or more variables xml_NAME from an extension's config.xml
# file - the file must include tag "<extension>". Example:
#  xml_var config.xml author menu => xml_author(Mad Hatter) xml_menu(menu.json)
# Note: unset variables xml_NAME before calling xml_var
# Limitations:
# . opening and closing XML tags must be on the same line
# . XML value must not include double quotes
xml_var () {
  local line xml=$1 valid=0
  shift
  while read line; do
    case $line in
    *"<extension>"*) valid=1 ;; # it's an extension's xml file
    *) for v in $*; do
         case $line in
         *${LT}$v${GT}* | *${LT}$v${SPACE}*)
           line=${line#*${LT}$v}
           line=${line#*${GT}}
           line=${line%${LT}/$v${GT}*}
           [[ 1 = $valid ]] && eval "xml_$v=\$(printf %s \"$line\")"
           break
         ;;
         esac
       done
    ;;
    esac
  done < $xml
}

# dump xml_* and json_* variables
debug_info () {
#available in genuine bash only, prints all xml_* and json_* variables
#      local v
#      echo -n "${0##*/} parsed:"
#      for v in ${!xml_*};  do echo -n " $v(${!v})"; done
#      for v in ${!json_*}; do echo -n " $v(${!v})"; done
#      echo
  
#ash: variable names are hardwired
#The following variables are available; $1 is the extension's dir fullpath
 echo -n "path($1)"
 echo -n " xml_name($xml_name) json_name_($json_name_)"
 echo -n " json_name($json_name) json_action($json_action) json_params($json_params) json_priority($json_priority)"
 echo
}

# default_output displays json_name,action' 'json_params
default_output () {
  local label=$json_name apath=$json_action group
  # fully qualify action path
  [[ -e "$1/$json_action" ]] && apath=$1/$json_action
  # top level menu name
  label=`sanitize $label`
  
  echo "$label$SEPARATOR$apath $json_params"
}

# touch_runner displays action,json_params,group'.'json_name (separator ';')
touch_runner () {
  local label=$json_name apath=$json_action group
  # top level menu name
  [[ "${json_name_}" ]] && group=${json_name_} || group=${xml_name}
  # qualify label
  label=`str_replacechars "$group" '.' '_'` # was ${group//.}.$label
  label=`sanitize $label`
  
  echo "$1$SEPARATOR$apath$SEPARATOR${json_params:-NULL}$SEPARATOR$label"
}

# two_level displays cindex,group,json_name,action' 'json_params
two_level () {
  local label=$json_name apath=$json_action group
  # top level menu name
  [[ "${json_name_}" ]] && group=${json_name_} || group=${xml_name}
  # fully qualify action path
  [[ -e "$1/$json_action" ]] && apath=$1/$json_action
  label=`sanitize $label`
  group=`sanitize $group`
 
  echo "$group$SEPARATOR$label$SEPARATOR$apath $json_params"
}

# prepend cyclical color index when -f=twolevel
colorize () {
  # global COLORMAX SEPARATOR
  local IFS=${NO_WSP} cindex=-1 cstate='' line group
  while read line; do
    IFS=${SEPARATOR} ; set $line ; group=$1 ; IFS=${NO_WSP}
    if [[ "$cstate" != "$group" ]]; then
      cstate=$group
      cindex=$(( ($cindex + 1) % $COLORMAX ))
    fi
    echo "$cindex$SEPARATOR$line"
  done
}

# usage loop [ignorecount]
# find and process all config.xml files and their corresponding json menu files
loop () {
local f px pj nj count=0 ignorecount=0 t
case $1 in
  ignorecount) ignorecount=1 ;;
esac
for f in $(find $EXTENSIONDIR -name config.xml); do
  unset xml_name xml_menu
  xml_var $f name menu
#echo "xml_name($xml_name) xml_menu($xml_menu)"
  [[ "$xml_menu" ]] || continue # not an extension's config.xml file
  case "${xml_menu##*.}" in
    json) ;; # ok
    *) continue ;; # don't know how to handle this menu type
  esac
  px=${f%/*} # px path to config.xml
  # pj path to json menu
  case "$xml_menu" in
    /*) pj=${xml_menu%/*}
    ;;
    *) # is relative
       pj=$px/${xml_menu}
       pj=${pj%/*}
    ;;
  esac
  nj=${xml_menu##*/} # nj json menu filename
  if [[ -f $pj/$nj ]]; then
    unset json_name json_name_ json_action json_params json_priority
    json_parse $pj/$nj $proc $pj
    count=$(( $? + $count ))
  fi
done
# when extensions dir is empty
[[ 00 = $count$ignorecount ]] && test_applet install && loop ignorecount
return 0
}

# usage" test_applet install|uninstall
# adds/removes a simple test applet in $EXTENSIONDIR
# Installing clears and recreates an existing installation of the test applet
# return: non-zero on creation error
test_applet () {
  local prnm=`str_replacechars "$PRODUCTNAME" "${WSP}" '_'`
  local dir=$EXTENSIONDIR/$prnm
  local sh="$dir/test.sh" xml="$dir/config.xml" json="$dir/menu.json"
  [[ -d "$dir" ]] && rm -f "$sh" "$xml" "$json" && rmdir "$dir"
  case "$1" in
  uninstall)
    if [[ -d "$dir" ]]; then
      echo >&2 "${0##*/}: can't uninstall test applet"
      return 1
    fi
    log "test applet uninstalled"
  ;;
  install)
    mkdir -p "$dir"
    if ! { [[ -d "$dir" ]] \
    && echo "<?xml version="1.0" encoding="UTF-8"?>
<extension>
    <information>
        <name>$PRODUCTNAME</name>
        <version>1.0</version>
        <author>stepk</author>
        <id>Test $PRODUCTNAME</id>
    </information>
    <menus>
        <menu type=\"json\">menu.json</menu>
    </menus>
</extension>" > "$xml" \
    && echo "{
\"items\": [
    {
        \"name\": \"$PRODUCTNAME\",
        \"priority\": 1,
        \"items\": [
            {\"name\": \"Test $PRODUCTNAME\", \"priority\": 0, \"action\": \"test.sh\"}
        ]
    }
]
}" > "$json" \
    && echo "#/bin/sh -
exec /usr/bin/lipc-set-prop com.lab126.appmgrd start app://com.lab126.booklet.settings?diagnosticMode=\;411
" > "$sh" \
    && chmod +x "$sh" && log "test applet installed"
    }
    then
      echo >&2 "${0##*/}: can't install test applet"
      return 1
    fi
  ;;
  esac
  return 0
}

# main
main () {
# global opt_format opt_sort
local opt proc pipe t
# parse script options
# Note: both long AND short options require = to set option values
for opt in $*; do
  case $opt in
    -c=*|--colors=*) opt=${opt#*=}
       case $opt in [0-9]|[0-9][0-9]|[0-9][0-9][0-9]) COLORMAX=$(($opt)) ;; esac ;;
      -f=*|--format=*) opt_format=${opt#*=} ;;
    -h|--help) usage; exit ;;
    -l|--log) ;; # pre-parsed near top of file
    -s|--sort) opt_sort=label ;;
    *)
      echo >&2 "${0##*/}: unknown option $opt"
      exit 1
    ;;
  esac
done

case "$opt_format" in
  touchrunner) proc=touch_runner; SEPARATOR=';' ;;
  twolevel) proc=two_level; COLORIDX=-1 ; COLORSTATE="" ;;
  debuginfo) proc=debug_info ;;
  default|'') proc=default_output ;;
  *) echo 2>&1 ${0##*/}: unknown format \"$opt_format\"; usage; exit 1 ;;
esac

pipe=loop

if [[ "$opt_sort" = label ]]; then
  case $proc in
    default_output) pipe="$pipe | sort -f -s" ;;
    touch_runner) pipe="$pipe | sort -f -t \"\$SEPARATOR\" -k 4,4 -s" ;;
    two_level) [[ 0 -lt $COLORMAX ]] && t=' | colorize' || t=''
      pipe="$pipe | sort -f -t \"\$SEPARATOR\" -k 1,1 -s$t"
       ;;
  esac
else
  case $proc in
    two_level) [[ 0 -lt $COLORMAX ]] && t=' | colorize' || t=''
      pipe="$pipe$t"
       ;;
  esac
fi

test_applet uninstall
log "$pipe"
eval $pipe
log "exit($?)"
}

main $*

That would work

Last edited by twobob; 01-27-2013 at 10:57 PM.
twobob is offline   Reply With Quote