суббота, 27 февраля 2016 г.

Perforce scripts

This is useful Perforce old my scripts-helpers.

Common utils:

# Returns number of changelists, prints list of changelists
find_p4cl() {
    p4 changes -u $USER -s pending|awk '
        BEGIN { found=0 }
        /^Change / {
            p4cl[found, 0] = $2
            p4cl[found, 1] = $4
            w = match($0, /\*pending\* /)
            if (w) d = substr($0, w + length("*pending* "))
            else d = ""
            p4cl[found, 2] = d
            found++
        }
        END {
            if (found) {
                print "Available ChangeLists:"
                print "======================"
                for (i=0; i<found; i++) {
                    printf "  %s on %s: %s...\n",
                        p4cl[i, 0],
                        p4cl[i, 1],
                        p4cl[i, 2]
                }
            }
            exit found
        }
    '
}

# Input from user changelist (show default value if it one only).
# If no input, returns default
read_p4cl() {
    local changelists _p4cl p4clhint
    changelists=`find_p4cl`
    if [ $? -eq 1 ]; then
        _p4cl=`echo "$changelists"|awk 'NR==3{print $1}'`
        p4clhint=" [${_p4cl}]"
    else
        _p4cl=""
        p4clhint=""
    fi
    echo "$changelists"
    echo
    echo -n "Enter P4 ChangeList number${p4clhint}: "; read p4cl; : ${p4cl:=$_p4cl}
}

# if not Perforce workspace then report it
check_p4ws() {
    CF=${P4CONFIG:-.p4config}
    [ -f "$CF" ] || { echo 'Seems not P4 workspace!' >&2; return 1; }
    return 0
}

List of midified files without switch it to "Edit" mode (devs often forget to do it :)

#!/bin/sh
# List of locally modified files without put them into ChangeList

. .p4utils
check_p4ws || exit 1
p4 reconcile -e -n

Review sedning:

#!/bin/sh
# Send ChangeList review request

. .p4utils
check_p4ws || exit 1
read_p4cl
echo "Posting review now. Wait, please..."
post-review $p4cl

Simplest config file parser in the world!

Nothing then split/strip/filter "combinators":

cfg = '''
    a   = aa aaa = aaaa
b   = bbb
        c=cccccccccc


'''
split_pair = lambda s: s.split('=', 1)
strip_pair = lambda p: map(str.strip, p)
parse_rc = lambda s: map(strip_pair, map(split_pair, filter(None, s.splitlines())))
print dict(parse_rc(cfg))  # print lik in Python 2x

Logging library for Unix Shell

This library is similar to Log library for Python or Java. Here its code:

#!/bin/sh

# Simple logging facility for Unix /bin/sh
# ========================================
#
# Configuration
# -------------
#
#   _LOGGING_DATEFMT, _LOGGING_FMT variables are used for format of datetime
#   and log record
#
#   * _LOGGING_DATEFMT : uses the same format as date(1) and Python Lib
#
#   * _LOGGING_FMT : uses format similar to Python logging Lib
#
#       Available fields for _LOGGING_FMT are:
#         asctime, filename, levelname, levelno, message, name, pathname,
#         process, processName, user
#
# Example
# -------
#
#     getLogger prg1
#     getLogger prg2
#     
#     addLogHandler prg1 FileHandler /dev/stderr
#     addLogHandler prg2 ConsoleHandler
#     addLogHandler prg2 FileHandler /var/log/somefile
#     addLogHandler prg2 FileHandler /var/log/somefile_copy
#
#     setLogLevel prg1 DEBUG
#
#     prg1_debug DebugMessage
#     prg1_error ErrorMessage
#     echo InfoMessage|prg2_info
#
#   will produces on STDERR console:
#     ...
#     2014-07-18T17:09:52+0300 prg1 DEBUG DebugMessage
#     ...
#
#   will produces on console:
#     ...
#     2014-07-18T17:09:52+0300 prg1 ERROR ErrorMessage
#     2014-07-18T17:09:52+0300 prg2 INFO InfoMessage
#     ...
#
#   and in /var/log/somefile:
#     ...
#     2014-07-18T17:09:52+0300 prg2 INFO InfoMessage
#     ...
#
#   and in /var/log/somefile_copy:
#     ...
#     2014-07-18T17:09:52+0300 prg2 INFO InfoMessage
#     ...

# TODO: log levels per handlers


############################## Settings #####################################

# Global log record format and date format:
_LOGGING_DATEFMT="%Y-%m-%dT%H:%M:%S%z"
_LOGGING_FMT="%asctime %name %levelname %message"



################################### Code #####################################

_log_SCRIPT=`readlink -f $0`

_log_getLogAttr() {
  name=$1; attr=${1}_$2
  eval echo $`echo $attr`
}

################################### Handlers #################################

# SYNOPSIS: ...
#
# Nothing to do, all args are ignored
_log_NullHandler() {
  :
}

# SYNOPSIS: $name $level $msg
#
# Logs $msg with $level via logger(1) utility
_log_SyslogHandler() {
  local name level msg syslog_level
  name="$1"; level="$2"; msg="$3"
  syslog_level=`_log_syslogLevel $level`
  logger -p "local0.${syslog_level}" -t $name "$msg"
}

# SYNOPSIS: $name $level $msg
#
# Logs message $msg on console
_log_ConsoleHandler() {
  local name level msg
  name="$1"; level="$2"; msg="$3"
  echo "$msg"
}

# SYNOPSIS: $subject $address $msg
#
# If mail(1) exists, call it to send $msg via EMail
_log_MailHandler() {
  command -v mail >/dev/null 2>&1 || return 1
  local subj addr name level msg
  subj="$1"; addr="$2"; name="$3"; level="$4"; msg="$5"
  echo "$msg"|mail -s "${subj}${level}: $name" $addr
}

# SYNOPSIS: $filename $msg
#
# Append message $msg to file $filename
_log_FileHandler() {
  local filename name level msg
  filename="$1"; name="$2"; level="$3"; msg="$4"
  touch "$filename"
  echo "$msg" >> "$filename"
}

_log_syslogLevel() {
  case $1 in
    CRITICAL)
      echo crit;;
    FATAL)
      echo panic;;
    ERROR)
      echo error;;
    WARNING)
      echo warning;;
    INFO)
      echo info;;
    DEBUG)
      echo debug;;
    *)
      echo notice;;
  esac
}

_log_levelNo() {
  case $1 in
    CRITICAL|FATAL)
      echo 50;;
    ERROR)
      echo 40;;
    WARNING)
      echo 30;;
    INFO)
      echo 20;;
    DEBUG)
      echo 10;;
    *)
      echo 0;;
  esac
}

_log() {
  local name level msg handlers h closure_args skip no_closure_args levelno enabled_levelno
  name="$1"; level="$2"; msg="$3"

  levelno=`_log_levelNo $level`
  enabled_levelno=`_log_getLogAttr $name level`
  [ $levelno -lt $enabled_levelno ] && return

  handlers=`_log_getLogAttr $name handlers`
  handlers=`echo "$handlers"|sed 's/__COMMA__/\\ /g'`
  for h in $handlers; do
    echo $skip|grep $h >/dev/null && continue
    closure_args=`_log_getLogAttr $name ${h}_handler_args`
    closure_args=`echo "$closure_args"|sed 's/__COMMA__/\\ /g'`
    no_closure_args=1
    for ca in $closure_args; do
      eval "$h $ca $name $level \"$msg\""
      no_closure_args=0
    done
    [ $no_closure_args ] && eval "$h $name $level \"$msg\""
    skip="$h $skip"
  done
}

# SYNOPSIS: $name $h [$args...]
#
# Adds handler $h for logger $name with possible arguments
# (see handlers' implementations for concrete args)
addLogHandler() {
  local name handler oldhandlers exthandler args oldargs
  name=$1; exthandler="$2"; handler=_log_"$exthandler"; shift 2; args="$*"
  oldhandlers=`_log_getLogAttr $name handlers`
  oldargs=`_log_getLogAttr $name ${handler}_handler_args`
  # __COMMA__ is used to separate items in lists
  eval "${name}_handlers=${handler}__COMMA__${oldhandlers:-_log_NullHandler}"
  eval "${name}_${handler}_handler_args=\"$args\"__COMMA__$oldargs"
}

# SYNOPSIS: $name | $name $longname
#
# No return and no output but setups logger for this $name. In 2nd form $longname
# is used in message substitution
getLogger() {
  local asctime filename levelname levelno message name pathname process processName _lv user longname

  asctime=$_LOGGING_DATEFMT
  filename=`getScriptName`
  message='${1:-`tee`}'
  name="$1"
  longname=${2:-$name}
  pathname=`echo "$_log_SCRIPT"|sed 's/\\//\\\\\//g'`
  process='$$'
  processName=`ps -A -opid,comm | awk -v PID=$$ '$1 == PID { print $2 }'`
  processName=`echo "$processName"|sed 's/\\//\\\\\//g'`
  user="$USER"

  for _lv in debug info warning error fatal critical; do
    levelname=`echo -n $_lv|tr '[:lower:]' '[:upper:]'`
    levelno=`_log_levelNo $levelname`

    fmt=`echo $_LOGGING_FMT | \
      sed 's/%asctime/\`date +$_LOGGING_DATEFMT\`/g' | \
      sed "s/%filename/$filename/g" | \
      sed "s/%levelname/$levelname/g" | \
      sed "s/%levelno/$levelno/g" | \
      sed "s/%message/$message/g" | \
      sed "s/%pathname/$pathname/g" | \
      sed "s/%processName/$processName/g" | \
      sed "s/%process/$process/g" | \
      sed "s/%user/$user/g" | \
      sed "s/%name/$longname/g"`
    eval "${name}_${_lv} () { _log $name $levelname \"$fmt\"; }"
  done
  setLogLevel $name INFO
}

# SYNOPSIS: $name $level
#
# Sets the threshold for $name logger to $level (INFO, ERROR, etc.). Logging
# messages which are less severe than $level will be ignored.
# Default is INFO
setLogLevel() {
  local name level levelno
  name="$1"; level=`echo -n $2|tr '[:lower:]' '[:upper:]'`
  levelno=`_log_levelNo $level`
  eval "${name}_level=$levelno"
}

# SYNOPSIS:
#
# Echos name of script
getScriptName() {
  echo `basename "$_log_SCRIPT"`
}

Example of usage:

#!/bin/sh
. ./logging.sh

getLogger prg1 `getScriptName`
addLogHandler prg1 ConsoleHandler
addLogHandler prg1 FileHandler ./lll1.log
addLogHandler prg1 FileHandler ./lll2.log
setLogLevel prg1 DEBUG

prg1_debug Debugggg
prg1_error Errrrror
echo 'Message-INFO!!!!
1111
2222'|prg1_info

воскресенье, 21 февраля 2016 г.

HFit - fitting algorithms in Haskell

It's very interesting class of tasks - fitting of some figure using several input points with noise. The simplest method is Least Squares Algorithm. Idea of algorithm is to find a and b of line equation a*x + b using sum's minimization of difference's squares: ∑(yi - (a*xi + b))2. Result is 2 formulas:

$$ a=\frac{n \sum\limits_{i=1}^n x_i y_i - \sum\limits_{i=1}^n x_i \sum\limits_{i=1}^n y_i} {n \sum\limits_{i=1}^n x_i^2 - ({\sum\limits_{i=1}^n x_i})^2} $$

$$ b=\frac{\sum\limits_{i=1}^n y_i - a \sum\limits_{i=1}^n x_i} {n}$$

And when we know a and b we can generates (x0, y0) (x1, y1) points to create line...

Application initializes Tcl/Tk UI via sending Tcl code to wish interpreter:

...
  mainTcl <- readFile "ui.tcl"
  (Just si, Just so, Just sx, ph) <- createProcess (proc wish []) {
    std_in=CreatePipe, std_out=CreatePipe, std_err=CreatePipe, close_fds=False}
  hSetBuffering si NoBuffering
  hSetBuffering so NoBuffering
  hPutStrLn si mainTcl
...

Communication loop lives in separate process:

...
  forkIO $ ioLoop si so
  waitForProcess ph
  putStrLn "bye."
  where
    ioLoop si so = forever $ do
        resp <- (hGetLine so) `catchIOError` (\_ -> return "eof")
        case resp of
          "eof" -> exitSuccess
          _|resp `startsWith` "calc " ->
            putStrLn ("-> " ++ resp) >> 
            let res = calc resp in
            putStrLn ("<- " ++ res) >> hPutStrLn si res
          _ -> putStrLn resp >> return ()
...

and when new line is got from wish then it will be processed with calc function which gets request (here it's call resp from Wish, yep:) and parse coordinates like "1 2 3 4", ie. x0 y0 x1 y1 ... xn yn with parsePt (after splitting them into words):

...
-- Parse ["12", "34", "56", "78"] to [(12, 34), (56, 78)]
parsePt :: [String] -> [Pt]
parsePt [] = []
parsePt (k:v:t) = (read k, read v) : parsePt t
...

then done all calculation in Double domain. Result of calculation is Tcl command like "takeCalc x0 y0 x1 y1" with coordinates of found line 2 points:

    -> calc 119 285 194 235 379 64
    <- takeCalc 119  306  379  46

Wish will get this command and create line on canvas.

Actually it's initial state, so needs many improvement, adding new algorithms, etc...