2017-12-08

JSON Web Token and Base64Url

我在讀維基百科的條目也有跟著使用 Tcl 驗證,我卡住了一段時間,因為最後的 Token 跟我程式計算出來的不同,不過最後我發現是 base64 和 Base64Url 有一些小小的不同,最後在搜尋 Tcler's wiki 以後知道要怎麼做了,下面是測試程式:

package require sha256

proc base64url_encode {string} {
    tailcall string map {+ - / _ = {}} [binary encode base64 $string]
}

proc base64url_decode {string} {
    tailcall binary decode base64 [string map {- + _ /} $string]
}


set header {{"alg":"HS256","typ":"JWT"}}
set payload {{"loggedInAs":"admin","iat":1422779638}}
set key {secretkey}

set unsignedToken "[base64url_encode $header].[base64url_encode $payload]"
set signature [base64url_encode [::sha2::hmac -bin -key $key $unsignedToken]]
set token "$unsignedToken.$signature"
puts $token

所以按照參考資料,寫了一個簡單的修正,然後編碼就正確了。

小知識: base64 != base64Url

* 更新:加入 decode 的部份

2017-12-06

Apache Rivet: status code and content type

最近在學習 Vue.js,所以有做一些 server 端的測試。寫法大致上是這樣:

Vue.js + Axios  -> send POST request to server -> get response

也就是所謂的前後端分離。在這個情況下,server 端(例如 Apache HTTP server + Apache Rivet 的組合)就變成比較像 web API 的部份。

XML 的範例可以看 Apache Rivet 的手冊:
#
# Ajax query servelet: a pseudo database is built into the dictionary 'composers' with the
# purpose of emulating the role of a real data source. 
# The script answers with  2 types of responses: a catalog of the record ids and a database 
# entry matching a given rec_id. The script obviously misses the error handling and the
# likes. Just an example to see rivet sending xml data to a browser. The full Tcl, JavaScript
# and HTML code are available from http://people.apache.org/~mxmanghi/rivet-ajax.tar.gz

# This example requires Tcl8.5 or Tcl8.4 with package 'dict' 
# (http://pascal.scheffers.net/software/tclDict-8.5.2.tar.gz)
# 

# A pseudo database. rec_id matches a record in the db

set composers [dict create  \
                1 {first_name Claudio middle_name "" last_name Monteverdi   \
                    lifespan 1567-1643 era Renaissance/Baroque}             \
                2 {first_name Johann middle_name Sebastian last_name Bach   \
                    lifespan 1685-1750 era Baroque }                        \
                3 {first_name Ludwig middle_name "" last_name "van Beethoven" \
                    lifespan 1770-1827 era Classical/Romantic}              \
                4 {first_name Wolfgang middle_name Amadeus last_name Mozart \
                    lifespan 1756-1791 era Classical }                      \
                5 {first_name Robert middle_name "" last_name Schumann      \
                    lifespan 1810-1856 era Romantic} ]

# we use the 'load' argument in order to determine the type of query
#
# load=catalog:         we have to return a list of the names in the database
# load=composer&amp;res_id=<id>: the script is supposed to return the record
#               having <id> as record id

if {[::rivet::var exists load]} {

# the xml declaration is common to every message (error messages included)

    set xml "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
    switch [::rivet::var get load] {
        catalog {
            append xml "<catalog>\n"
            foreach nm [dict keys $composers] {
                set first_name  [dict get $composers $nm first_name]
                set middle_name [dict get $composers $nm middle_name]
                set last_name   [dict get $composers $nm last_name]
                append xml "    <composer key=\"$nm\">$first_name "
                if {[string length [string trim $middle_name]] > 0} {
                    append xml "$middle_name "
                }
                append xml "$last_name</composer>\n"
            }
            append xml "</catalog>\n"
        }
        composer {
            append xml "<composer>\n"
            if {[::rivet::var exists rec_id]} {
                set rec_id [::rivet::var get rec_id]
                if {[dict exists $composers $rec_id]} {
                    foreach {k v} [dict get $composers $rec_id] {
                        append xml "<$k>$v</$k>\n"
                    }
                }
            }
            append xml "</composer>\n"
        }
    }

# we have to tell the client this is an XML message. Failing to do so
# would result in an XMLResponse property set to null

    ::rivet::headers type "text/xml"
    ::rivet::headers add Content-Length [string length $xml]
    puts $xml
}

只要知道怎麼拿取 request 的資料跟如何回應,就可以組成 web API。下面是我快速驗證的 prototype:
<?
    package require rl_json
    package require tdbc::postgres

    ::rivet::load_response response

    if {[info exists response(sqlstring)]==1} {
        set sqlstring $response(sqlstring)
    }

    if {[info exists response(sqlstring)]==0 || [info exists sqlstring]==0} {
        ::rivet::headers  numeric 400
    } else {
        ::rivet::headers numeric 200
        ::rivet::headers type application/json

        tdbc::postgres::connection create db -user danilo -password danilo -port 5432 -database danilo

        set stmt [db prepare $sqlstring]
        set resultset [$stmt execute]

        set cols [$resultset columns]
        set param_colitem [list]
        foreach col $cols {
            lappend param_colitem "string \"$col\""
        }
        rl_json::json set data "column" [rl_json::json new "array" {*}$param_colitem]

        set param_rowitems [list]
        $resultset foreach -as lists result {
            set param_rowitem [list]
            lappend param_rowitem "array"
            foreach row $result {
                lappend param_rowitem "string \"$row\""
            }

            lappend param_rowitems $param_rowitem
        }

        $resultset close
        $stmt close
        db close

        set data [rl_json::json set data "row" [rl_json::json new "array" {*}$param_rowitems]]
        puts $data
    }
?>

Apache Rivet 有自己的 Database API,可是因為我只是要寫一個測試用的程式,所以直接使用 TDBC。這個範例是送出 SQL statement,然後 POST 到 server 上,server 執行以後回傳結果。

因為並沒有做使用者資料驗證和檢查,這個測試程式請不要在測試目的以外的地方使用,這只是用來說明,我們可以使用 Apache Rivet 用來實作 web api。

2017-11-26

NaviServer: enable gzip compression

參考連結:
How to enable gzip compression

NaviServer 在 nsd-config.tcl 中的 ns/server/${server} 加入下列的設定,動態網頁 ADP 的部份就可以支援 gzip compression:
ns_section     "ns/server/default"
   
# Compress response character data: ns_return, ADP etc.
#
ns_param        compressenable  on      ;# false, use "ns_conn compress" to override
ns_param        compresslevel   4       ;# 4, 1-9 where 9 is high compression, high overhead
ns_param        compressminsize 512     ;# Compress responses larger than this
# ns_param      compresspreinit true    ;# false, if true then initialize and allocate buffers at startup


一開始的時候我以為和 AOLServer 一樣,需要 nszlib module,但是我寫好了 RPM spec 並且安裝好了以後,才發現 NaviServer 已經整合了 zlib 的部份,所以在一開始 configure 的時候就需要 --with-zlib 設定才行(我是往前翻安裝的筆記並且參考連結以後才發現不用安裝 nszlib module)。

經過實測,NaviServer 只要針對 ADP 的部份進行 gzip compression,而靜態網頁看起來是沒有壓縮的(PS. 我只有加這篇文章的設定進行測試)。

2017-11-25

NaviServer: 設定 HTTPS (self-signed certificate)

nsssl module 是 NaviServer 用來處理 SSL 部份的 module。我目前使用的 NaviServer 4.99.15 已經整合 nsssl 的功能進入核心。

下面是參考 nsssl 文件上如何產生  self-signed certificate 與我之前其它 server 的設定,綜合之後的筆記。

Creating self-signed certificate,首先是建立 localhost.conf 設定檔案:
[req]
default_bits       = 2048
default_keyfile    = localhost.key
distinguished_name = req_distinguished_name
req_extensions     = req_ext
x509_extensions    = v3_ca

[req_distinguished_name]
countryName                 = Country Name (2 letter code)
countryName_default         = US
stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = New York
localityName                = Locality Name (eg, city)
localityName_default        = Rochester
organizationName            = Organization Name (eg, company)
organizationName_default    = localhost
organizationalUnitName      = organizationalunit
organizationalUnitName_default = Development
commonName                  = Common Name (e.g. server FQDN or YOUR name)
commonName_default          = localhost
commonName_max              = 64

[req_ext]
subjectAltName = @alt_names

[v3_ca]
subjectAltName = @alt_names

[alt_names]
DNS.1   = localhost
DNS.2   = 127.0.0.1

Run the following commands using OpenSSL to create a self-signed certificate in Linux or Mac OSX with OpenSSL :
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt -config localhost.conf -passin pass:YourSecurePassword

cat localhost.crt localhost.key > server.pem
rm -rf localhost.crt localhost.key
openssl dhparam 2048 >> server.pem

然後將 server.pem 複製到 /var/lib/naviserver/modules/nsssl 目錄下。
接下來設定 NaviServer 的部份(修改 conf 目錄下的 nsd-config.tcl,或者是你的設定檔):
#
# NSSSL
#

ns_section    ns/server/default/modules
ns_param      nsssl                nsssl.so

ns_section    ns/server/default/module/nsssl
ns_param      certificate      $home/modules/nsssl/server.pem
ns_param      address          127.0.0.1
ns_param      port             8081
ns_param      ciphers              "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!RC4"
ns_param      protocols            "!SSLv2:!SSLv3"
ns_param      verify                0

ns_param      extraheaders {
   Strict-Transport-Security "max-age=31536000; includeSubDomains"
   X-Frame-Options SAMEORIGIN
   X-Content-Type-Options nosniff
}

原本範例 port 是設定為 443,不過因為 1024 以下的 port 需要 root 權限,所以我改成 8081 來進行測試。

注意:default 要視你的設定而定,因為我是使用一開始的 nsd-config.tcl 來測試,所以 server name 是 default。

2017-11-24

Next Scripting Framework

Next Scripting Framework

NX is a highly flexible, Tcl-based, object-oriented scripting language. It is a descendant of XOTcl and was designed based on 10 years of experience with XOTcl in projects containing several hundred thousand lines of code.


上面是引述官網的介紹,也就是 NSF 是 XOTcl 的後繼者。在我的認知中,以前 Tcl 最主要提供物件導向支援的套件,一個是 Incr Tcl,一個就是 XOTcl。

我並不是 NSF 的使用者,不過有需要研究一下要怎麼安裝在 openSUSE 才對,然後我會試寫一個 RPM spec 來安裝。

因為 NaviServer 的 WebSocket 實作是以 NSF 寫的,所以如果我要知道目前 NaviServer 的實作情況,那麼先置條件是要裝 NSF。等裝完並且沒有問題以後,才能夠開始 NaviServer 的 WebSocket 功能測試。



配合維基百科上的 Handshake 說明:

用戶端請求
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

伺服器回應
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/


更新:
安裝 NSF 成功。
接下來執行 chat 範例成功(需要設定 host 為 localhost)。


更新 2017/11/25
我發現 revproxy module (Reverse Proxy module) 也是使用 NSF 寫的,所以 NaviServer 也有提供 Reverse Proxy 的功能,裝上 Reverse Proxy module 就好了。

tcl-lmdb v0.3.6

檔案放置網頁

tcl-lmdb - Tcl interface to the Lightning Memory-Mapped Database

About

This is the Lightning Memory-Mapped Database (LMDB) extension for Tcl using the Tcl Extension Architecture (TEA).

LMDB is a Btree-based database management library with an API similar to BerkeleyDB. The library is thread-aware and supports concurrent read/write access from multiple processes and threads. The DB structure is multi-versioned, and data pages use a copy-on-write strategy, which also provides resistance to corruption and eliminates the need for any recovery procedures. The database is exposed in a memory map, requiring no page cache layer of its own. This extension provides an easy to use interface for accessing LMDB database files from Tcl.

Main Change

  • Fix Segfault when you use an env without opening it

一些說明

merge gahr 的 solution。

2017-11-23

Install NaviServer on openSUSE

NaviServer is a high performance web server written in C and Tcl.

如果要自己手動安裝:
./configure --prefix=/var/lib/naviserver --with-tcl=/usr/lib64 --with-zlib=/usr/lib64 --with-openssl=/usr/lib64
make
sudo make install

如果在 make 的時候遇到 -fstack-protector-strong 或者是 -fstack-clash-protection 無法辨識的問題, 修改 include/Makefile.global, 將 -fstack-protector-strong 或者是 -fstack-clash-protection 改為 -fstack-protector。 這是編譯套件時 GCC 新舊版本不同所造成的狀況。
sed -i s/stack-protector-strong/stack-protector/g include/Makefile.global
sed -i s/stack-clash-protection/stack-protector/g include/Makefile.global

NaviServer 預設的使用者是 nsadmin,所以需要使用下列的指令來增加使用者:
sudo useradd -d/var/lib/naviserver -U -M -s/bin/bash nsadmin

再來需要修改 NaviServer 目錄的擁用者設定,修改擁有者 user:group 為 nsadmin:nsadmin。
sudo chown -R nsadmin:nsadmin /var/lib/naviserver

再來要設定 nsadmin 帳號的密碼,使用 passwd 改變 nsadmin 使用者的密碼:
sudo passwd nsadmin

再來是執行 NaviServer
sudo -i -u nsadmin  /var/lib/naviserver/bin/nsd -f -t /var/lib/naviserver/conf/nsd-config.tcl

再來瀏覽 http://localhost:8080/,如果出現 NaviServer 的訊息就表示成功了。

/var/lib/naviserver/conf 下還有其它的設定範例,包含 openacs-config.tcl, sample-config.tcl, simple-config.tcl, 也可以用來參考。


以上手動安裝的部份。在知道怎麼手動安裝以後,我就寫了 script 來產生 RPM 檔案,成果請看 naviserver-spec

(更新:我發現和裝到 /usr/local/ns 的狀況不同,所以不用設定 log 目錄位置,所以我移除了怎麼設定 log 目錄的部份)

2017-11-21

Wub systemd service

假設  Wub 目錄是放在 /var/opt/wub,將 user:group 設定為 wwwrun:www,所以試寫第一版的 wub.service 如下:

[Unit]
Description=Tcl Wub web-server
After=network.target

[Service]
WorkingDirectory=/var/opt/wub
ExecStart=/usr/bin/tclsh Wub.tcl
Restart=always
# Restart service after 10 seconds if Wub service crashes
RestartSec=10
User=wwwrun
Group=www
SyslogIdentifier=wub

[Install]
WantedBy=multi-user.target

如果是 openSUSE,檔名是 wub.service,然後放在 /etc/systemd/system 目錄下。這樣就可以將 wub 作為一個系統服務來管理。

也就是說,可以這樣啟動 Wub:
sudo service wub start

關閉 Wub:
sudo service wub stop

目前狀況:
sudo service wub status


更新 2017/11/22:
加入 After= 和 WantedBy=

2017-11-20

TclVFS @ core.tcl.tk

TclVFS 目前主要開發的位址已經被移到 core.tcl.tk,所以如果需要拿取最新原始碼或者是對這個套件有興趣的人需要更新網址,不過看起來還在整理的階投。還有就是我注意 mpexpr 也被放到 core.tcl.tk 的列表中。

如果 TclVFS 有人繼續維護的話,我想是一件好事,因為在一些情況下, Tcl Virtual Filesystem 是一個很好用的工具。

2017-11-14

Apache 2, Apache Rivet and openSUSE

這篇假設你已經在 openSUSE 安裝了 Tcl,如果沒有,那需要先安裝才行。測試的環境為 openSUSE Leap 42.3。

Apache HTTP Server 2 在 openSUSE 安裝的方式:
sudo zypper in apache2

如果要啟動 Apache 2:
sudo systemctl start apache2

如果要停止 Apache 2:
sudo systemctl stop apache2

如果要開機的時候就啟動服務,使用:
sudo chkconfig apache2 on

如果不要,使用:
sudo chkconfig apache2 off

如果需要公開在網路上,還需要設定防火牆,否則只能在 localhost 使用。


再來安裝 Apache Rivet,如果要在 openSUSE 安裝,先設定軟體庫:
sudo zypper addrepo https://download.opensuse.org/repositories/Apache:/Modules/openSUSE_Leap_42.3/ Apache-Modules

更新軟體庫:
sudo zypper refresh

更新以後,使用下列的指令安裝:
sudo zypper install apache2-mod_rivet

加入 Apache Rivet module 到 Apache 2:
sudo a2enmod rivet
(* 如果要移除,使用 sudo a2dismod rivet 來移除)

接下來重新啟動 Apache 2:
sudo systemctl restart apache2

到 /srv/www/htdocs/ 目錄下,建立 hello.rvt,內容如下:
<? set hello_message "Hello world" ?>
<html>
  <head>
    <title><?= $hello_message ?></title>
  </head>
  <body><?= [::rivet::html $hello_message pre b] ?></body>
</html>
瀏覽 http://localhost/hello.rvt,如果有看到訊息表示成功安裝。


再來設定 Apache2 HTTPS 的部份。一開始先確定 mod_ssl 有開啟:
sudo a2enmod ssl

再來是設定 Self-Signed Certificates 的部份,在 Linux 上使用 OpenSSL 建立。Create a config file for your certificate :
 [req]
default_bits       = 2048
default_keyfile    = localhost.key
distinguished_name = req_distinguished_name
req_extensions     = req_ext
x509_extensions    = v3_ca

[req_distinguished_name]
countryName                 = Country Name (2 letter code)
countryName_default         = US
stateOrProvinceName         = State or Province Name (full name)
stateOrProvinceName_default = New York
localityName                = Locality Name (eg, city)
localityName_default        = Rochester
organizationName            = Organization Name (eg, company)
organizationName_default    = localhost
organizationalUnitName      = organizationalunit
organizationalUnitName_default = Development
commonName                  = Common Name (e.g. server FQDN or YOUR name)
commonName_default          = localhost
commonName_max              = 64

[req_ext]
subjectAltName = @alt_names

[v3_ca]
subjectAltName = @alt_names

[alt_names]
DNS.1   = localhost
DNS.2   = 127.0.0.1
Run the following 2 commands using OpenSSL to create a self-signed certificate in openSUSE with OpenSSL :
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt -config localhost.conf -passin pass:YourSecurePassword

sudo openssl pkcs12 -export -out localhost.pfx -inkey localhost.key -in localhost.crt

然後將我們製造的檔案複製到 Apache 的目錄下:
sudo cp localhost.crt /etc/apache2/ssl.crt/server.crt
sudo cp localhost.key /etc/apache2/ssl.key/server.key

如果是 Officially Signed Certificate,可以參考 OpenSUSE: Setting Up a Secure Web Server with SSL

再來修改 /etc/sysconfig/apache2 的設定,
APACHE_START_TIMEOUT="10" 
APACHE_SERVER_FLAGS="SSL"

複製 /etc/apache2/vhost.d/vhost-ssl.template 到 /etc/apache2/vhost.d/vhost-ssl.conf,主要的設定如下:
<IfDefine SSL>
<IfDefine !NOSSL>

##
## SSL Virtual Host Context
##

<VirtualHost *:443>

    #  General setup for the virtual host
    DocumentRoot "/srv/www/htdocs"
    #ServerName www.example.com:443
    #ServerAdmin webmaster@example.com
    ErrorLog /var/log/apache2/error_log
    TransferLog /var/log/apache2/access_log

    #   SSL Engine Switch:
    #   Enable/Disable SSL for this virtual host.
    SSLEngine on

    #   You can use per vhost certificates if SNI is supported.
    SSLCertificateFile /etc/apache2/ssl.crt/server.crt
    SSLCertificateKeyFile /etc/apache2/ssl.key/server.key
    #SSLCertificateChainFile /etc/apache2/ssl.crt/vhost-example-chain.crt

    #   Per-Server Logging:
    #   The home of a custom SSL log file. Use this when you want a
    #   compact non-error SSL logfile on a virtual host basis.
    CustomLog /var/log/apache2/ssl_request_log   ssl_combined

</VirtualHost>

</IfDefine>
</IfDefine>

接下來讓 main site 使用 HTTPS,編輯 /etc/apache2/default-server.conf,加入下面的設定:
IncludeOptional /etc/apache2/conf.d/*.conf
IncludeOptional /etc/apache2/vhosts.d/*.conf

接下來重新開啟 Apache 2,
sudo systemctl restart apache2

瀏覽 https://localhost/hello.rvt, 如果有看到訊息表示成功安裝(PS. 因為是 Self-Signed Certificates,所以 Firefox 會看到警告訊息)。

若要強制使用 SSL,需要開啟 mod_rewrite 才行。
sudo a2enmod rewrite

再來修改 /etc/apache2/vhost.d/vhost-ssl.conf
<VirtualHost *:80>
    RewriteEngine On
    RewriteCond %{HTTPS} !=on
    RewriteRule (.*) https://%{SERVER_NAME}/$1 [R,L] 
</VirtualHost>

<VirtualHost *:443>

    #  General setup for the virtual host
    DocumentRoot "/srv/www/htdocs"
    #ServerName www.example.com:443
    #ServerAdmin webmaster@example.com
    ErrorLog /var/log/apache2/error_log
    TransferLog /var/log/apache2/access_log

    #   SSL Engine Switch:
    #   Enable/Disable SSL for this virtual host.
    SSLEngine on

    #   You can use per vhost certificates if SNI is supported.
    SSLCertificateFile /etc/apache2/ssl.crt/server.crt
    SSLCertificateKeyFile /etc/apache2/ssl.key/server.key
    #SSLCertificateChainFile /etc/apache2/ssl.crt/vhost-example-chain.crt

    #   Per-Server Logging:
    #   The home of a custom SSL log file. Use this when you want a
    #   compact non-error SSL logfile on a virtual host basis.
    CustomLog /var/log/apache2/ssl_request_log   ssl_combined

</VirtualHost>

* 2017/11/18 更新:更新 rewrite 的規則

接下來重新開啟 Apache 2,
sudo systemctl restart apache2

這樣就會強制都使用 HTTPS 瀏覽。

2017-11-13

TemplaTcl and 8.6.1

TemplaTcl: a Tcl template engine


在 Tcler'wiki 發現的套件,我在測試以後發現可以使用,並且對下面的使用留言感到疑惑,

I'm trying to run this script and keep getting an error for the following line of script:
$obj($self:interp) eval $tclBuf

經過實測,發現 Windows 7 (64bit) 的 8.6.7 和 openSUSE LEAP 42.3 的  8.6.7 可以正常使用,但是 Ubuntu 14.04 的 8.6.1 會發生同樣的錯誤訊息。

但是 Ubuntu 環境,使用 ActiveTcl 8.6.3 也同樣可以執行,所以目前推定是 Tcl 8.6.1 應該有問題,會導致 TemplaTcl 無法執行。


更新:
在更多測試以後,發現一個很有趣的問題。就是執行 render 之後,內容是會累積的。這在 CGI 程式下有可能沒問題(因為 process 都會再重新執行一次),但是如果是要使用一個像 Tanzer 的 http server,就有可能會出現問題。

(* Tanzer 有自己的 template system,但是因為我是要測試 TemplaTcl,所以拿 TemplaTcl 來用)

目前的解法是執行到最後,將目前的資料清除,
        if {$obj($self:options:printCommand) == "_defaultSpool"} {
            set x [$obj($self:interp) eval {_defaultSpool {} get}]
            $obj($self:interp) eval {_defaultSpool {} clear}

            set obj($self:data) {}
            return $x
        }

而這樣才能夠在 Tanzer 每次 request 時正常的顯示,而不會資料重複。

如果還要修改,再來就是針對小型網頁的優化,就是先讀取檔案內容並且儲存到 dict,然後每次我們需要的時候再從 dict 拿取。 PS. 但是這樣不知道多人環境下會不會有問題

這樣就可以由 Tanzer 作 URL routing,並且撰寫需要動作的部份,需要輸出畫面時,就由 TemplaTcl 來輸出畫面。

2017-11-07

openSUSE docker image for Tcl

openSUSE 有內建 docker,如果要安裝:
sudo zypper install docker

安裝以後,首先是先確定 Docker server 有在執行:
sudo systemctl status docker

如果沒有,需要啟動 Docker server:
sudo systemctl start docker

再來是下載 openSUSE LEAP 42.3 影像檔,
sudo docker pull opensuse:42.3

再來列出目前的影像檔,確定我們有下載成功:
sudo docker images

如果要執行 openSUSE LEAP 42.3 image,
sudo docker run -t -i opensuse:42.3 /bin/bash

接下來加入東西,建立自己的影像檔。首先是先建立一個工作目錄,然後
touch Dockerfile

接下來加入我們 Dockerfile 的內容:
FROM opensuse:42.3
MAINTAINER Danilo Chang 
RUN zypper addrepo http://download.opensuse.org/repositories/devel:/languages:/tcl/openSUSE_Leap_42.3/  tcl-Leap_42.3 && zypper addrepo https://download.opensuse.org/repositories/home:/danilochang/openSUSE_Leap_42.3/ danilo
RUN zypper --no-gpg-checks refresh && zypper --no-gpg-checks -n update
RUN zypper --no-gpg-checks -n install tcl tcllib tclvfs vectcl tls libgumbo1 tdom rl_json  tcl-trf tclcurl tclreadline memchan tcl-sugar tclsoap tclws                                                                          
COPY tclshrc /root/.tclshrc                                                                                     
CMD ["tclsh8.6"]

CMD ["tclsh8.6"] 讓我們如果不指定 /bin/bash,執行的時候就會直接進入 tclsh 的環境。需要注意的是,我從 tcl devel 和我自己的 repositories 下載檔案。


tclshrc 的內容:
if {$tcl_interactive} {
    package require tclreadline
    
    proc ::tclreadline::prompt1 {} {
        global env
        if {[catch {set pwd [pwd]} tmp]} {
            set pwd "unable to get pwd"
        }

        if [info exists env(HOME)] {
            regsub $env(HOME) $pwd "~" pwd
        }


        if [info exists env(USER)] {
            set user $env(USER)
        } else {
            set user tclsh
        }

        return "$user@[lindex [split [info hostname] "."] 0]:$pwd% "
    }
    
    ::tclreadline::Loop
}

再來使用 docker build 建立影像檔:
sudo docker build -t="ray2501/docker-tcl:devel" .

我們就可以執行建立的影像檔:
sudo docker run -t -i ray2501/docker-tcl:devel /bin/bash

執行的時候如果不指定執行 bash,就會直接執行 tclsh8.6:
sudo docker run -t -i ray2501/docker-tcl:devel

如果要存取本地的目錄並且對應到 docker 中的目錄,並且先執行 bash:
sudo docker run -t -i -v /home/danilo/Public/tclfiles:/root ray2501/docker-tcl:devel /bin/bash

這樣如果執行 /bin/bash 以後,可以在 bash 環境中使用 tclsh8.6 來執行 /home/danilo/Public/tclfiles 中的 tcl script 檔案(在 docker 中的目錄則為 /root)。

(* 或者是將 Tcl script 檔案使用 COPY 複製到 docker 影像檔以後,使用 CMD 來執行。總之,應該有更多的設定方法才對。)


如果要停止容器,使用下列的方式停止 (找出 CONTAINER-ID 以後使用 stop 停止):
sudo docker stop $(sudo docker ps -a -q)

然後再刪除 CONTAINER:
sudo docker rm $(sudo docker ps -a -q)

如果要刪除 Docker image,先列出目前的 image:
sudo docker images

然後再刪除要刪除的項目:
sudo docker rmi ray2501/docker-tcl:devel


這樣就完成了一次 openSUSE Tcl docker 環境的基本測試。如果需要參考更多的 Dockerfile,可以參考 docker-tcl


在建立一個基本的 Tcl 環境可以使用以後,再來建立一個 Wub server, 在我們的工作目錄下放置 wub 目錄和相關的檔案,再修改 wub/Wub/wub.vcl 的設定,
backend default {
    set backend.host = "0.0.0.0";
    set backend.port = "8080";
}

將 backend.host 修改為 docker 影像檔的網路設定值。再更新我們的 Dockerfile:
FROM opensuse:42.3
MAINTAINER Danilo Chang
RUN zypper addrepo http://download.opensuse.org/repositories/devel:/languages:/tcl/openSUSE_Leap_42.3/  tcl-Leap_42.3 && zypper addrepo https://download.opensuse.org/repositories/home:/danilochang/openSUSE_Leap_42.3/ danilo
RUN zypper --no-gpg-checks refresh && zypper --no-gpg-checks -n update
RUN zypper --no-gpg-checks -n install tcl tcllib tclvfs vectcl tls libgumbo1 tdom rl_json  tcl-trf tclcurl tclreadline memchan tcl-sugar tclsoap tclws which
COPY tclshrc /root/.tclshrc
COPY wub /root/wub
EXPOSE 8080
WORKDIR /root/wub
CMD ["tclsh8.6", "Wub.tcl"]

在工作目錄下新增一個檔案 .dockerignore,內容如下:
wub.log

再來使用 docker build 建立影像檔:
sudo docker build -t="ray2501/docker-wub:devel" .

如果建立成功,使用下面的方式執行(將本機的 8080 port 對應到 docker 容器的 8080 port):
sudo docker run -p 8080:8080 -d ray2501/docker-wub:devel

使用下面的命令觀察執行的情況:
sudo docker ps -l

如果要進入容器執行命令:
sudo docker run -t -i ray2501/docker-wub:devel /bin/bash

測試方式:
curl -i localhost:8080

或者是開啟瀏覽器瀏覽 http://localhost:8080

Docker and Tcl

docker


這只是先做資訊收集的工作。搜尋以後,Tcler'wiki 上有關於 docker 的條目。所以目前已經有的項目是 Ubuntu-based batteries-included Tcl 8.6 for Docker

網路上的入門文件與資料:
如何在Linux下安裝Docker
Docker —— 從入門到實踐
Open source Java projects: Docker
docker-cheat-sheet
How to access a directory in hosts machine from inside a docker container?
Docker 無法移除死掉的 Container 修復

(* openSUSE 有內建,所以先確定是否已經有安裝,如果沒有,
使用 sudo zypper install docker 安裝)

DockerHub:
DockerHub

openSUSE container:
opensuse


Tcl 相關連結:
docker-client
docker-tcl
mini-tcl
docker-alpine-tcl

Docker 相關連結:
Moby project


目前比較常聽到的組合:
Docker + Kubernetes
(但是這通常是使用 Docker 一段時間後有調度需求,所以開始使用 Kubernetes)


想法:
那麼可以建立一個基本的 Tcl 環境,指定本地環境的目錄作為 volume,然後在 docker 執行我想要測試的 tcl script 嗎……? 目前的第一步。

2017-10-30

List tm file

#!/usr/bin/tclsh
package require fileutil

set tmpaths [::tcl::tm::path list]
foreach tmpath $tmpaths {
    if {[file exists $tmpath] && [file isdirectory $tmpath]} {
        foreach file [fileutil::find $tmpath {string match -nocase *.tm}] {
            puts $file
        }
    }
}

這只是練習用的程式。使用 tcllib fileutil,利用 fileutil::find 尋找副檔名為 tm 的檔案。雖然我有寫第一行,不過目前只有在 Windows 7 測試過。

Eclipse Dynamic Languages Toolkit

還不確定我會不會使用 Eclipse 一段時間,但是記錄一下 Eclipse 對於 Tcl 的支援。支援的能力來自於 Eclipse Dynamic Languages Toolkit (DLTK),需要自己安裝 plug-in

DLTK 有支援 Tcl Syntax highlighting。
  • 根據 Which tools support Java 9’s new modularity features 的資訊,目前並沒有所有的工具都準備好迎接 Java 9 的模組系統。隨著 Java 9 的正式發佈,比較大量的測試才正開始而已。
  • 但是 Eclipse 和 Apache Maven,也就是接下來我準備要測試的工具已經準備好迎接 Java 9 的模組化了。
  • 在升級到 JDK 9 以後,我安裝用來測試的 Eclipse 和 Apache Maven 可以正確運作。接下來測試 DLTK (Tcl),看起來是正常的,不過實際上要等更多的使用情況才知道。
另外,就 openSUSE: tclBlend and OpenJDK 9 注意到的,JDK 9 的目錄架構有所改變,看起來是因為 openSUSE drop 掉三十二位元的支援,所以可以進行目錄簡化的工作。但是還不確定這是否會成為通例。(PS. JRE_HOME 需要注意是否有正確設定,至少我的沒有)

更新:
JDK and JRE File Structure (JDK 8)
Installed Directory Structure of JDK and JRE (JDK 9)

2017-10-26

openSUSE: tclBlend and OpenJDK 9

使用 zypper addrepo 的方式增加 Java 的 Repository,然後升級到 OpenJDK 9(警語:這樣有可能會讓系統不穩定,所以除非需要才這樣做),但是沒有 Java plugin。

OpenJDK 9 的目錄架構略有改變,所以編譯 tclBlend 會失敗。經過查看 openSUSE 的目錄架構,按照錯誤提示,修改 tcljava.m4,下面是 github 上的修改記錄:

+        # OpenJDK 9 Linux (server JVM)
+
+        F=lib/libjava.so
+        if test "x$ac_java_jvm_jni_lib_flags" = "x" ; then
+            AC_MSG_LOG([Looking for $ac_java_jvm_dir/$F], 1)
+            if test -f $ac_java_jvm_dir/$F ; then
+                AC_MSG_LOG([Found $ac_java_jvm_dir/$F], 1)
+
+                D=`dirname $ac_java_jvm_dir/$F`
+                ac_java_jvm_jni_lib_runtime_path=$D
+                ac_java_jvm_jni_lib_flags="-L$D -ljava -lverify"
+
+                D=$ac_java_jvm_dir/lib/server
+                ac_java_jvm_jni_lib_runtime_path="${ac_java_jvm_jni_lib_runtime_path}:$D"
+                ac_java_jvm_jni_lib_flags="$ac_java_jvm_jni_lib_flags -L$D -ljvm"
+            fi
+        fi
+

然後使用 autoconf 更新設定,下面是 github 上 configure 的修改記錄:
+        # OpenJDK 9 Linux (server JVM)
+
+        F=lib/libjava.so
+        if test "x$ac_java_jvm_jni_lib_flags" = "x" ; then
+
+    echo Looking for $ac_java_jvm_dir/$F >&5
+
+
+            if test -f $ac_java_jvm_dir/$F ; then
+
+    echo Found $ac_java_jvm_dir/$F >&5
+
+
+
+                D=`dirname $ac_java_jvm_dir/$F`
+                ac_java_jvm_jni_lib_runtime_path=$D
+                ac_java_jvm_jni_lib_flags="-L$D -ljava -lverify"
+
+                D=$ac_java_jvm_dir/lib/server
+                ac_java_jvm_jni_lib_runtime_path="${ac_java_jvm_jni_lib_runtime_path}:$D"
+                ac_java_jvm_jni_lib_flags="$ac_java_jvm_jni_lib_flags -L$D -ljvm"
+            fi
+        fi
+

這樣就可以正確編譯。

不過還沒辦法拿去 openSUSE build service 進行驗證。

BuildRequires:    java-1_8_0-openjdk-devel

目前主流的 JDK 版本仍然是 8,openSUSE build service 我暫時沒有更動。如果使用者升級到 OpenJDK 9,那麼要改 BuildRequires,
BuildRequires:    java-9-openjdk-devel

更新(目前的 Oracle 規畫):



所以如果是甲骨文,JDK 9 將會是一個過渡的版本,接下來將會推出 18.3,然後再來是 18.9,而 18.9 才是一個長期支援的版本。而 OpenJDK 對應的是 JDK Project,目前還沒有開 18.3 repositories 出來。

這表示大規模遷移到 Java 9 的機率不大,目前大多數仍然會留在 Java 8,直到下一個長期支援版本到來(目前 Oracle 設定為 18.9)。

這也代表我不用急著更新 RPM spec 以後丟上去 openSUSE build service 測試,至少在 18.9 出現以前,JDK 8 都會是目前的主要版本。

所以策略還是一樣,要等 openSUE LEAP 15 才知道是否需要修改 tclBlend RPM spec。如果預設的 JDK 還是 8,那就不用更動,如果升到 18.9,我就需要研究一下按照版本不同而進行設定的 BuildRequires。

2017-10-24

openSUSE tcl-dbif: let it run

目前 openSUSE OBS 上找到的是 1.0 版,相依性的設定有設 tcl-dbus,所以要先安裝 tcl-dbus,然後才安裝 tcl-dbif。

不過如果你真的使用下列的指令安裝:
sudo zypper install tcl-dbif


你很快就會發現無法正確使用。使用 package require dbif 並不會正確的找到套件。原因也很簡單,

在 tclsh 下使用下列的指令查詢:
::tcl::tm::path list


dbif 會安裝在 /usr/share/tcl/dbif1.0 目錄,但是這個目錄卻沒有在 openSUSE LEAP 42.3 tclsh 搜尋目錄的列表內。修正的方法很簡單,使用 ::tcl::tm::path 指令修正,在家目錄下的 ~/.tclshrc 一開始的時候加上我們想搜尋的目錄,
::tcl::tm::path add /usr/share/tcl/dbif1.0


這樣就可以讓 tcl-dbif 目錄正常的使用。但是會需要這樣是因為 .tclshrc 需要放在家目錄,這樣就…… 不知道怎麼從 rpm spec 修改。

* 原本我以為會去搜尋 /usr/share/tcl 目錄,結果沒有,但是還是可以透過修改 .tclshrc 方式來使用。

* 其實還有個違反 tm 檔案設計想法的改法,就是加上 pkgIndex.tcl,就可以讓 Tclsh 搜尋到。這個方法還沒測試。

更新:雖然和 tm 檔案設計的想法衝突,但是 RPM spec 加上 pkgIndex.tcl 就可以正常運作
使用 build script 建立 RPM 來測試 (注意:tcl-dbif 1.2 需要使用 tcl-dbus 2.1 版本才行)

The ActiveState of Tcl: TEApot and TEAcup Are Now Open Source

The ActiveState of Tcl: TEApot and TEAcup Are Now Open Source


所以 ActiveState 開源了  TEApot 和 TEAcup。Source code 放在 Gihub:
https://github.com/ActiveState/teapot


使用 Magicsplat Tcl/Tk for Windows 執行時出現問題。

比對:
tcllib 1.18 下載以後解壓縮,modules\fumagic,pkgIndex.tcl
if {![package vsatisfies [package provide Tcl] 8.4]} {return}

# Recognizers
package ifneeded fileutil::magic::filetype 1.0.2 [list source [file join $dir filetypes.tcl]]
package ifneeded fileutil::magic::mimetype 1.0.2 [list source [file join $dir mimetypes.tcl]]

# Runtime
package ifneeded fileutil::magic::rt 1.0 [list source [file join $dir rtcore.tcl]]

# Compiler packages
package ifneeded fileutil::magic::cgen   1.0 [list source [file join $dir cgen.tcl]]
package ifneeded fileutil::magic::cfront 1.0 [list source [file join $dir cfront.tcl]]

與 Magicsplat Tcl/Tk for Windows 的版本:
if {![package vsatisfies [package provide Tcl] 8.6]} {return}
# Recognizers
package ifneeded fileutil::magic::filetype 1.2.0 [list source [file join $dir filetypes.tcl]]

# Runtime
package ifneeded fileutil::magic::rt 1.2.0 [list source [file join $dir rtcore.tcl]]

# Compiler packages
package ifneeded fileutil::magic::cgen   1.2.0 [list source [file join $dir cgen.tcl]]
package ifneeded fileutil::magic::cfront 1.2.0 [list source [file join $dir cfront.tcl]]

在比對完 github 的 check-in 記錄以後,應該是有人直接修改了 tcllib 的部份,所以如果是從 github 或者是 1.18 release 之後(精確的說是 2016 年 6 月 13 日)從 trunk 拿的版本,就可能會出現這個問題。

* 即使 fileutil::magic::mimetype 問題解決,還是需要 Trf 套件
* 有 Trf 套件還是無法執行,需要修改 Trf 套件的 pkgIndex.tcl 版本號(ActiveTcl 8.6.6 issue)
* 改了版號還是無法執行,pref-devkit.tm 需要 projectInfo 套件
* 移除掉 package require projectInfo 也無法正確執行,沒有 jobs-async.tm 檔案


更新:
看起來我的做法錯誤, 下載 ActiveTcl 8.6.6 以後重做一次。

can't find package fileutil::magic::mimetype
    while executing
"package require fileutil::magic::mimetype   "
    (file "C:/Temp/teapot/lib/metadata/teapot-metadata-read.tm" line 38)


ActiveTcl 使用 Tcllib 1.18,所以不會有這個問題,如果有遇到;
更新 mimetype 變成 filetype (不知道這個做法會不會影響程式執行)
然後

attempt to provide package Trf 2.1 failed: package Trf 2.1.4 provided instead
    ("package ifneeded Trf 2.1" script)


ActiveTcl 會遇到這個問題,把版本修正為精確版本。
更新 C:/ActiveTcl/lib/trf21/pkgIndex.tcl, 修改 2.1 為 2.1.4.
然後

couldn't read file "C:/Temp/teapot/lib/misc/jobs-async.tm": no such file or dire
ctory
    while executing
"source C:/Temp/teapot/lib/misc/jobs-async.tm"
    ("package ifneeded jobs::async 0.1" script)
    invoked from within
"package require jobs::async"
    (file "C:/Temp/teapot/lib/tap/tap-db-files.tm" line 31)


更新 C:/Temp/teapot/lib/tap/tap-db-files.tm,
修改 jobs::async 變成 jobs

can't find package projectInfo
    while executing
"package require projectInfo     "
    (file "C:/Temp/teapot/lib/prefdk/pref-devkit.tm" line 44)


重做以後遇到這個問題,目前的暫時解法是移除掉package require projectInfo 這一行。

然後終於有一個可以正確執行的東西。


不過可惜的是到這裡還沒有結束,使用 tclsh main.tcl list [package name] 以後,會出現

Bad configuration
No archives known

的訊息,有可能是需要一些其它設定,那麼即使修改到可以執行,還是沒用(PS. 已經使用有包含 mimetype 的 1.18 版測試過,看起來不是這個套件的問題)。所以有可能需要包裝為 teapot 的工具並且經過適當設定才能夠使用。

外部的開發者無法在自己環境使用的工具等於沒有開源一樣,而等於沒有開源的意思就是還是只有 ActiveState 自己的人在開發。經過測試,雖然 ActiveState 開源了 teapot,但是就目前的有限資訊下,對於非 ActiveState 的人(甚至是 ActiveTcl 8.6.6 的使用者)來說是個沒有用的工具。

更新:
我發了一個訊息到 comp.lang.tcl,這樣如果有使用 fileutil::magic::mimetype 的人在 Tcllib 被修改以後的版本遇到問題,他們才知道發生了什麼情況。

2017-10-05

Sugar: Lisp-like macro system for Tcl

Sugar


會注意到是因為 Debian/Ubuntu 有這個套件 (套件名叫 tcl-sugar),然後就去 Tcler's wiki 看是不是有資料。沒想到可以用 pure Tcl 實作了一個類似 Lisp 的 macro system。

2017-10-02

Magicsplat Tcl/Tk for Windows, Visual Studio 2017 and Jsonnet

Google Jsonnet 在 v0.9.5 加入一個 Visual Studio 2017 的專案檔,這表示已經成功的移植到 Windows(使用 Visual Studio 2017 編譯)。

我按照以前寫的 extension,改寫之前 win 目錄下的 makefile.vc 等檔案,試看看能不能使用 Visual Studio 2017 來編譯 tcljsonnet。結果出現一個很奇怪的狀況,在最後連結的時候,Visual Studio 2017 會一直抱怨 BAWT-Tcl (使用 MinGW-W64 編譯) 所使用的是舊的格式,我需要重新編譯才能夠正確連結。

經過思考,換成 Magicsplat Tcl/Tk for Windows 來測試(注:我安裝 在 c:\Tcl,而 Magicsplat  使用 Visual Studio 編譯),然後就很順利的編譯了,而且 tcljsonnet 也可以很正確的使用,不會有 C++ exception 模式不相容的問題。

但是這樣也代表,微軟 Windows 平台出現了格式的相容性問題,但是除非是一個環境中會使用 MinGW-W64 與 Visual Studio 2017 的人才會很快就遇到這個問題。

但是如果將 MinGW-W64 與 Visual Studio 2017 因為 binary 格式不相容而視為二個平台,就會出現很大的維護問題,至少對我而言會很吃力。而目前看起來,並沒有一個好的解法,還蠻麻煩的。 更新:我把使用 MinGW-64 編譯的 extension(例如我自己寫的 tcl-lmdb)放到 Magicsplat Tcl/Tk for Windows 的 lib 目錄下,發現可以使用,所以看起來問題沒那麼大。目前我還是以使用 Mingw-W64 為主,只有需要 Visual Studio 的時候才使用 Visual Studio。

2017-09-19

BAWT-Tcl Windows installer

BAWT 提供了 8.6.x 系列的 Windows 安裝程式,並且安裝了一些常用的擴充套件以及 BAWT 作者自己的作品。


如果你使用 MSYS2/MinGW-w64 的組合,然後 Tcl 使用 BAWT-Tcl 安裝 (假設是安裝在 c:/Tcl),在編譯自己的套件時有可能會出現一些小亂流。

這是因為 tclConfig.shtkConfig.sh 在編譯的機器所指向的目錄和目標機器並不相同。也就是 TCL_PREFIX, TCL_EXEC_PREFIX, TCL_BUILD_LIB_SPEC, TCL_LIB_SPEC 等目錄所記錄的資料不是正確的。但是要修改很簡單,只要指向正確的目銾就可以解決問題,下面是一個例子:

# Top-level directory in which Tcl's platform-independent files are
# installed.
TCL_PREFIX='/c/Tcl'


假設要改善的話,應該只能夠從安裝程式著手,也就是使用者設定好安裝目錄以後,按照安裝目錄的位置進行改寫。取巧的做法是編譯機器和目標機器的環境很像,然後目標機器的預設目錄符合預設的設定(還是有可能出問題,因為使用者有可能不是安裝在預設位置,但是機率大幅度降低)。

2017-09-17

regex_rez: Tcl bindings for RE2

regex_rez


可是,我目前只需要基本功能,所以只實作最簡單的 fullmatch, partialmatch, replace 和 globalrelpace。


維基百科:
RE2 (software)


第一個測試程式使用內建的正規表示式:
puts -nonewline "Please input a number: "
flush stdout
gets stdin number
if {$number <= 0 || $number >= 10} {
   puts "The range is 1 - 9."
   exit
}

set max [expr pow(10, $number)]
set result [list]

puts "\nNow get the result:"
puts "==========="
puts "Start"
puts "==========="
puts [time {
set re {1.*1|2.*2|3.*3|4.*4|5.*5|6.*6|7.*7|8.*8|9.*9|0.*0}
for {set i 1} {$i < $max} {incr i} {
    if {[regexp $re $i] != 1} {
        lappend result $i
    }
}
} 1]
puts "\n"
puts [join $result ", "]
puts "\n==========="
puts "End"
puts "==========="

第二個測試程式使用 RE2:

package require regex_rez

puts -nonewline "Please input a number: "
flush stdout
gets stdin number
if {$number <= 0 || $number >= 10} {
   puts "The range is 1 - 9."
   exit
}

set max [expr pow(10, $number)]
set result [list]

puts "\nNow get the result:"
puts "==========="
puts "Start"
puts "==========="
puts [time {
set re [regex::re2 create {1.*1|2.*2|3.*3|4.*4|5.*5|6.*6|7.*7|8.*8|9.*9|0.*0}]
for {set i 1} {$i < $max} {incr i} {
    if {[$re partialmatch $i] != 1} {
        lappend result $i
    }
}
} 1]
puts "\n"
puts [join $result ", "]
$re close
puts "\n==========="
puts "End"
puts "==========="

(* 更新測試程式二,加上 $re close,這樣才會正確 close)

內建從輸入數字一到數字九:
475 microseconds per iteration
1029 microseconds per iteration
6884 microseconds per iteration
36152 microseconds per iteration
315566 microseconds per iteration
3134428 microseconds per iteration
32075931 microseconds per iteration
325211857 microseconds per iteration
2147483648 microseconds per iteration

RE2 從輸入數字一到數字九:
325 microseconds per iteration
649 microseconds per iteration
7262 microseconds per iteration
48204 microseconds per iteration
347111 microseconds per iteration
2727082 microseconds per iteration
21171756 microseconds per iteration
173788033 microseconds per iteration
1628503061 microseconds per iteration


雖然沒有很仔細的比較,在這個測試中如果是百萬級數目或者是以前比對,內建的正規表示式表現都很不錯,但是如果數目到達百萬級或者是以後比對,Tcl 內建的正規表示式速度看起來明顯的比較慢。

很有趣的問題。

2017-09-12

tclmixer - use SDL2/SDL2_mixer

tclmixer


TclMixer 使用 SDL 1.2 和 SDL_Mixer 1.2,我這幾天突然有個神奇的想法,那就是如果切換到 SDL2/SDL2_mixer 不知道會不會很難。

所以我下載了 TEA sample extension 然後將 TclMixer 的 source code 移過來,做了一點點小修改(主要是改版本,改成 2.0.0),然後發現…… SDL 和 SDL2 的 API 差距沒想像中大,因為沒改什麼東西,可是看起來 TclMixer 可以用。

實測 openSUSE 42.3 的結果,雖然可以使用,但是 SDL_mixer 還沒有 mp3 的部份,不過 OGG/Vorbis 是可用的。

MP3 的最後一個軟體專利在今年到期,所以一些原本無法直接使用的 MP3 自由軟體應該會開始逐漸重新進入 Linux 世界。至少在我測試 mpg123 的時候,我發現 openSUSE 在 42.3 將這個軟體放進來了(只是 devel package name 有改變,與 packman 的不同,但是主套件和 shared library package name 看起來是一致的)。

2017-08-26

tDOM 0.9.0

ANNOUNCE: tDOM 0.9.0


tDOM 在很長一段時間的 0.8.3 以後,釋出了一個新版 v0.9.0。這版加入了 JSON 的支援,並且在編譯時開啟 html5 選項(需要連結 Google Gumbo html parser library)的情況下就有 html5 parser 可以用。

tDOM contains:

    *  for convenience expat 2.2.0, the XML parser originated from
       James Clark, although you're able to link tDOM with other
       expat versions or the library provided by the system.
    *  building a DOM tree from XML in one go implemented in C for
       maximum performance and minimum memory usage, and DOM I and II
       methods to work on such a tree using either a OO-like or a
       handle syntax.
    *  a Tcl interface to expat for event-like (SAX-like) XML parsing.
    *  a complete, compliant and fast XPath implementation in C
       following the November 99 W3C recommendation for navigating and
       data extraction.
    *  a fast XSLT implementation in C following the W3C Recommendation
       16 November 1999.
    *  optional DTD validation.
    *  a JSON parser which parses any possible JSON input into a DOM
       tree without losing information.
    *  an efficient and Tcl'ish way to create XML and HTML documents
       and JSON string.
    *  as build option an interface to the gumbo HTML5 parser, which
       also digests almost any other HTML.
    *  an even faster simple XML parser for trusted XML input.
    *  additional convenience methods.

2017-08-21

tcl-rocksdb v0.1.1

tcl-rocksdb


RocksDB 更改了授權,從原本的 BSD+PATENTS 變成 BSD+PATENTS/GPL v2 (dual license) 然後又變成 Apache 2.0/GPL v2 (dual license)。

而會修改授權,是因為 Apache 基金會接到 Apache Cassandra Team 的請求,希望能夠釐清使用 RocksDB 是否會有版權上的疑慮以後,公佈了結果,Facebook BSD+PATENTS license 被列為 category-x,同時提出了下列的建議:
  • No new project, sub-project or codebase, which has not used Facebook BSD+patents licensed jars (or similar), are allowed to use them. In other words, if you haven't been using them, you aren't allowed to start. It is Cat-X.
  • If you have been using it, and have done so in a *release*, you have a temporary exclusion from the Cat-X classification thru August 31, 2017. At that point in time, ANY and ALL usage of these Facebook BSD+patents licensed artifacts are DISALLOWED. You must either find a suitably licensed replacement, or do without. There will be NO exceptions.
  • Any situation not covered by the above is an implicit DISALLOWAL of usage.

所以如果 RocksDB 沒有修改授權,那麼 Apache Cassandra 就無法使用 RocksDB,已經使用的 Apache Flink, Apache Samza 等各個專案也需要開始尋找替代品。而 RocksDB team 在 Apache 基金會公佈結果以後,很快就修改了授權,變成 Apache 2.0/GPL v2 (dual license)。

既然授權的疑慮清除(PATENTS 是很複雜的議題,如果可以,我會儘量避開),所以我實作了 tcl-rocksdb (prototype),實作一些最基本的操作,同時也寫了簡易的 test case 測試。因為其它的部份我不知道怎麼實作成 Tcl extension 比較好,所以目前的 v0.1.1 就是我實作上比較知道怎麼做的部份了。


2017/08/22
加入幾個新的 command,更新版本為 v0.2。

另外,我發現 LevelDB 和 RocksDB 的基本操作長的很像,所以按照目前的資料,也寫了一個 tcl-leveldb

2017-08-05

Apache Tomcat HTTP/2 與 TclCurl

一閈始是設定 Apache Tomcat HTTP/2 的部份(Apache Tomcat 在 8.5 及以後的版本開始支援)。

HTTP/2 需要 Apache Tomcat Native Library 的支援才行, 所以首先是先安裝 Apache Tomcat Native Library。

Tomcat Native Library 需要 libapr1,所以如果是使用 openSUSE,
sudo zypper install libapr1-devel

再來是自己編譯 Tomcat 或者是使用 RPM 安裝

然後,需要創造 OpenSSL Certificate。 可以參考 Howto: Make Your Own Cert With OpenSSL。

如果只是要自己測試使用所以使用 self-signed,在 conf 目錄下使用下列的指令:
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 1826 -key ca.key -out ca.crt

最後修改 conf 目錄下的 server.xml,開啟 HTTP/2 的支援並且修改如下:

    <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
               maxThreads="150" SSLEnabled="true" >
        <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
        <SSLHostConfig>
            <Certificate certificateKeyFile="conf/ca.key"
                         certificateFile="conf/ca.crt" />
        </SSLHostConfig>
    </Connector>

再來重新開啟 Apache Tomcat server,如果 https://localhost:8443 可以瀏覽,就正確的設定好了(* 因為是 self-signed,所以會看到安全性警告)。


TclCurl 在最近的版本有支援 HTTP/2,下面是驗證 Apache Tomcat HTTP/2 (self-signed) 的範例:
package require TclCurl

set curlHandle [curl::init]
$curlHandle configure -url https://localhost:8443 -sslverifyhost 0 \
    -sslverifypeer 0 -bodyvar result
$curlHandle setopt CURLOPT_HTTP_VERSION 2TLS

catch { $curlHandle perform } curlErrorNumber
if { $curlErrorNumber != 0 } {
    puts "error [curl::easystrerror $curlErrorNumber]"
}

# Print result and clean up
puts $result
$curlHandle cleanup

那麼我們怎麼知道真的是使用 HTTP/2 協定呢?這只要檢查 Tomcat logs 目錄下的 log 就可以確定。

2017-08-03

TclCurl and HTTP/2

TclCurl


HTTP/2 預計在 Java 9 加入支援,所以我想預計會開始增加使用率。

目前 BitBucket 上的 TclCurl 已經加上 HTTP/2 的支援,所以如果要測試 HTTP/2,需要使用 TclCurl。根據 Tcler's Wiki 的資料,目前好像也只有 TclCurl 可以使用 (PS. 目前內建的 http 套件只支援到 1.1)。

Debian 方面,7.22.0+hg20160822-1 這個版本就是使用目前 BitBucket 上到最近的 source code。

openSUSE 的部份我檢查了一下,發現還停留在原作者最後一個釋出的版本,所以我把 openSUSE 的檔案拿過來,加上 BitBucket 上的 source code,然後修改 spec 的 URL 到目前的來源,試著 build 一版看會不會有問題。比較有問題的是版本號碼,因為同一個版本會造成問題(也就是我有可能會進行一些調整,只是還不知道怎麼做比較好) (更新:版本設為 7.22.0+hg20160822) (2017/08/06 更新:放上去 openSUSE build service 測試以後,將版本更新為 7.22.0_hg20160822)。

(另外一個問題是 openSUSE 42.x CURL 版本停在 7.37.0,這表示我可能需要升級 CURL)
(升級到 7.54.1,然後 TclCurl 會發生 libssh2_scp_recv2 找不到的問題,這需要升級 libssh2-1 到新的版本,我升到 libssh2-1-1.8.0-56.2.x86_64 以後解決問題)


更新:
Tools for debugging, testing and using HTTP/2
( 所以 CURL 在  7.43.0及以後的版本,使用 nghttp2 這個函式庫來支援 HTTP/2)

cURL 的文件部份:
HTTP/2 with curl

HTTP/2 解釋文件:
http2 explained

另外的文件:
Introduction to HTTP/2
Chapter 12. HTTP/2

2017-08-01

Swapping variables

資料來自於 Tcler's Wiki - Tcl Gems


使用 Tcl 8.5 內建命令 lassign:
lassign [list $b $a] a b

lassign 可以將一個 list unpack 然後設定其它的變數,利用這一點,就可以交換二個變數的值,很有趣的做法。

2017-07-04

TclProDebug

TclProDebug


The TclPro Debugger version 2.0 is an upgrade of of the debugger included in the TclPro product version 1.5 released and open-sourced by Scriptics Corporation.


FlightAware 贊助的案子,將 TclPro 的 debugger 更新到 Tcl 8.5+ 可以使用。


* 要先編譯 lib 目錄下的 tclparser 並且安裝才行

2017-06-28

Tcl/Tk and ActiveMQ

ActiveMQ: Cross Language Clients and Protocols


網頁下面有使用的範例,其中 Tcl/Tk 的部份有二個,一個是支援 STOMP 協定的 tstomp 套件,一個則是利用 Tcl/Java 來呼叫 Java API。因為有簡單的使用範例,所以我想有需要的時候可以用來參考。


* 另外,ActiveMQ 有支援 MQTT,所以 Tcl MQTT client 可以用來測試是否可以執行,或者是使用我包裝 Paho MQTT C Client 的 Tcl extension tcl.mqttc

2017-06-17

執行程式並且取得標準輸出的結果

set var [list ls -l]
catch {exec {*}$var} result

set lines [split $result "\n"]
foreach line $lines {
    if {[string index $line 0] == "d"} {
        puts $line
    }
}

只是用來練習的程式。使用 exec 來執行程式 (ls -l),取得結果以後再來處理。

2017-06-14

Using Nashorn from Java code (TclBlend version)

package require java

java::import javax.script.ScriptEngine
java::import javax.script.ScriptEngineManager
java::import javax.script.ScriptException

set manager [java::new ScriptEngineManager]
set nashorn [$manager getEngineByName "nashorn"]
set value [$nashorn eval "10 + 20"]
puts "Result: [$value toString]"

Nashorn 是使用 Java 寫的 JavaScript runtime,for Java 8 使用。目前支援的規範是 ECMAScript 5.1。

這裡透過 Nashorn 來執行 JavaScript code。我只是使用 TclBlend 執行 sample code 測試看看。

2017-06-13

apply

set twice {{f v} {apply $f [apply $f $v]}}
set f {{v} {return [expr $v + 5]}}

puts [apply $twice $f 10]

這只是在說明,你可以將匿名函數的內容設給一個變數,然後使用 apply 來呼叫。

2017-06-05

tclhttpd-markdown

tclhttpd-markdown


Tcllib 在 1.18 中 提供了 Markdown 套件,可以將 Markdown 格式轉換為 HTML 的工具。為了檢視這個套件目前的情況,所以作了一個簡略的 TclHttpd 支援。如果是 *.md 檔案,就使用 Tcllib Markdown 套件進行轉換,然後傳回 UTF-8 編碼的 HTML。

我想使用其它的 Markdown 轉換工具應該也是可以,只要修改轉換的部份和提供正確的 package require 就可以才對。

2017-06-04

Apache Tomcat, CGI and Tcl

最近使用 Apache Tomcat 在測試一些東西,所以同時也測試一下如何設定 CGI 以執行 Tcl CGI scrpits 的設定。

預設中 CGI 是 disable 的。在 Apache Tomcat 下使用 Tcl 來撰寫 CGI 程式,需要設定 conf/web.xml 與 conf/context.xml。 可以參考 Apache Tomcat 的文件

首先要設定 web.xml,開啟 CGIServlet 的支援,並且設定執行的程式。 如果是修改 conf/web.xml,那效果是全域性的。如果修改 WEB-INF/web.xml,就只侷限在你的 web application。
    <servlet>
        <servlet-name>cgi</servlet-name>
        <servlet-class>org.apache.catalina.servlets.CGIServlet</servlet-class>
        <init-param>
          <param-name>cgiPathPrefix</param-name>
          <param-value>WEB-INF/cgi</param-value>
        </init-param>
        <init-param>
          <param-name>executable</param-name>
          <param-value>/usr/bin/tclsh</param-value>
        </init-param>        
        <load-on-startup>5</load-on-startup>
    </servlet>

與 URL mapping:
    <servlet-mapping>
        <servlet-name>cgi</servlet-name>
        <url-pattern>/cgi-bin/*</url-pattern>
    </servlet-mapping>

再來是 context.xml,設定 Context 的 privileged 屬性為 true(但是要注意,這樣的更改是全域設定):
<Context privileged="true">

在 webapps/ROOT/WEB-INF 目錄中建立一個新的目錄 cgi。 接下來將我們的 Tcl CGI script 放在 cgi 目錄就可以了。舉例來說,如果是 hello.tcl,網址就是:
http://localhost:8080/cgi-bin/hello.tcl

如果不要在根目錄,舉例來說,可以在 webapps 下建立一個目錄 myapp,myapp 目錄下再建立一個目錄 WEB-INF, 然後 WEB-INF 目錄下再建立目錄 cgi。

接下來將我們的 Tcl CGI script 放在 cgi 目錄,如果正確設定就可以執行。舉例來說,如果是 hello.tcl,網址就是:
http://localhost:8080/myapp/cgi-bin/hello.tcl

2017-05-19

Tcl extension and TDBC driver for CrateDB database : cratedb-tcl 0.1

Source code:
cratedb-tcl


如果需要知道 CrateDB 的一些資料:
CrateDB


因為 CrateDB REST API 支援 Parameter Substitution,所以實際上可以用來模擬 prepare/execute 的行為,這個套件為了驗證這個想法而寫的。

也就是實際上是使用 TclCurl 發出 POST request,然後取得結果。而為了模擬,所以我們將步驟分為幾個部份:
  • prepare: 取得 SQL code,只是放在某個變數中
  • param: 使用者設定參數的型別 (TYPE) 與值 (VALUE)
  • 當使用者呼叫 execute,我們將前面二者的資料結合並且設定好 Parameter Substitution 以後,發出 POST request 然後取得結果
而因為模擬了 prepare/execute,所以我們就可以使用 TDBC 中的 ::tdbc::tokenize 分析 SQL 並且進行參數代換的工作,這部份完成以後,就有一個基本可用的 TDBC driver。

但是需要注意的是因為是用模擬的,跟一般資料庫的行為還是有一些差距,當設定  PARAM 時如果沒有為某個欄位指定 TYPE/VALUE,一般會設為 NULL,雖然 TDBC 的部份我有做一些處理,但是還是沒辦法跟一般的資料庫行為完全一樣。

讓我驚嚇的部份是 CrateDB 欄位部份會自動按照變數 欄位的字母大小排序,也就是在 insert 時不指定欄位順序的話,就會出現插入資料到錯誤的欄位。我反覆測試的時候發現的,目前不知道是 CrateDB 的設計還是 bug。

TclCurl DELETE request (using -customrequest)

package require TclCurl

proc httpBlobDelete {url} {
    set curlHandle [curl::init]
    $curlHandle configure -url $url -customrequest DELETE
    catch { $curlHandle perform } curlErrorNumber
    if { $curlErrorNumber != 0 } {
        return -code error [curl::easystrerror $curlErrorNumber]
    }
    
    set responsecode [$curlHandle getinfo responsecode]
    $curlHandle cleanup
    
    if {$responsecode == 204} {
        return -code ok
    } elseif {$responsecode == 404} {
        return -code error "Not Found"
    } else {
            return -code error "ERROR"
    }
}

用來測試 CrateDB REST API 中 BLOB delete 的程式。

如果 TclCurl 要指定 DELETE request,使用 -customrequest 來指定。因為 CrateDB 會回傳 status code 來表示檔案已成功刪除 (204),或者是沒有這個檔案 (404),所以取得 responsecode 以後判斷。大致上是這樣。


更新:
CrateDB 加入 BLOB 資料使用 PUT,下面是測試的程式:
package require TclCurl

proc httpBlobPut {url data} {
    set curlHandle [curl::init]
    $curlHandle configure -url $url -customrequest PUT -postfields $data
    catch { $curlHandle perform } curlErrorNumber
    if { $curlErrorNumber != 0 } {
        return -code error [curl::easystrerror $curlErrorNumber]
    }
    
    set responsecode [$curlHandle getinfo responsecode]
    $curlHandle cleanup
    
    if {$responsecode == 201} {
        return -code ok
    } elseif {$responsecode == 409} {
        return -code error "Conflict"
    } else {
            return -code error "ERROR"
    }
}


使用 GET 讀取內容:
proc httpBlobGet {url} {
    set curlHandle [curl::init]
    $curlHandle configure -url $url -bodyvar html
    catch { $curlHandle perform } curlErrorNumber
    if { $curlErrorNumber != 0 } {
        return -code error [curl::easystrerror $curlErrorNumber]
    }
    
    set responsecode [$curlHandle getinfo responsecode]
    $curlHandle cleanup
    
    if {$responsecode == 200} {
        return -code ok $html
    } elseif {$responsecode == 404} {
        return -code error "Not Found"
    } else {
            return -code error "ERROR"
    }
}

2017-05-14

info library

info library

info library returns the name of the library directory in which standard Tcl scripts are stored.

我忘記我有沒有記錄過這件事,就是 openSUSE 和 Ubuntu 如果你使用 info library 取得資訊,openSUSE (64 位元) 是在 /usr/lib64/tcl/tcl8.6,但是 Debian/Ubuntu 使用不同的方式來組織 32/64 位元目錄,所以會拿到不同的資訊。

如果是純粹 Tcl 所撰寫的套件,openSUSE 也有可能會放在 /usr/share/tcl 這個目錄下面。

2017-05-12

tclcubrid v0.9.5

Source code

tclcubrid

About


CUBRID is an open source SQL-based relational database management system with object extensions developed by Naver Corporation for web applications.

tclcubrid is a Tcl extension by using CUBRID CCI (CCI Client Interface) driver to connect CUBRID. I write this extension to research CUBRID and CCI (CCI Client Interface) driver.

Main changelog

  1. Improve set/multiset/sequence type handle for fetch_row_list/fetch_row_dict method.
    Now tclcubrid will return a Tcl list for set/multiset/sequence type.
    It is an INCOMPATIBILITY change! User maybe need update source code.
  2. Add blob/clob write size check

改進對於 set/multiset/sequence type 的處理。同時加入更多 blob/clob size 的檢查。


更新:
CUBRID 網站怪怪的,我本來想指向正確的文件位址,結果位址一直變動。最後我覺得煩了,我把位址暫時清掉,然後也重置了  tclcubrid 的 git repository。

更新:
早上起來發現網站又回復為原來的樣子,所以我把連結加回去了,希望接下來不會又出現連結失效的問題。

2017-05-03

TclCurl and YQL

我不太確定 YQL 是不是還有在維護,不過這是用來測試 TclCurl Http GET 的程式,送出查詢給 Yahoo Query Language (YQL),然後取得結果。

package require Tcl 8.6
package require TclOO
package require TclCurl

oo::class create CurlYQL {
    variable html_result
    
    constructor {} {
        set html_result ""
    }
    
    destructor {
    }    
    
    method query {query args} {
        variable url
        variable pairs

        set url "https://query.yahooapis.com/v1/public/yql"
        set pairs {}

        lappend pairs "[curl::escape format]=[curl::escape json]"
        lappend pairs "[curl::escape q]=[curl::escape $query]"
        
        foreach {name value} $args {
            lappend pairs "[curl::escape $name]=[curl::escape $value]"
        }
    
        append url ? [join $pairs &]

        set curlHandle [curl::init]
        $curlHandle configure -url $url -bodyvar [namespace current]::html_result
        catch { $curlHandle perform } curlErrorNumber
        if { $curlErrorNumber != 0 } {
            error [curl::easystrerror $curlErrorNumber]
        }

        $curlHandle cleanup
    }
    
    #
    # get the results
    #
    method getResults {} {
        return $html_result
    }
}

所以接下來就是測試。

送出查詢到 Yhaoo Finance (for CSV file) 查詢 SPY, VOO, IVV 的股價,我們拿到結果以後,使用 rl_json 分析結果並且印出來。

package require rl_json

set curlyql [CurlYQL new]
$curlyql query {select * from csv where url='http://download.finance.yahoo.com/d/quotes.csv?s=SPY,VOO,IVV&f=sl1d1t1c1ohgv&e=.csv' and columns='symbol,price,date,time,change,col1,high,low,col2'}
set query_result [$curlyql getResults]

if {[::rl_json::json exists $query_result query]==1} {
    set rows [::rl_json::json get $query_result query results row]

    puts "========================================\n"
    foreach row $rows {        
        puts "symbol: [dict get $row symbol]"
        puts "price: [dict get $row price]"
        puts "date: [dict get $row date]"
        puts "time: [dict get $row time]"
        puts "change: [dict get $row change]"
        puts "col1: [dict get $row col1]"
        puts "high: [dict get $row high]"
        puts "low: [dict get $row low]"
        puts "col2: [dict get $row col2]"
        puts "========================================\n"
    }
}

$curlyql destroy

* 注意:rl_json 新增加的 command json 放在 namespace rl_json 內,不過使用說明上沒有特別明講,所以需要注意一下這件事。

* 更新:
我把資料放到 Github 上了。

2017-04-29

package vcompare

可以看 Wiki 的解釋

Compares the two version numbers given by version1 and version2. Returns -1 if version1 is an earlier version than version2, 0 if they are equal, and 1 if version1 is later than version2.


實際上的用法可以看 tcllib 中 zipfile::decode module。

if {[package vcompare $tcl_patchLevel "8.6"] < 0} {
  # Only needed pre-8.6
  package require Trf                       ; # Wrapper to zlib
  package require zlibtcl                   ; # Zlib usage. No commands, access through Trf
  set ::zipfile::decode::native_zip_functs 0
} else {
  set ::zipfile::decode::native_zip_functs 1
}

也就是如果 >= 8.6,使用內建的 zlib,如果小於就使用 Trf 與 zlibtcl。

接下來我檢查了一下 zipfile::decode module,發現 zipfile::decode module 並沒有修改完全,::zipfile::decode::GetFile 中沒有使用 native_zip_functs 來選擇要使用的 command。下面是修正的 patch。

     deflate {
         go     $fd(fileloc)
         nbytes $fd(csize)
-        return [zip -mode decompress -nowrap 1 -- [getval]]
+        if {$::zipfile::decode::native_zip_functs} {
+          return [zlib inflate [getval]]
+        } else {
+          return [zip -mode decompress -nowrap 1 -- [getval]]
+        }
     }
     default {
         Error "Unable to handle file \"$src\" compressed with method \"$fd(cm)\"" \



我已經在 Tcl Library Source Repository 開了一個 Ticket 來記錄這件事。

2017-04-14

tclmpi v1.0

目前在 Ubuntu 14.04 上測試過。

Ubuntu 14.04 需要安裝 openmpi,指令如下:
sudo apt-get install openmpi-bin libopenmpi-dev


然後從 https://github.com/akohlmey/tclmpi 或者是 http://sites.google.com/site/akohlmey/software/tclmpi 下載 v1.0 source code。

接下來需要修改 Makefile,讓 tcl.h 可以被正確找到。
TCLINCLUDE=-I/usr/include/tcl

修改完 Makefile 以後,執行 make,然後在 /usr/lib 建立 tclmpi1.0 目錄,將 pkgIndex.tcl, tclmpi.tcl 與 _tclmpi.so 三個檔案複製到 /usr/lib/tclmpi1.0,這樣就完成安裝。

再來使用 examples 下的 hello.tcl 來測試,如果有正確輸出,就表示安裝正確。

package require tclmpi 1.0

# initialize TclMPI
::tclmpi::init

set comm tclmpi::comm_world
set size [::tclmpi::comm_size $comm]
set rank [::tclmpi::comm_rank $comm]

puts "hello world, this is rank $rank of $size"

# close out TclMPI
::tclmpi::finalize
exit 0

2017-04-09

TclTLS-rpm-spec

TclTLS-rpm-spec
TclTLS website


參考 openSUSE 網站上的 RPM spec 以後,做一些小修改造出 1.7.11 的 RPM  檔案。看起來是可以使用,但是只有小量的測試而已,我不知道會不會有什麼問題。

然後寫一個 build.tcl 來加速整個流程。

2017-03-03

zookeepertcl

zookeepertcl

Flightaware 最近在 github 增加了 zookeepertcl,Tcl interface to Apache ZooKeeper distributed coordination system。


如果要寫一個 CP 分散式系統,就我聽過的,在一致性上目前有三個方法可以使用,實作正確的 Raft Consensus Algorithm,實作正確的 Paxos Algorithm 以及使用 Apache Zookeeper。

雖然 Apache Zookeeper 是用 Java 寫的,但是其一致性算法已經經過證明而且十分可靠,所以我想有個 Tcl interface 還是不錯的。


更新:
如果你對 Apache ZooKeeper 的測試報告有興趣,可以閱讀 Jepsen: Zookeeper

2017-02-22

tclreadline and .tclshrc

tclreadline

flightaware clone 了一份 tclreadline 的 source code。如果在 Ubuntu 14.04 下編譯,aux 目錄下會缺少東西,需要使用 libtoolize & aclocal & autoconf & automake --add-missing 來更新。

下面只是嘗試設定 tclsh 提示。

if {$tcl_interactive} {
    package require tclreadline 

    proc ::tclreadline::prompt1 {} {
        global env
        if {[catch {set pwd [pwd]} tmp]} {
            set pwd "unable to get pwd"
        }

        if [info exists env(HOME)] {
            regsub $env(HOME) $pwd "~" pwd
        }


        if [info exists env(USER)] {
            set user $env(USER)
        } else {
            set user tclsh
        }

        return "$user@[lindex [split [info hostname] "."] 0]:$pwd% "
    }

    ::tclreadline::Loop
}



更新:
同時也在 openSUSE 上測試 tclreadline,為了可以快速安裝和移除,參考了其它人的做法以後,使用 RPM spec 和 build script 來造 RPM 檔案

2017-01-20

BaseXClient-Tcl

Source code:
BaseXClient-Tcl


Tcl client for BaseX database.

BaseX 是一個 XML 資料庫,我只是用來做某些簡單的測試。大部份的功能跟官方的 client 都有點像,不過還是有做一些簡化假設,但是整體上功能應該是差不多的(應該啦)。

2017-01-10

print tcl_platform info

使用 parray 列出來 tcl_platform 的資料:

parray tcl_platform

tcl_platform is an associative array whose elements contain information about the platform on which the application is running, such as the name of the operating system, its current release number, and the machine's instruction set.

2017-01-05

tkvlc: A demo to embed libVLC to Tk toolkit widget

Source code:
tkvlc


使用 OpenGL with C and Tcl/Tk 的方法,將 libVLC 內嵌在 Tk frame widget 內播放影片的方法。

我只有在 openSUSE 上測試。用來驗證這個方法的可行性。


更新:
有些說明沒有寫好,我重新推一個上去 Github。

更新:
會在 Windows build fail,更新 source code。

2017/01/09 更新:
使用 libvlc_media_get_duration 讀取影片的長度。如果在 libvlc_media_player_set_media 之後沒有播放前就讀取,讀到的長度會是 0。

解決的方法有二個,一個是 play 以後 stop,再讀取就會是正確值(就是要先做一小段播放的動作)。一個是 set_media 之後呼叫 libvlc_media_parse,在分析以後 meta info 就會被填寫為正確值,那之後取得的資料就會是正確的。

2017/01/09 更新:
增加更多的 command,並且更新 command 的組織方式。
將版本更新為 v0.3。

2017-01-04

OpenGL with C and Tcl/Tk

OpenGL with C and Tcl/Tk


很有趣的是,這篇文章其實指出了一個很有用的用法,就是拿到 HWND/XWindow ID 以後,我們就可以內嵌一些內容到一個 Tk 的 frame 內。

關鍵是 It involves making a frame widget in Tcl, and then passing its HWND to C to initialize the OpenGL rendering context.


而我猜不只是 OpenGL,具有類似方法的函式庫也可以這樣使用。

因為沒有真的實作過,所以還不確定這個技巧的可用性有多高,但是我直覺認為,這個方法可以結合不少外部函式庫來使用。