среда, 21 сентября 2011 г.

Creating of Intel HEX with Tcl/Tk

Intel HEX format is the simple ASCII format usually used to program different devices (it's flash and EEPROM memories). To create such data records, I use this code:
package provide ihex 1.0

namespace eval ihex {
    namespace export data EOF eraser
    set EOF ":00000001FF"

    proc verint {type value} {
        # Verify that value is in the type range. Known types are b (byte),
        # w (word), d (doubleword), q (quadword). If is out-of-range, then
        # raise will be callen
        set absvalue [::tcl::mathfunc::abs $value]

        proc raise {t v} {
            # Raise error message. t is type name, such as "word" or "quadword"
            error [format "Value 0x%X is not a %s" $v $t]
        }

        switch -- $type {
            b { if {$absvalue > 255} {raise byte $absvalue} }
            w { if {$absvalue > 65536} {raise word $absvalue} }
            d { if {$absvalue > 4294967296} {raise doubleword $absvalue} }
            q { if {$absvalue > 18446744073709551616} {raise quadword $absvalue} }
        }
        return 1
    }

    proc args2bytes {args} {
        # Converts args to list of bytes (binary form of args). See
        # >>> args2bytes b1 w4A
        # 01 00 4A
        #
        # Each arg is integer prefixed by b|w|d|q and is in hex-form.
        set bytes [list]
        foreach arg $args {
            set r [scan $arg %1s%x argtype argvalue]
            if {$r != 2} {
                #puts "some string..."
                continue
            } else {
                # verify value of argvalue (in type bounds)
                verint $argtype $argvalue
                # format argvalue into string
                switch -- $argtype {
                    b {set fmt %02X}
                    w {set fmt %04X}
                    d {set fmt %08X}
                    q {set fmt %016X}
                }
                set sarg [format $fmt $argvalue]
                set argbytes [regexp -all -inline .. $sarg]
                set bytes [concat $bytes $argbytes]
            }
        }
        return $bytes
    }

    proc crc {bytes} {
        # Calculate Intel-hex CRC for list of bytes (usual numbers in hex format
        # but without prefixes like b|w|d|q)
        set sum 0
        foreach b $bytes {
            incr sum 0x$b
        }
        set sum [expr {(1 + ~$sum) & 0xFF}] ;# bcb
        return $sum
    }

    proc data {address args} {
        # Returns data record for Intel hex format. args are list of integers such
        # b23 wAE... - byte 0x23, word 0x00AE
        set bytes [args2bytes {*}$args]
        set len [args2bytes [format b%X [llength $bytes]]]
        set address [args2bytes $address]
        set type 00
        set cc [crc [concat {} $len $address $type $bytes]]

        # Prepare output line
        set head :$len[join $address ""]$type
        set tail [join $bytes ""][format %02X $cc]
        return $head$tail
    }

    proc join_eep {args} {
        # Join eep data records (":LLAAAA00..." strings) into one data record with size <= 256.
        # Does not check CRC of input records!
        set MAXSZ 256 ; # DONT USE > 256!!!
        # In the mem will be 256 "00 00 00..."
        for {set i 0} {$i < $MAXSZ} {incr i} { lappend mem 00 }
        set last_index 0
        foreach rec $args {
            set len [string range $rec 1 2]
            set address [string range $rec 3 6]
            set type [string range $rec 7 8]
            if {"0x$type" != 0} { continue } ;# skip not data records
            set bytes [string range $rec 9 end-2]
            set bytes [regexp -all -inline .. $bytes]
            #set ev [eval "expr {0x$address + 0x$len - 1}"]
            set from "0x$address"; set to [expr {"0x$address" + "0x$len" - 1}]
            set last_index [::tcl::mathfunc::max $last_index $to]
            set replacing "lreplace \$mem $from $to $bytes"
            set mem [eval $replacing]
        }
        set len [format b%X [expr {$last_index + 1}]]
        set len [args2bytes $len]
        set address "00 00"
        set type 00
        set mem [lrange $mem 0 $last_index]
        set cc [crc [concat {} $len $address $type $mem]]

        # Prepare output line
        set head :$len[join $address ""]$type
        set tail [join $mem ""][format %02X $cc]
        return $head$tail
    }
}
Procedure ihex::data is for create one record (ASCII line) from it's arguments as:
ihex::data b23 w0A
where each argument is value prefixed by b (for byte), w (for word), d (for doubleword), q (for quadword). Procedure ihex::join_eep is used to "join" into one memory 256-bytes chunk several records. It's arguments are in form of ":LLAAAATTDD...DCC". Procedure args2bytes is not exported but may be usable for creation bytes dump of it's arguments (also used b, w, d, q prefixes).

Changing icons in Windows Tcl Starpacks

Created Starpack will have default Tcl icons and you can see them in different view modes of Windows explorer (table, large icons...). This approach is based on http://wiki.tcl.tk/10922 receipt, but is in Makefile target form. And uses tools: reshacker.exe,  upx.exe and windres.exe from MinGW installation (at $MINGWHOME/bin/windres.exe), also supposes that you are using tclkit.exe as runtime. In my case I have application rfprg and "virtual" FS (catalog) - rfprg.vfs. And I have all these tools in the same catalog (with tclkit.exe). I created there the rfprg.rc file:
APPICONS ICON "rfprg.vfs/tclkit.ico"
TK ICON "rfprg.vfs/tclkit.ico"
Then I added next target into my Makefile:
icons:
	cp tclkit.exe tclkit1.exe
	upx.exe -d tclkit1.exe
	reshacker.exe -delete tclkit1.exe , tclkit2.exe , icongroup,,
	windres.exe -i rfprg.rc -o rfprg.res -J rc -O res -r
	reshacker.exe -add tclkit2.exe , tclkit3.exe , rfprg.res , ,,
	upx.exe tclkit3.exe
	mv tclkit3.exe tclkit.exe
	rm rfprg.res tclkit2.exe tclkit1.exe
And them:
make icons
This will replace "system" icons of tclkit.exe with rfprg.vfs/tclkit.ico. So you can use this "patched" tclkit.exe runtime to create Starpack with your custom icon.

понедельник, 29 августа 2011 г.

Bottle application helper

Next is the helper class for creation of the Bottle Web Framework applications:
def expose(path, **kw):
    def decor(view):
        view._route_path_ = path
        view._kwargs_ = kw
        return view
    return decor

class BottleBaseApp(object):
    """
    - Autosave attributes with names from __session__ attribute
    - Session type is attr __session_type__ ('cookie'|'pickle'),
      'cookie' is default
    - Methods decorated with @expose will be auto-routed and
      sessions' attrs will be flushed at the end
    """
    def __init__(self, bottleapp):
        self.bottle = bottleapp
        # Prepare session manager as attr sessions
        if self.__session_type__ == "pickle":
            self.sessions = PickleSession()
        else:
            self.sessions = CookieSession()

        def router(meth):
            """Return wrapped view-method"""
            # meth is already bound to self
            def wrp(f=meth, *a, **kw):
                try:
                    ret = f(*a, **kw) # bound, no needs self,
                finally:
                    self.flush_session()
                return ret
            functools.update_wrapper(wrp, meth)
            return wrp

        # Route all decorated methods
        for attrname in dir(self):
            attr = getattr(self, attrname)
            path = getattr(attr, "_route_path_", None)
            kw = getattr(attr, "_kwargs_", {})
            if path:
                self.bottle.route(path, callback=router(attr), **kw)

    def flush_session(self):
        """Copy to session all attrs and save session"""
        try:
            ses = self.sessions.get_session()
            for a in self.__session__:
                try:
                    v = getattr(self, a)
                    ses[a] = v
                except:
                    continue
            self.sessions.save(ses)
        except Exception as x:
            pass
And now is the example how to use this:
class MyApp(BottleBaseApp):
    __session_type__ = "cookie"
    __session__ = ["tasks_ctl_res", "tasks_prc", "prc_conns",]

    def __init__(self, bottleapp):
        super(MyApp, self).__init__(bottleapp)
        self.tasks_ctl_res = {}
        self.tasks_prc = []
        self.prc_conns = {}


    @expose("/index")
    def index(self):
        return "Index page is empty"

    # ... etc ...
I use Session implementation by Sean Reifschneider - MANY THANKS! - (see this).

воскресенье, 21 августа 2011 г.

Internationalization in the Bottle

This approach is very limited! In serious sites you have to support Web-robots, links with language code... I use it for small (embedded Web console) projects only.

I18N in the Bottle Python framework is easy. I prefer to do i18n for 2 different tasks:
  1. i18n for messages (strings)
  2. i18n for static text big pies (in the templates)
I use gettext mechanism. In my application folder I have folders:
locale/
    en/
      TEMPLATES/
        xxx.tpl
    bg/
      LC_MESSAGES/
        messages.mo
      TEMPLATES/
        xxx.tpl
    xx/
      LC_MESSAGES/
        messages.mo
      TEMPLATES/
        xxx.tpl
bg_messages.po
xx_messages.po

"locale" is the folder for i18n/i10n. Folders with language codes ("bg", "xx") are placed here. Each of them contents two subfolders: LC_MESSAGES with compiled .po file (messages.mo) and TEMPLAES (with .tpl files), except en/ folder: no LC_MESSAGES here.

I use make-like mechanism for compilation .po-files, adding new locale - Python fabricate build tool. It's very lightweight and is written in single Python file!

.po-files are very simple. I changed next strings in the one:
"Content-Type: text/plain; charset=utf8\n"
"Content-Transfer-Encoding: utf8\n"
and encoded it into utf-8 (using vim, for example). Then added my msgid, msgstr pairs, as usual.

So, my Bottle application have to know current language (preferred by user) and how to: a) found corresponding template b) how to translate word/text.
# to test, change language in the browser (see priority of them!). Then
# refresh page - you should see text in different language
_LANGS = {}
def initi18n():
    """Init internationalization of UI
    """
    import __builtin__
    global _LANGS
    for l in ("bg","ru"): # list of supported language
        trans = gettext.translation(I18N_DOMAIN, LOCALEDIR, languages=[l])
        _LANGS[l] = trans
    # default translator (i.e. _())
    __builtin__.__dict__["_"] = unicode


def request_lang(req):
    """Determine preferred language of the user from WSGI environment
    variable HTTP_ACCEPT_LANGUAGE. Default is 'en'
    """
    # if cfg module overrides user language
    if cfg.LANG != "default":
        return cfg.LANG

    try:
        return req.environ.get("HTTP_ACCEPT_LANGUAGE", "").split(";")[0].split("-")[0]
    except:
        return "en"


def switch_lang(lang):
    """Select language for output VIA GETTEXT
    """
    import __builtin__
    trans = _LANGS.get(lang, None)
    if trans:
        trans.install(unicode=True)
    else:
        __builtin__.__dict__["_"] = unicode


def templatei18n(name, lang=None, **kw):
    """Like bottle template() but render for current language
    from os.environ["LANGUAGE"]. And not like bottle's template,
    doesn't support source, only name (not path!) as 1st arg!
    If no lang, it will be selected from request
    """
    if not lang:
        lang = request_lang(request)
    tplpath = "%s/%s/TEMPLATES/%s" % (LOCALEDIR, lang, name)
    tplpath = os.path.abspath(tplpath) + ".tpl"
    if not os.path.exists(tplpath):
        tplpath = name
    return template(tplpath, **kw)


def i18n(templ):
    """Decorator for template rendering using current
    language (i18n support)
    """
    def decor(view):
        @functools.wraps(view)
        def w(*a, **kw):
            lang = request_lang(request)
            switch_lang(lang)
            d = view(*a, **kw)
            return templatei18n(templ, lang=lang, **d)
        return w
    return decor

I use initi18n() before application starting and use @i18n decorator for localized templates. Nothing else:
@i18n("tasks") # name of the template
def tasks_list(self):
    """Show table of tasks
    """
    ps = tasks_info()
    for taskname in ps:
        msg = self.runres.get(taskname, "") or ps[taskname]["msg"]
        ps[taskname]["msg"] = msg
        self.runres[taskname] = ""
    return dict(ps=ps)
User should select preferred language in the Web browser options. You - create template "tasks.tpl" in all locale/*/TEMPLATES and translated messages in .mo-file to use _(..) function everywhere you need (Python code, templates).