summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/bee
diff options
context:
space:
mode:
authorWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
committerWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
commitea28451286d3ea4a772fa174483f9a7a66bb1ab3 (patch)
tree6ee9d8a7848333a7ceeee3b13d492e40225f8b86 /tcllib/modules/bee
parentb5ca09bae0d6a1edce939eea03594dd56383f2c8 (diff)
parent7c621da28f07e449ad90c387344f07a453927569 (diff)
downloadblt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.zip
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.gz
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.bz2
Merge commit '7c621da28f07e449ad90c387344f07a453927569' as 'tcllib'
Diffstat (limited to 'tcllib/modules/bee')
-rw-r--r--tcllib/modules/bee/ChangeLog116
-rw-r--r--tcllib/modules/bee/bee.bench79
-rw-r--r--tcllib/modules/bee/bee.man343
-rw-r--r--tcllib/modules/bee/bee.pcx81
-rw-r--r--tcllib/modules/bee/bee.tcl990
-rw-r--r--tcllib/modules/bee/bee.test384
-rw-r--r--tcllib/modules/bee/example.torrentbin0 -> 22267 bytes
-rw-r--r--tcllib/modules/bee/pkgIndex.tcl4
8 files changed, 1997 insertions, 0 deletions
diff --git a/tcllib/modules/bee/ChangeLog b/tcllib/modules/bee/ChangeLog
new file mode 100644
index 0000000..630a16c
--- /dev/null
+++ b/tcllib/modules/bee/ChangeLog
@@ -0,0 +1,116 @@
+2013-02-01 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.15 ========================
+ *
+
+2011-12-13 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.14 ========================
+ *
+
+2011-01-24 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.13 ========================
+ *
+
+2009-12-07 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.12 ========================
+ *
+
+2008-12-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.11.1 ========================
+ *
+
+2008-10-16 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.11 ========================
+ *
+
+2008-06-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * bee.pcx: New file. Syntax definitions for the public commands of
+ the bee package.
+
+2007-09-12 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.10 ========================
+ *
+
+2007-03-19 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * bee.man: Fixed all warnings due to use of now deprecated
+ commands. Added a section about how to give feedback.
+
+2006-10-03 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.9 ========================
+ *
+
+2006-01-22 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * bee.test: More boilerplate simplified via use of test support.
+
+2006-01-19 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * bee.test: Hooked into the new common test support code.
+
+2005-10-21 Andreas Kupries <andreask@activestate.com>
+
+ * bee.test: Fixed typo.
+
+2005-10-18 Andreas Kupries <andreask@activestate.com>
+
+ * bee.bench: New file, benchmarks, only basics for now.
+
+2005-10-06 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.8 ========================
+ *
+
+2004-10-05 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ *
+ * Released and tagged Tcllib 1.7 ========================
+ *
+
+2004-09-27 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * bee.man: Cleared up version confusion. This package
+ * bee.test: definitely requires 8.4. Fixed in package
+ * pkgIndex.tcl: index, docs, added boilerplate abort to
+ testsuite.
+
+ * bee.test: Fixed problem with testsuite, cannot use viewFile,
+ does not do binary.
+
+2004-08-15 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+ * bee.tcl: Typo police.
+ * bee.man:
+ * bee.test:
+
+2004-06-23 Andreas Kupries <andreas_kupries@users.sourceforge.bet>
+
+ * bee.man: Polished the documentation.
+
+2004-06-21 Andreas Kupries <andreas_kupries@users.sourceforge.bet>
+
+ * bee.tcl: Completed the implementation.
+ * bee.man: Completed documentation.
+ * bee.test: Completed testsuite.
+
+2004-06-18 Andreas Kupries <andreas_kupries@users.sourceforge.bet>
+
+ * New module: BEE de- and encoding. BEE is the serialization
+ format used by BitTorrent for its data and protocol messages.
diff --git a/tcllib/modules/bee/bee.bench b/tcllib/modules/bee/bee.bench
new file mode 100644
index 0000000..2fb55fe
--- /dev/null
+++ b/tcllib/modules/bee/bee.bench
@@ -0,0 +1,79 @@
+# -*- tcl -*-
+# Tcl Benchmark File
+#
+# This file contains a number of benchmarks for the 'bee' module.
+# This allow developers to monitor/gauge/track package performance.
+#
+# (c) 2005 Andreas Kupries <andreas_kupries@users.sourceforge.net>
+
+# We need at least version 8.2 for the package and thus the
+# benchmarks.
+
+if {![package vsatisfies [package provide Tcl] 8.4]} {
+ return
+}
+
+# ### ### ### ######### ######### ######### ###########################
+## Setting up the environment ...
+
+package forget bee
+catch {namespace delete ::bee}
+source [file join [file dirname [info script]] bee.tcl]
+
+# ### ### ### ######### ######### ######### ###########################
+## Benchmarks.
+
+foreach n {
+ -10 -100 -1000 -10000 -100000 -1000000
+ 0
+ 10 100 1000 10000 100000 1000000
+} {
+ bench -desc "BEE encode Number $n" -body {
+ bee::encodeNumber $n
+ }
+
+ bench -desc "BEE decode Number $n" -pre {
+ set str [bee::encodeNumber $n]
+ } -body {
+ bee::decode $str
+ } -post {
+ unset str
+ }
+
+ bench -desc "BEE decodeIndices Number $n" -pre {
+ set str [bee::encodeNumber $n]
+ } -body {
+ bee::decodeIndices $str
+ } -post {
+ unset str
+ }
+}
+
+foreach n {10 100 1000 10000} {
+ bench -desc "BEE encode String $n" -pre {
+ set str [string repeat X $n]
+ } -body {
+ bee::encodeString $str
+ } -post {
+ unset str
+ }
+
+ bench -desc "BEE decode String $n" -pre {
+ set str [bee::encodeString [string repeat X $n]]
+ } -body {
+ bee::decode $str
+ } -post {
+ unset str
+ }
+
+ bench -desc "BEE decodeIndices String $n" -pre {
+ set str [bee::encodeString [string repeat X $n]]
+ } -body {
+ bee::decodeIndices $str
+ } -post {
+ unset str
+ }
+}
+
+# ### ### ### ######### ######### ######### ###########################
+## Complete
diff --git a/tcllib/modules/bee/bee.man b/tcllib/modules/bee/bee.man
new file mode 100644
index 0000000..c6c4781
--- /dev/null
+++ b/tcllib/modules/bee/bee.man
@@ -0,0 +1,343 @@
+[comment {-*- tcl -*- doctools manpage}]
+[manpage_begin bee n 0.1]
+[keywords bee]
+[keywords BitTorrent]
+[keywords bittorrent]
+[keywords serialization]
+[keywords torrent]
+[copyright {2004 Andreas Kupries <andreas_kupries@users.sourceforge.net>}]
+[moddesc {BitTorrent}]
+[titledesc {BitTorrent Serialization Format Encoder/Decoder}]
+[category Networking]
+[require Tcl 8.4]
+[require bee [opt 0.1]]
+[description]
+[para]
+
+The [package bee] package provides de- and encoder commands for data
+in bencoding (speak 'bee'), the serialization format for data and
+messages used by the BitTorrent application.
+
+[para]
+
+[section {PUBLIC API}]
+
+[subsection ENCODER]
+
+The package provides one encoder command for each of the basic forms,
+and two commands per container, one taking a proper tcl data structure
+to encode in the container, the other taking the same information as
+several arguments.
+
+[para]
+[list_begin definitions]
+
+[call [cmd ::bee::encodeString] [arg string]]
+
+Returns the bee-encoding of the [arg string].
+
+[call [cmd ::bee::encodeNumber] [arg integer]]
+
+Returns the bee-encoding of the [arg integer] number.
+
+[call [cmd ::bee::encodeListArgs] [arg value]...]
+
+Takes zero or more bee-encoded values and returns the bee-encoding of
+their list.
+
+[call [cmd ::bee::encodeList] [arg list]]
+
+Takes a list of bee-encoded values and returns the bee-encoding of the
+list.
+
+[call [cmd ::bee::encodeDictArgs] [arg key] [arg value]...]
+
+Takes zero or more pairs of keys and values and returns the
+bee-encoding of the dictionary they form. The values are expected to
+be already bee-encoded, but the keys must not be. Their encoding will
+be done by the command itself.
+
+[call [cmd ::bee::encodeDict] [arg dict]]
+
+Takes a dictionary list of string keys and bee-encoded values and
+returns the bee-encoding of the list. Note that the keys in the input
+must not be bee-encoded already. This will be done by the command
+itself.
+
+[list_end]
+[para]
+
+[subsection DECODER]
+
+The package provides two main decoder commands, one for decoding a
+string expected to contain a complete data structure, the other for
+the incremental decoding of bee-values arriving on a channel. The
+latter command is asynchronous and provides the completed decoded
+values to the user through a command callback.
+
+[para]
+[list_begin definitions]
+
+[call [cmd ::bee::decode] [arg string] [opt [arg endvar]] [opt [arg start]]]
+
+Takes the bee-encoding in the string and returns one decoded value. In
+the case of this being a container all contained values are decoded
+recursively as well and the result is a properly nested tcl list
+and/or dictionary.
+
+[para]
+
+If the optional [arg endvar] is set then it is the name of a variable
+to store the index of the first character [emph after] the decoded
+value into. In other words, if the string contains more than one value
+then [arg endvar] can be used to obtain the position of the bee-value
+after the bee-value currently decoded. together with [arg start], see
+below, it is possible to iterate over the string to extract all
+contained values.
+
+[para]
+
+The optional [arg start] index defaults to [const 0], i.e. the
+beginning of the string. It is the index of the first character of the
+bee-encoded value to extract.
+
+[call [cmd ::bee::decodeIndices] [arg string] [opt [arg endvar]] [opt [arg start]]]
+
+Takes the same arguments as [cmd ::bee::decode] and returns the same
+information in [arg endvar]. The result however is different. Instead
+of the tcl value contained in the [arg string] it returns a list
+describing the value with respect to type and location (indices for
+the first and last character of the bee-value). In case of a container
+the structure also contains the same information for all the embedded
+values.
+
+[para]
+
+Formally the results for the various types of bee-values are:
+
+[list_begin definitions]
+[def string]
+
+A list containing three elements:
+
+[list_begin itemized]
+[item]
+The constant string [const string], denoting the type of the value.
+
+[item]
+An integer number greater than or equal to zero. This is the index of
+the first character of the bee-value in the input [arg string].
+
+[item]
+An integer number greater than or equal to zero. This is the index of
+the last character of the bee-value in the input [arg string].
+
+[list_end]
+[para]
+
+[emph Note] that this information is present in the results for all
+four types of bee-values, with only the first element changing
+according to the type of the value.
+
+[def integer]
+
+The result is like for strings, except that the type element contains
+the constant string [const integer].
+
+[def list]
+
+The result is like before, with two exceptions: One, the type element
+contains the constant string [const list]. And two, the result
+actually contains four elements. The last element is new, and contains
+the index data as described here for all elements of the bee-list.
+
+[def dictionary]
+
+The result is like for strings, except that the type element contains
+the constant string [const dict]. A fourth element is present as well,
+with a slightly different structure than for lists. The element is a
+dictionary mapping from the strings keys of the bee-dictionary to a
+list containing two elements. The first of them is the index
+information for the key, and the second element is the index
+information for the value the key maps to. This structure is the only
+which contains not only index data, but actual values from the
+bee-string. While the index information of the keys is unique enough,
+i.e. serviceable as keys, they are not easy to navigate when trying to
+find particular element. Using the actual keys makes this much easier.
+
+[list_end]
+[para]
+
+[call [cmd ::bee::decodeChannel] [arg chan] \
+ [option -command] [arg cmdprefix] \
+ [opt [option -exact]] \
+ [opt "[option -prefix] [arg data]"] \
+]
+
+The command creates a decoder for a series of bee-values arriving on
+the channel [arg chan] and returns its handle. This handle can be used
+to remove the decoder again.
+
+Setting up another bee decoder on [arg chan] while a bee decoder is
+still active will fail with an error message.
+
+[para]
+[list_begin definitions]
+[def [option -command]]
+
+The command prefix [arg cmdprefix] specified by the [emph required]
+option [option -command] is used to report extracted values and
+exceptional situations (error, and EOF on the channel).
+
+The callback will be executed at the global level of the interpreter,
+with two or three arguments. The exact call signatures are
+
+[para]
+[list_begin definitions]
+[call [cmd cmdprefix] [method eof] [arg token]]
+
+The decoder has reached eof on the channel [arg chan]. No further
+invocations of the callback will be made after this. The channel has
+already been closed at the time of the call, and the [arg token] is
+not valid anymore as well.
+
+[call [cmd cmdprefix] [method error] [arg token] [arg message]]
+
+The decoder encountered an error, which is not eof. For example a
+malformed bee-value. The [arg message] provides details about the
+error. The decoder token is in the same state as for eof,
+i.e. invalid. The channel however is kept open.
+
+[call [cmd cmdprefix] [method value] [arg token] [arg value]]
+
+The decoder received and successfully decoded a bee-value.
+
+The format of the equivalent tcl [arg value] is the same as returned
+by [cmd ::bee::decode]. The channel is still open and the decoder
+token is valid. This means that the callback is able to remove the
+decoder.
+
+[list_end]
+[para]
+
+[def [option -exact]]
+
+By default the decoder assumes that the remainder of the data in the
+channel consists only of bee-values, and reads as much as possible per
+event, without regard for boundaries between bee-values. This means
+that if the the input contains non-bee data after a series of
+bee-value the beginning of that data may be lost because it was
+already read by the decoder, but not processed.
+
+[para]
+
+The [option -exact] was made for this situation. When specified the
+decoder will take care to not read any characters behind the currently
+processed bee-value, so that any non-bee data is kept in the channel
+for further processing after removal of the decoder.
+
+[para]
+
+[def [option -prefix]]
+
+If this option is specified its value is assumed to be the beginning
+of the bee-value and used to initialize the internal decoder
+buffer. This feature is required if the creator of the decoder used
+data from the channel to determine if it should create the decoder or
+not. Without the option this data would be lost to the decoding.
+
+[list_end]
+[para]
+
+[call [cmd ::bee::decodeCancel] [arg token]]
+
+This command cancels the decoder set up by [cmd ::bee::decodeChannel]
+and represented by the handle [arg token].
+
+[call [cmd ::bee::decodePush] [arg token] [arg string]]
+
+This command appends the [arg string] to the internal decoder
+buffer. It is the runtime equivalent of the option [option -prefix] of
+[cmd ::bee::decodeChannel]. Use it to push data back into the decoder
+when the [method value] callback used data from the channel to
+determine if it should decode another bee-value or not.
+
+[list_end]
+[para]
+
+[section {FORMAT DEFINITION}]
+
+Data in the bee serialization format is constructed from two basic
+forms, and two container forms. The basic forms are strings and
+integer numbers, and the containers are lists and dictionaries.
+
+[para]
+[list_begin definitions]
+[def "String [arg S]"]
+
+A string [arg S] of length [arg L] is encoded by the string
+
+"[arg L][const :][arg S]", where the length is written out in textual
+form.
+
+[def "Integer [arg N]"]
+
+An integer number [arg N] is encoded by the string
+
+"[const i][arg N][const e]".
+
+[def "List [arg v1] ... [arg vn]"]
+
+A list of the values [arg v1] to [arg vn] is encoded by the string
+
+"[const l][arg BV1]...[arg BVn][const e]"
+
+where "BV[var i]" is the bee-encoding of the value "v[var i]".
+
+[def "Dict [arg k1] -> [arg v1] ..."]
+
+A dictionary mapping the string key [arg k][var i] to the value
+
+[arg v][var i], for [var i] in [const 1] ... [var n]
+is encoded by the string
+
+"[const d][arg BK][var i][arg BV][ var i]...[const e]"
+
+for i in [const 1] ... [var n], where "BK[var i]" is the bee-encoding
+of the key string "k[var i]". and "BV[var i]" is the bee-encoding of
+the value "v[var i]".
+
+[para]
+
+[emph Note]: The bee-encoding does not retain the order of the keys in
+the input, but stores in a sorted order. The sorting is done for the
+"raw strings".
+
+[list_end]
+[para]
+
+Note that the type of each encoded item can be determined immediately
+from the first character of its representation:
+
+[para]
+[list_begin definitions]
+[def i]
+Integer.
+[def l]
+List.
+[def d]
+Dictionary.
+[def "[lb]0-9[rb]"]
+String.
+[list_end]
+[para]
+
+By wrapping an integer number into [const i]...[const e] the format
+makes sure that they are different from strings, which all begin with
+a digit.
+
+[section EXAMPLES]
+
+[vset CATEGORY bee]
+[include ../doctools2base/include/feedback.inc]
+[manpage_end]
diff --git a/tcllib/modules/bee/bee.pcx b/tcllib/modules/bee/bee.pcx
new file mode 100644
index 0000000..b8cf178
--- /dev/null
+++ b/tcllib/modules/bee/bee.pcx
@@ -0,0 +1,81 @@
+# -*- tcl -*- bee.pcx
+# Syntax of the commands provided by package bee.
+#
+# For use by TclDevKit's static syntax checker (v4.1+).
+# See http://www.activestate.com/solutions/tcl/
+# See http://aspn.activestate.com/ASPN/docs/Tcl_Dev_Kit/4.0/Checker.html#pcx_api
+# for the specification of the format of the code in this file.
+#
+
+package require pcx
+pcx::register bee
+pcx::tcldep 0.1 needs tcl 8.4
+
+namespace eval ::bee {}
+
+pcx::message needCommand {Required -command is missing} err
+
+pcx::check 0.1 std ::bee::decode \
+ {checkSimpleArgs 1 3 {
+ checkWord
+ checkVarNameWrite
+ checkWholeNum
+ }}
+pcx::check 0.1 std ::bee::decodeCancel \
+ {checkSimpleArgs 1 1 {
+ checkWord
+ }}
+pcx::check 0.1 std ::bee::decodeChannel \
+ {checkSimpleArgs 1 -1 {
+ checkChannelID
+ {checkConstrained {checkSequence {
+ {checkSwitches exact {
+ {-command {checkSetConstraint cmd {checkProcCall 3}}}
+ {-exaxt}
+ {-prefix checkWord}
+ } {checkAtEnd}}
+ {checkConstraint {
+ {!cmd {warn bee::needCommand {} checkNOP}}
+ } {checkNOP}}
+ }}}
+ }}
+pcx::check 0.1 std ::bee::decodeIndices \
+ {checkSimpleArgs 1 3 {
+ checkWord
+ checkVarNameWrite
+ checkWholeNum
+ }}
+pcx::check 0.1 std ::bee::decodePush \
+ {checkSimpleArgs 2 2 {
+ checkWord
+ checkWord
+ }}
+pcx::check 0.1 std ::bee::encodeDict \
+ {checkSimpleArgs 1 1 {
+ checkDict
+ }}
+pcx::check 0.1 std ::bee::encodeDictArgs \
+ {checkSimpleArgsModNk 0 2 {
+ checkWord
+ checkWord
+ }}
+pcx::check 0.1 std ::bee::encodeList \
+ {checkSimpleArgs 1 1 {
+ checkList
+ }}
+pcx::check 0.1 std ::bee::encodeListArgs \
+ {checkSimpleArgs 0 -1 {
+ checkWord
+ }}
+pcx::check 0.1 std ::bee::encodeNumber \
+ {checkSimpleArgs 1 1 {
+ checkInt
+ }}
+pcx::check 0.1 std ::bee::encodeString \
+ {checkSimpleArgs 1 1 {
+ checkWord
+ }}
+
+# Initialization via pcx::init.
+# Use a ::bee::init procedure for non-standard initialization.
+pcx::complete
diff --git a/tcllib/modules/bee/bee.tcl b/tcllib/modules/bee/bee.tcl
new file mode 100644
index 0000000..6eb53c0
--- /dev/null
+++ b/tcllib/modules/bee/bee.tcl
@@ -0,0 +1,990 @@
+# bee.tcl --
+#
+# BitTorrent Bee de- and encoder.
+#
+# Copyright (c) 2004 by Andreas Kupries <andreas_kupries@users.sourceforge.net>
+# See the file license.terms.
+
+package require Tcl 8.4
+
+namespace eval ::bee {
+ # Encoder commands
+ namespace export \
+ encodeString encodeNumber \
+ encodeListArgs encodeList \
+ encodeDictArgs encodeDict
+
+ # Decoder commands.
+ namespace export \
+ decode \
+ decodeChannel \
+ decodeCancel \
+ decodePush
+
+ # Channel decoders, reference to state information, keyed by
+ # channel handle.
+
+ variable bee
+ array set bee {}
+
+ # Counter for generation of names for the state variables.
+
+ variable count 0
+
+ # State information for the channel decoders.
+
+ # stateN, with N an integer number counting from 0 on up.
+ # ...(chan) Handle of channel the decoder is for.
+ # ...(cmd) Command prefix, completion callback
+ # ...(exact) Boolean flag, set for exact processing.
+ # ...(read) Buffer for new characters to process.
+ # ...(type) Type of current value (integer, string, list, dict)
+ # ...(value) Buffer for assembling the current value.
+ # ...(pend) Stack of pending 'value' buffers, for nested
+ # containers.
+ # ...(state) Current state of the decoding state machine.
+
+ # States of the finite automaton ...
+ # intro - One char, type of value, or 'e' as stop of container.
+ # signum - sign or digit, for integer.
+ # idigit - digit, for integer, or 'e' as stop
+ # ldigit - digit, for length of string, or :
+ # data - string data, 'get' characters.
+ # Containers via 'pend'.
+
+ #Debugging help, nesting level
+ #variable X 0
+}
+
+
+# ::bee::encodeString --
+#
+# Encode a string to bee-format.
+#
+# Arguments:
+# string The string to encode.
+#
+# Results:
+# The bee-encoded form of the string.
+
+proc ::bee::encodeString {string} {
+ return "[string length $string]:$string"
+}
+
+
+# ::bee::encodeNumber --
+#
+# Encode an integer number to bee-format.
+#
+# Arguments:
+# num The integer number to encode.
+#
+# Results:
+# The bee-encoded form of the integer number.
+
+proc ::bee::encodeNumber {num} {
+ if {![string is integer -strict $num]} {
+ return -code error "Expected integer number, got \"$num\""
+ }
+
+ # The reformatting deals with hex, octal and other tcl
+ # representation of the value. In other words we normalize the
+ # string representation of the input value.
+
+ set num [format %d $num]
+ return "i${num}e"
+}
+
+
+# ::bee::encodeList --
+#
+# Encode a list of bee-coded values to bee-format.
+#
+# Arguments:
+# list The list to encode.
+#
+# Results:
+# The bee-encoded form of the list.
+
+proc ::bee::encodeList {list} {
+ return "l[join $list ""]e"
+}
+
+
+# ::bee::encodeListArgs --
+#
+# Encode a variable list of bee-coded values to bee-format.
+#
+# Arguments:
+# args The values to encode.
+#
+# Results:
+# The bee-encoded form of the list of values.
+
+proc ::bee::encodeListArgs {args} {
+ return [encodeList $args]
+}
+
+
+# ::bee::encodeDict --
+#
+# Encode a dictionary of keys and bee-coded values to bee-format.
+#
+# Arguments:
+# dict The dictionary to encode.
+#
+# Results:
+# The bee-encoded form of the dictionary.
+
+proc ::bee::encodeDict {dict} {
+ if {([llength $dict] % 2) == 1} {
+ return -code error "Expected even number of elements, got \"[llength $dict]\""
+ }
+ set temp [list]
+ foreach {k v} $dict {
+ lappend temp [list $k $v]
+ }
+ set res "d"
+ foreach item [lsort -index 0 $temp] {
+ foreach {k v} $item break
+ append res [encodeString $k]$v
+ }
+ append res "e"
+ return $res
+}
+
+
+# ::bee::encodeDictArgs --
+#
+# Encode a variable dictionary of keys and bee-coded values to bee-format.
+#
+# Arguments:
+# args The keys and values to encode.
+#
+# Results:
+# The bee-encoded form of the dictionary.
+
+proc ::bee::encodeDictArgs {args} {
+ return [encodeDict $args]
+}
+
+
+# ::bee::decode --
+#
+# Decode a bee-encoded value and returns the embedded tcl
+# value. For containers this recurses into the contained value.
+#
+# Arguments:
+# value The string containing the bee-encoded value to decode.
+# evar Optional. If set the name of the variable to store the
+# index of the first character after the decoded value to.
+# start Optional. If set the index of the first character of the
+# value to decode. Defaults to 0, i.e. the beginning of the
+# string.
+#
+# Results:
+# The tcl value embedded in the encoded string.
+
+proc ::bee::decode {value {evar {}} {start 0}} {
+ #variable X
+ #puts -nonewline "[string repeat " " $X]decode @$start" ; flush stdout
+
+ if {$evar ne ""} {upvar 1 $evar end} else {set end _}
+
+ if {[string length $value] < ($start+2)} {
+ # This checked that the 'start' index is still in the string,
+ # and the end of the value most likely as well. Note that each
+ # encoded value consists of at least two characters (the
+ # bracketing characters for integer, list, and dict, and for
+ # string at least one digit length and the colon).
+
+ #puts \t[string length $value]\ <\ ($start+2)
+ return -code error "String not large enough for value"
+ }
+
+ set type [string index $value $start]
+
+ #puts -nonewline " $type=" ; flush stdout
+
+ if {$type eq "i"} {
+ # Extract integer
+ #puts -nonewline integer ; flush stdout
+
+ incr start ; # Skip over intro 'i'.
+ set end [string first e $value $start]
+ if {$end < 0} {
+ return -code error "End of integer number not found"
+ }
+ incr end -1 ; # Get last character before closing 'e'.
+ set num [string range $value $start $end]
+ if {
+ [regexp {^-0+$} $num] ||
+ ![string is integer -strict $num] ||
+ (([string length $num] > 1) && [string match 0* $num])
+ } {
+ return -code error "Expected integer number, got \"$num\""
+ }
+ incr end 2 ; # Step after closing 'e' to the beginning of
+ # ........ ; # the next bee-value behind the current one.
+
+ #puts " ($num) @$end"
+ return $num
+
+ } elseif {($type eq "l") || ($type eq "d")} {
+ #puts -nonewline $type\n ; flush stdout
+
+ # Extract list or dictionary, recursively each contained
+ # element. From the perspective of the decoder this is the
+ # same, the tcl representation of both is a list, and for a
+ # dictionary keys and values are also already in the correct
+ # order.
+
+ set result [list]
+ incr start ; # Step over intro 'e' to beginning of the first
+ # ........ ; # contained value, or behind the container (if
+ # ........ ; # empty).
+
+ set end $start
+ #incr X
+ while {[string index $value $start] ne "e"} {
+ lappend result [decode $value end $start]
+ set start $end
+ }
+ #incr X -1
+ incr end
+
+ #puts "[string repeat " " $X]($result) @$end"
+
+ if {$type eq "d" && ([llength $result] % 2 == 1)} {
+ return -code error "Dictionary has to be of even length"
+ }
+ return $result
+
+ } elseif {[string match {[0-9]} $type]} {
+ #puts -nonewline string ; flush stdout
+
+ # Extract string. First the length, bounded by a colon, then
+ # the appropriate number of characters.
+
+ set end [string first : $value $start]
+ if {$end < 0} {
+ return -code error "End of string length not found"
+ }
+ incr end -1
+ set length [string range $value $start $end]
+ incr end 2 ;# Skip to beginning of the string after the colon
+
+ if {![string is integer -strict $length]} {
+ return -code error "Expected integer number for string length, got \"$length\""
+ } elseif {$length < 0} {
+ # This cannot happen. To happen "-" has to be first character,
+ # and this is caught as unknown bee-type.
+ return -code error "Illegal negative string length"
+ } elseif {($end + $length) > [string length $value]} {
+ return -code error "String not large enough for value"
+ }
+
+ #puts -nonewline \[$length\] ; flush stdout
+ if {$length > 0} {
+ set start $end
+ incr end $length
+ incr end -1
+ set result [string range $value $start $end]
+ incr end
+ } else {
+ set result ""
+ }
+
+ #puts " ($result) @$end"
+ return $result
+
+ } else {
+ return -code error "Unknown bee-type \"$type\""
+ }
+}
+
+# ::bee::decodeIndices --
+#
+# Similar to 'decode', but does not return the decoded tcl values,
+# but a structure containing the start- and end-indices for all
+# values in the structure.
+#
+# Arguments:
+# value The string containing the bee-encoded value to decode.
+# evar Optional. If set the name of the variable to store the
+# index of the first character after the decoded value to.
+# start Optional. If set the index of the first character of the
+# value to decode. Defaults to 0, i.e. the beginning of the
+# string.
+#
+# Results:
+# The structure of the value, with indices and types for all
+# contained elements.
+
+proc ::bee::decodeIndices {value {evar {}} {start 0}} {
+ #variable X
+ #puts -nonewline "[string repeat " " $X]decode @$start" ; flush stdout
+
+ if {$evar ne ""} {upvar 1 $evar end} else {set end _}
+
+ if {[string length $value] < ($start+2)} {
+ # This checked that the 'start' index is still in the string,
+ # and the end of the value most likely as well. Note that each
+ # encoded value consists of at least two characters (the
+ # bracketing characters for integer, list, and dict, and for
+ # string at least one digit length and the colon).
+
+ #puts \t[string length $value]\ <\ ($start+2)
+ return -code error "String not large enough for value"
+ }
+
+ set type [string index $value $start]
+
+ #puts -nonewline " $type=" ; flush stdout
+
+ if {$type eq "i"} {
+ # Extract integer
+ #puts -nonewline integer ; flush stdout
+
+ set begin $start
+
+ incr start ; # Skip over intro 'i'.
+ set end [string first e $value $start]
+ if {$end < 0} {
+ return -code error "End of integer number not found"
+ }
+ incr end -1 ; # Get last character before closing 'e'.
+ set num [string range $value $start $end]
+ if {
+ [regexp {^-0+$} $num] ||
+ ![string is integer -strict $num] ||
+ (([string length $num] > 1) && [string match 0* $num])
+ } {
+ return -code error "Expected integer number, got \"$num\""
+ }
+ incr end
+ set stop $end
+ incr end 1 ; # Step after closing 'e' to the beginning of
+ # ........ ; # the next bee-value behind the current one.
+
+ #puts " ($num) @$end"
+ return [list integer $begin $stop]
+
+ } elseif {$type eq "l"} {
+ #puts -nonewline $type\n ; flush stdout
+
+ # Extract list, recursively each contained element.
+
+ set result [list]
+
+ lappend result list $start @
+
+ incr start ; # Step over intro 'e' to beginning of the first
+ # ........ ; # contained value, or behind the container (if
+ # ........ ; # empty).
+
+ set end $start
+ #incr X
+
+ set contained [list]
+ while {[string index $value $start] ne "e"} {
+ lappend contained [decodeIndices $value end $start]
+ set start $end
+ }
+ lappend result $contained
+ #incr X -1
+ set stop $end
+ incr end
+
+ #puts "[string repeat " " $X]($result) @$end"
+
+ return [lreplace $result 2 2 $stop]
+
+ } elseif {($type eq "l") || ($type eq "d")} {
+ #puts -nonewline $type\n ; flush stdout
+
+ # Extract dictionary, recursively each contained element.
+
+ set result [list]
+
+ lappend result dict $start @
+
+ incr start ; # Step over intro 'e' to beginning of the first
+ # ........ ; # contained value, or behind the container (if
+ # ........ ; # empty).
+
+ set end $start
+ set atkey 1
+ #incr X
+
+ set contained [list]
+ set val [list]
+ while {[string index $value $start] ne "e"} {
+ if {$atkey} {
+ lappend contained [decode $value {} $start]
+ lappend val [decodeIndices $value end $start]
+ set atkey 0
+ } else {
+ lappend val [decodeIndices $value end $start]
+ lappend contained $val
+ set val [list]
+ set atkey 1
+ }
+ set start $end
+ }
+ lappend result $contained
+ #incr X -1
+ set stop $end
+ incr end
+
+ #puts "[string repeat " " $X]($result) @$end"
+
+ if {[llength $result] % 2 == 1} {
+ return -code error "Dictionary has to be of even length"
+ }
+ return [lreplace $result 2 2 $stop]
+
+ } elseif {[string match {[0-9]} $type]} {
+ #puts -nonewline string ; flush stdout
+
+ # Extract string. First the length, bounded by a colon, then
+ # the appropriate number of characters.
+
+ set end [string first : $value $start]
+ if {$end < 0} {
+ return -code error "End of string length not found"
+ }
+ incr end -1
+ set length [string range $value $start $end]
+ incr end 2 ;# Skip to beginning of the string after the colon
+
+ if {![string is integer -strict $length]} {
+ return -code error "Expected integer number for string length, got \"$length\""
+ } elseif {$length < 0} {
+ # This cannot happen. To happen "-" has to be first character,
+ # and this is caught as unknown bee-type.
+ return -code error "Illegal negative string length"
+ } elseif {($end + $length) > [string length $value]} {
+ return -code error "String not large enough for value"
+ }
+
+ #puts -nonewline \[$length\] ; flush stdout
+ incr end -1
+ if {$length > 0} {
+ incr end $length
+ set stop $end
+ } else {
+ set stop $end
+ }
+ incr end
+
+ #puts " ($result) @$end"
+ return [list string $start $stop]
+
+ } else {
+ return -code error "Unknown bee-type \"$type\""
+ }
+}
+
+
+# ::bee::decodeChannel --
+#
+# Attach decoder for a bee-value to a channel. See the
+# documentation for details.
+#
+# Arguments:
+# chan Channel to attach to.
+# -command cmdprefix Completion callback. Required.
+# -exact Keep running after completion.
+# -prefix data Seed for decode buffer.
+#
+# Results:
+# A token to use when referring to the decoder.
+# For example when canceling it.
+
+proc ::bee::decodeChannel {chan args} {
+ variable bee
+ if {[info exists bee($chan)]} {
+ return -code error "bee-Decoder already active for channel"
+ }
+
+ # Create state and token.
+
+ variable count
+ variable [set st state$count]
+ array set $st {}
+ set bee($chan) $st
+ upvar 0 $st state
+ incr count
+
+ # Initialize the decoder state, process the options. When
+ # encountering errors here destroy the half-baked state before
+ # throwing the message.
+
+ set state(chan) $chan
+ array set state {
+ exact 0
+ type ?
+ read {}
+ value {}
+ pend {}
+ state intro
+ get 1
+ }
+
+ while {[llength $args]} {
+ set option [lindex $args 0]
+ set args [lrange $args 1 end]
+ if {$option eq "-command"} {
+ if {![llength $args]} {
+ unset bee($chan)
+ unset state
+ return -code error "Missing value for option -command."
+ }
+ set state(cmd) [lindex $args 0]
+ set args [lrange $args 1 end]
+
+ } elseif {$option eq "-prefix"} {
+ if {![llength $args]} {
+ unset bee($chan)
+ unset state
+ return -code error "Missing value for option -prefix."
+ }
+ set state(read) [lindex $args 0]
+ set args [lrange $args 1 end]
+
+ } elseif {$option eq "-exact"} {
+ set state(exact) 1
+ } else {
+ unset bee($chan)
+ unset state
+ return -code error "Illegal option \"$option\",\
+ expected \"-command\", \"-prefix\", or \"-keep\""
+ }
+ }
+
+ if {![info exists state(cmd)]} {
+ unset bee($chan)
+ unset state
+ return -code error "Missing required completion callback."
+ }
+
+ # Set up the processing of incoming data.
+
+ fileevent $chan readable [list ::bee::Process $chan $bee($chan)]
+
+ # Return the name of the state array as token.
+ return $bee($chan)
+}
+
+# ::bee::Parse --
+#
+# Internal helper. Fileevent handler for a decoder.
+# Parses input and handles both error and eof conditions.
+#
+# Arguments:
+# token The decoder to run on its input.
+#
+# Results:
+# None.
+
+proc ::bee::Process {chan token} {
+ if {[catch {Parse $token} msg]} {
+ # Something failed. Destroy and report.
+ Command $token error $msg
+ return
+ }
+
+ if {[eof $chan]} {
+ # Having data waiting, either in the input queue, or in the
+ # output stack (of nested containers) is a failure. Report
+ # this instead of the eof.
+
+ variable $token
+ upvar 0 $token state
+
+ if {
+ [string length $state(read)] ||
+ [llength $state(pend)] ||
+ [string length $state(value)] ||
+ ($state(state) ne "intro")
+ } {
+ Command $token error "Incomplete value at end of channel"
+ } else {
+ Command $token eof
+ }
+ }
+ return
+}
+
+# ::bee::Parse --
+#
+# Internal helper. Reading from the channel and parsing the input.
+# Uses a hardwired state machine.
+#
+# Arguments:
+# token The decoder to run on its input.
+#
+# Results:
+# None.
+
+proc ::bee::Parse {token} {
+ variable $token
+ upvar 0 $token state
+ upvar 0 state(state) current
+ upvar 0 state(read) input
+ upvar 0 state(type) type
+ upvar 0 state(value) value
+ upvar 0 state(pend) pend
+ upvar 0 state(exact) exact
+ upvar 0 state(get) get
+ set chan $state(chan)
+
+ #puts Parse/$current
+
+ if {!$exact} {
+ # Add all waiting characters to the buffer so that we can process as
+ # much as is possible in one go.
+ append input [read $chan]
+ } else {
+ # Exact reading. Usually one character, but when in the data
+ # section for a string value we know for how many characters
+ # we are looking for.
+
+ append input [read $chan $get]
+ }
+
+ # We got nothing, do nothing.
+ if {![string length $input]} return
+
+
+ if {$current eq "data"} {
+ # String data, this can be done faster, as we read longer
+ # sequences of characters for this.
+ set l [string length $input]
+ if {$l < $get} {
+ # Not enough, wait for more.
+ append value $input
+ incr get -$l
+ return
+ } elseif {$l == $get} {
+ # Got all, exactly. Prepare state machine for next value.
+
+ if {[Complete $token $value$input]} return
+
+ set current intro
+ set get 1
+ set value ""
+ set input ""
+
+ return
+ } else {
+ # Got more than required (only for !exact).
+
+ incr get -1
+ if {[Complete $token $value[string range $input 0 $get]]} {return}
+
+ incr get
+ set input [string range $input $get end]
+ set get 1
+ set value ""
+ set current intro
+ # This now falls into the loop below.
+ }
+ }
+
+ set where 0
+ set n [string length $input]
+
+ #puts Parse/$n
+
+ while {$where < $n} {
+ # Hardwired state machine. Get current character.
+ set ch [string index $input $where]
+
+ #puts Parse/@$where/$current/$ch/
+ if {$current eq "intro"} {
+ # First character of a value.
+
+ if {$ch eq "i"} {
+ # Begin reading integer.
+ set type integer
+ set current signum
+ } elseif {$ch eq "l"} {
+ # Begin a list.
+ set type list
+ lappend pend list {}
+ #set current intro
+
+ } elseif {$ch eq "d"} {
+ # Begin a dictionary.
+ set type dict
+ lappend pend dict {}
+ #set current intro
+
+ } elseif {$ch eq "e"} {
+ # Close a container. Throw an error if there is no
+ # container to close.
+
+ if {![llength $pend]} {
+ return -code error "End of container outside of container."
+ }
+
+ set v [lindex $pend end]
+ set t [lindex $pend end-1]
+ set pend [lrange $pend 0 end-2]
+
+ if {$t eq "dict" && ([llength $v] % 2 == 1)} {
+ return -code error "Dictionary has to be of even length"
+ }
+
+ if {[Complete $token $v]} {return}
+ set current intro
+
+ } elseif {[string match {[0-9]} $ch]} {
+ # Begin reading a string, length section first.
+ set type string
+ set current ldigit
+ set value $ch
+
+ } else {
+ # Unknown type. Throw error.
+ return -code error "Unknown bee-type \"$ch\""
+ }
+
+ # To next character.
+ incr where
+ } elseif {$current eq "signum"} {
+ # Integer number, a minus sign, or a digit.
+ if {[string match {[-0-9]} $ch]} {
+ append value $ch
+ set current idigit
+ } else {
+ return -code error "Syntax error in integer,\
+ expected sign or digit, got \"$ch\""
+ }
+ incr where
+
+ } elseif {$current eq "idigit"} {
+ # Integer number, digit or closing 'e'.
+
+ if {[string match {[-0-9]} $ch]} {
+ append value $ch
+ } elseif {$ch eq "e"} {
+ # Integer closes. Validate and report.
+ #puts validate
+ if {
+ [regexp {^-0+$} $value] ||
+ ![string is integer -strict $value] ||
+ (([string length $value] > 1) && [string match 0* $value])
+ } {
+ return -code error "Expected integer number, got \"$value\""
+ }
+
+ if {[Complete $token $value]} {return}
+ set value ""
+ set current intro
+ } else {
+ return -code error "Syntax error in integer,\
+ expected digit, or 'e', got \"$ch\""
+ }
+ incr where
+
+ } elseif {$current eq "ldigit"} {
+ # String, length section, digit, or :
+
+ if {[string match {[-0-9]} $ch]} {
+ append value $ch
+
+ } elseif {$ch eq ":"} {
+ # Length section closes, validate,
+ # then perform data processing.
+
+ set num $value
+ if {
+ [regexp {^-0+$} $num] ||
+ ![string is integer -strict $num] ||
+ (([string length $num] > 1) && [string match 0* $num])
+ } {
+ return -code error "Expected integer number as string length, got \"$num\""
+ }
+
+ set value ""
+
+ # We may have already part of the data in
+ # memory. Process that piece before looking for more.
+
+ incr where
+ set have [expr {$n - $where}]
+ if {$num < $have} {
+ # More than enough in the buffer.
+
+ set end $where
+ incr end $num
+ incr end -1
+
+ if {[Complete $token [string range $input $where $end]]} {return}
+
+ set where $end ;# Further processing behind the string.
+ set current intro
+
+ } elseif {$num == $have} {
+ # Just enough.
+
+ if {[Complete $token [string range $input $where end]]} {return}
+
+ set where $n
+ set current intro
+ } else {
+ # Not enough. Initialize value with the data we
+ # have (after the colon) and stop processing for
+ # now.
+
+ set value [string range $input $where end]
+ set current data
+ set get $num
+ set input ""
+ return
+ }
+ } else {
+ return -code error "Syntax error in string length,\
+ expected digit, or ':', got \"$ch\""
+ }
+ incr where
+ } else {
+ # unknown state = internal error
+ return -code error "Unknown decoder state \"$current\", internal error"
+ }
+ }
+
+ set input ""
+ return
+}
+
+# ::bee::Command --
+#
+# Internal helper. Runs the decoder command callback.
+#
+# Arguments:
+# token The decoder invoking its callback
+# how Which method to invoke (value, error, eof)
+# args Arguments for the method.
+#
+# Results:
+# A boolean flag. Set if further processing has to stop.
+
+proc ::bee::Command {token how args} {
+ variable $token
+ upvar 0 $token state
+
+ #puts Report/$token/$how/$args/
+
+ set cmd $state(cmd)
+ set chan $state(chan)
+
+ # We catch the fileevents because they will fail when this is
+ # called from the 'Close'. The channel will already be gone in
+ # that case.
+
+ set stop 0
+ if {($how eq "error") || ($how eq "eof")} {
+ variable bee
+
+ set stop 1
+ fileevent $chan readable {}
+ unset bee($chan)
+ unset state
+
+ if {$how eq "eof"} {
+ #puts \tclosing/$chan
+ close $chan
+ }
+ }
+
+ lappend cmd $how $token
+ foreach a $args {lappend cmd $a}
+ uplevel #0 $cmd
+
+ if {![info exists state]} {
+ # The decoder token was killed by the callback, stop
+ # processing.
+ set stop 1
+ }
+
+ #puts /$stop/[file channels]
+ return $stop
+}
+
+# ::bee::Complete --
+#
+# Internal helper. Reports a completed value.
+#
+# Arguments:
+# token The decoder reporting the value.
+# value The value to report.
+#
+# Results:
+# A boolean flag. Set if further processing has to stop.
+
+proc ::bee::Complete {token value} {
+ variable $token
+ upvar 0 $token state
+ upvar 0 state(pend) pend
+
+ if {[llength $pend]} {
+ # The value is part of a container. Add the value to its end
+ # and keep processing.
+
+ set pend [lreplace $pend end end \
+ [linsert [lindex $pend end] end \
+ $value]]
+
+ # Don't stop.
+ return 0
+ }
+
+ # The value is at the top, report it. The callback determines if
+ # we keep processing.
+
+ return [Command $token value $value]
+}
+
+# ::bee::decodeCancel --
+#
+# Destroys the decoder referenced by the token.
+#
+# Arguments:
+# token The decoder to destroy.
+#
+# Results:
+# None.
+
+proc ::bee::decodeCancel {token} {
+ variable bee
+ variable $token
+ upvar 0 $token state
+ unset bee($state(chan))
+ unset state
+ return
+}
+
+# ::bee::decodePush --
+#
+# Push data into the decoder input buffer.
+#
+# Arguments:
+# token The decoder to extend.
+# string The characters to add.
+#
+# Results:
+# None.
+
+proc ::bee::decodePush {token string} {
+ variable $token
+ upvar 0 $token state
+ append state(read) $string
+ return
+}
+
+
+package provide bee 0.1
diff --git a/tcllib/modules/bee/bee.test b/tcllib/modules/bee/bee.test
new file mode 100644
index 0000000..4ea1c7c
--- /dev/null
+++ b/tcllib/modules/bee/bee.test
@@ -0,0 +1,384 @@
+# -*- tcl -*-
+# bee.test: tests for the bee encoding.
+#
+# Copyright (c) 2004 by Andreas Kupries <andreas_kupries@users.sourceforge.net>
+# All rights reserved.
+#
+# RCS: @(#) $Id: bee.test,v 1.9 2006/10/09 21:41:39 andreas_kupries Exp $
+
+# -------------------------------------------------------------------------
+
+source [file join \
+ [file dirname [file dirname [file join [pwd] [info script]]]] \
+ devtools testutilities.tcl]
+
+testsNeedTcl 8.4
+testsNeedTcltest 1.0
+
+testing {
+ useLocal bee.tcl bee
+}
+
+# -------------------------------------------------------------------------
+# encoder ............................................................
+
+test bee-1.0 {encoder, string} {
+ bee::encodeString ""
+} {0:}
+
+test bee-1.1 {encoder, string} {
+ bee::encodeString spam
+} {4:spam}
+
+test bee-1.2 {encoder, string, wrong#args} {
+ catch {bee::encodeString} msg
+ set msg
+} [tcltest::wrongNumArgs {bee::encodeString} {string} 1]
+
+test bee-1.3 {encoder, string, wrong#args} {
+ catch {bee::encodeString 3 4} msg
+ set msg
+} [tcltest::tooManyArgs {bee::encodeString} {string}]
+
+
+
+test bee-2.0 {encoder, integer} {
+ bee::encodeNumber 0
+} {i0e}
+
+test bee-2.1 {encoder, integer, stupid zero} {
+ bee::encodeNumber -0
+} {i0e}
+
+test bee-2.2 {encoder, integer, good octal} {
+ bee::encodeNumber 004
+} {i4e}
+
+test bee-2.3 {encoder, integer, negatives} {
+ bee::encodeNumber -5
+} {i-5e}
+
+test bee-2.4 {encoder, integer, non-numeric} {
+ catch {bee::encodeNumber spam} msg
+ set msg
+} {Expected integer number, got "spam"}
+
+test bee-2.5 {encoder, integer, bad octal} {
+ catch {bee::encodeNumber 009} msg
+ set msg
+} {Expected integer number, got "009"}
+
+test bee-2.6 {encoder, integer, hex} {
+ bee::encodeNumber 0x45
+} {i69e}
+
+test bee-2.7 {encoder, integer, wrong#args} {
+ catch {bee::encodeNumber} msg
+ set msg
+} [tcltest::wrongNumArgs {bee::encodeNumber} {num} 1]
+
+test bee-2.8 {encoder, integer, wrong#args} {
+ catch {bee::encodeNumber 3 4} msg
+ set msg
+} [tcltest::tooManyArgs {bee::encodeNumber} {num}]
+
+
+
+test bee-3.0 {encoder, list, empty} {
+ bee::encodeListArgs
+} {le}
+
+test bee-3.1 {encoder, list, empty elements} {
+ bee::encodeListArgs [bee::encodeString {}] [bee::encodeString {}]
+} {l0:0:e}
+
+test bee-3.2 {encoder, list, regular elements} {
+ bee::encodeListArgs [bee::encodeString eggs] [bee::encodeNumber 12]
+} {l4:eggsi12ee}
+
+test bee-3.3 {encoder, list, empty} {
+ bee::encodeList {}
+} {le}
+
+test bee-3.4 {encoder, list, empty elements} {
+ bee::encodeList [list [bee::encodeString {}] [bee::encodeString {}]]
+} {l0:0:e}
+
+test bee-3.5 {encoder, list, regular elements} {
+ bee::encodeList [list [bee::encodeString eggs] [bee::encodeNumber 12]]
+} {l4:eggsi12ee}
+
+test bee-3.6 {encoder, list, empty} {
+ catch {bee::encodeList} msg
+ set msg
+} [tcltest::wrongNumArgs {bee::encodeList} {list} 1]
+
+test bee-3.7 {encoder, list, empty} {
+ catch {bee::encodeList 1 2} msg
+ set msg
+} [tcltest::tooManyArgs {bee::encodeList} {list}]
+
+
+test bee-4.0 {encoder, dict, empty} {
+ bee::encodeDictArgs
+} {de}
+
+test bee-4.1 {encoder, dict, empty elements} {
+ bee::encodeDictArgs {} [bee::encodeString {}]
+} {d0:0:e}
+
+test bee-4.2 {encoder, dict, regular elements} {
+ bee::encodeDictArgs eggs [bee::encodeNumber 12]
+} {d4:eggsi12ee}
+
+test bee-4.3 {encoder, dict, empty} {
+ bee::encodeDict {}
+} {de}
+
+test bee-4.4 {encoder, dict, empty elements} {
+ bee::encodeDict [list {} [bee::encodeString {}]]
+} {d0:0:e}
+
+test bee-4.5 {encoder, dict, regular elements} {
+ bee::encodeDict [list eggs [bee::encodeNumber 12]]
+} {d4:eggsi12ee}
+
+test bee-4.6 {encoder, dict, empty} {
+ catch {bee::encodeDict} msg
+ set msg
+} [tcltest::wrongNumArgs {bee::encodeDict} {dict} 1]
+
+test bee-4.7 {encoder, dict, empty} {
+ catch {bee::encodeDict 1 2} msg
+ set msg
+} [tcltest::tooManyArgs {bee::encodeDict} {dict}]
+
+test bee-4.8 {encoder, dict, sorted keys} {
+ bee::encodeDictArgs spam [bee::encodeNumber 2] eggs [bee::encodeNumber 12]
+} {d4:eggsi12e4:spami2ee}
+
+
+# decoder ............................................................
+
+proc tick {m tok args} {
+ global res tick
+ lappend res $m $args
+ if {$m eq "eof" || $m eq "error"} {set tick 0}
+ return
+}
+
+proc tickoff {m tok args} {
+ global res tick
+ lappend res $m $args
+ bee::decodeCancel $tok
+ set tick 0
+ return
+}
+
+proc gen {name data} {
+ set path [makeFile {} $name]
+ set f [open $path w]
+ puts -nonewline $f $data
+ close $f
+ return $path
+}
+
+foreach {n bee result} {
+ 0 i0e {0 3}
+ 1 i-5e {-5 4}
+ 2 4:spam {spam 6}
+ 3 0: {{} 2}
+ 4 le {{} 2}
+ 5 l0:e {{{}} 4}
+ 6 li5ee {5 5}
+ 7 li5e4:spame {{5 spam} 11}
+ 8 de {{} 2}
+ 9 d0:0:e {{{} {}} 6}
+ 10 d0:i5ee {{{} 5} 7}
+ 11 d1:a4:spame {{a spam} 11}
+ 12 ld1:a4:spame3:egge {{{a spam} egg} 18}
+ 13 13:eggs+spam+ham {eggs+spam+ham 16}
+ 14 d1:a1:b1:c1:de {{a b c d} 14}
+} {
+ test bee-5.$n {decoder} {
+ list [bee::decode $bee end] $end
+ } $result ; # {}
+
+ test bee-6.$n {decoder, channel} {
+ set path [gen bee6.$n $bee]
+
+ set res "" ; set tick 1
+ bee::decodeChannel [open $path r] \
+ -command tick
+ vwait tick
+ removeFile bee6.$n
+ set res
+ } [list value [list [lindex $result 0]] eof {}] ; # {}
+
+ test bee-7.$n {decoder, channel} {
+ set path [gen bee7.$n $bee]
+
+ set res "" ; set tick 1
+ bee::decodeChannel [open $path r] \
+ -command tick -exact
+ vwait tick
+ removeFile bee7.$n
+ set res
+ } [list value [list [lindex $result 0]] eof {}] ; # {}
+}
+
+foreach {n bee result resultchan} {
+ 0 i-0e {Expected integer number, got "-0"}
+ {Expected integer number, got "-0"}
+ 1 i-5 {End of integer number not found}
+ {Incomplete value at end of channel}
+ 2 ie {Expected integer number, got ""}
+ {Syntax error in integer, expected sign or digit, got "e"}
+ 3 4: {String not large enough for value}
+ {Incomplete value at end of channel}
+ 4 1: {String not large enough for value}
+ {Incomplete value at end of channel}
+ 5 0 {String not large enough for value}
+ {Incomplete value at end of channel}
+ 6 123 {End of string length not found}
+ {Incomplete value at end of channel}
+ 7 12t: {Expected integer number for string length, got "12t"}
+ {Syntax error in string length, expected digit, or ':', got "t"}
+ 8 -123 {Unknown bee-type "-"}
+ {Unknown bee-type "-"}
+ 9 d0:e {Dictionary has to be of even length}
+ {Dictionary has to be of even length}
+} {
+ test bee-8.$n {decoder errors} {
+ catch {bee::decode $bee} msg
+ set msg
+ } $result ; # {}
+
+ test bee-9.$n {decoder, channel} {
+ set path [gen bee9.$n $bee]
+
+ set res "" ; set tick 1
+ bee::decodeChannel [set f [open $path r]] \
+ -command tick
+ vwait tick
+ close $f
+ removeFile bee9.$n
+ set res
+ } [list error [list $resultchan]] ;# {}
+
+ test bee-10.$n {decoder, channel} {
+ set path [gen bee10.$n $bee]
+
+ set res "" ; set tick 1
+ bee::decodeChannel [set f [open $path r]] \
+ -command tick -exact
+ vwait tick
+ close $f
+ removeFile bee10.$n
+ set res
+ } [list error [list $resultchan]] ;# {}
+}
+
+
+foreach {n bee result} {
+ 0 i0e {{integer 0 2} 3}
+ 1 i-5e {{integer 0 3} 4}
+ 2 4:spam {{string 0 5} 6}
+ 3 0: {{string 0 1} 2}
+ 4 le {{list 0 1 {}} 2}
+ 5 l0:e {{list 0 3 {{string 1 2}}} 4}
+ 6 li5ee {{list 0 4 {{integer 1 3}}} 5}
+ 7 li5e4:spame {{list 0 10 {{integer 1 3} {string 4 9}}} 11}
+ 8 de {{dict 0 1 {}} 2}
+ 9 d0:0:e {{dict 0 5 {{} {{string 1 2} {string 3 4}}}} 6}
+ 10 d0:i5ee {{dict 0 6 {{} {{string 1 2} {integer 3 5}}}} 7}
+ 11 d1:a4:spame {{dict 0 10 {a {{string 1 3} {string 4 9}}}} 11}
+ 12 ld1:a4:spame3:egge {{list 0 17 {{dict 1 11 {a {{string 2 4} {string 5 10}}}} {string 12 16}}} 18}
+ 13 13:eggs+spam+ham {{string 0 15} 16}
+ 14 d1:a1:b1:c1:de {{dict 0 13 {a {{string 1 3} {string 4 6}} c {{string 7 9} {string 10 12}}}} 14}
+} {
+ test bee-11.$n {decoder} {
+ list [bee::decodeIndices $bee end] $end
+ } $result ; # {}
+}
+
+
+test bee-12.0 {decoder, torrent file} {
+ set end 0
+
+ # tcltest::viewFile does not do binary :(
+ set f [open [file join $::tcltest::testsDirectory example.torrent] r]
+ fconfigure $f -translation binary
+ set d [read $f]
+ close $f
+
+ set data [bee::decode $d end]
+
+ # Cut the binary stuff out of the result, to much, display problems
+ list [lreplace $data 5 5 [lreplace [lindex $data 5] end end {}]] $end
+
+} {{announce http://bt.etree.org/announce.php {creation date} 1087598771 info {files {{length 627 path ch1999-05-22.md5} {length 434 path ch1999-05-22.txt} {length 4356201 path ch1999-05-22d1t01.shn} {length 53782885 path ch1999-05-22d1t02.shn} {length 50689401 path ch1999-05-22d1t03.shn} {length 70969629 path ch1999-05-22d1t04.shn} {length 31978833 path ch1999-05-22d1t05.shn} {length 57722005 path ch1999-05-22d1t06.shn} {length 45629997 path ch1999-05-22d2t01.shn} {length 74878121 path ch1999-05-22d2t02.shn} {length 102446341 path ch1999-05-22d2t03.shn} {length 71148293 path ch1999-05-22d2t04.shn}} name ch1999-05-22.schoeps.shnf {piece length} 524288 pieces {}}} 22267}
+
+
+
+foreach {n bee result} {
+ 0 i0e4:hams {value 0 value hams eof {}}
+ 1 lede {value {{}} value {{}} eof {}}
+ 2 le3:egg {value {{}} value egg eof {}}
+ 3 3:eggle {value egg value {{}} eof {}}
+ 4 de3:egg {value {{}} value egg eof {}}
+ 5 3:eggde {value egg value {{}} eof {}}
+ 6 li6e6:plierse3:ham {value {{6 pliers}} value ham eof {}}
+ 7 7:monitorli6e6:plierse {value monitor value {{6 pliers}} eof {}}
+ 8 d6:pliersi6ee3:ham {value {{pliers 6}} value ham eof {}}
+ 9 7:monitord6:pliersi6ee {value monitor value {{pliers 6}} eof {}}
+} {
+ test bee-13.$n {decoder, channel, multiple values} {
+ set path [gen bee13.$n $bee]
+
+ set res "" ; set tick 1
+ bee::decodeChannel [open $path r] \
+ -command tick
+ vwait tick
+ removeFile bee13.$n
+ set res
+ } $result ; # {}
+
+ test bee-14.$n {decoder, channel, multiple values} {
+ set path [gen bee14.$n $bee]
+
+ set res "" ; set tick 1
+ bee::decodeChannel [open $path r] \
+ -command tick -exact
+ vwait tick
+ removeFile bee14.$n
+ set res
+ } $result ; # {}
+
+ test bee-15.$n {decoder, channel, multiple values, abort} {
+ set path [gen bee15.$n $bee]
+
+ set res "" ; set tick 1
+ bee::decodeChannel [set f [open $path r]] \
+ -command tickoff
+ vwait tick
+ close $f
+ removeFile bee15.$n
+ set res
+ } [lrange $result 0 1] ; # {}
+
+ test bee-16.$n {decoder, channel, multiple values, abort} {
+ set path [gen bee16.$n $bee]
+
+ set res "" ; set tick 1
+ bee::decodeChannel [set f [open $path r]] \
+ -command tickoff -exact
+ vwait tick
+ close $f
+ removeFile bee16.$n
+ set res
+ } [lrange $result 0 1] ; # {}
+}
+
+# ....... ............................................................
+testsuiteCleanup
diff --git a/tcllib/modules/bee/example.torrent b/tcllib/modules/bee/example.torrent
new file mode 100644
index 0000000..3421f57
--- /dev/null
+++ b/tcllib/modules/bee/example.torrent
Binary files differ
diff --git a/tcllib/modules/bee/pkgIndex.tcl b/tcllib/modules/bee/pkgIndex.tcl
new file mode 100644
index 0000000..e95dedf
--- /dev/null
+++ b/tcllib/modules/bee/pkgIndex.tcl
@@ -0,0 +1,4 @@
+# Tcl package index file, version 1.1
+
+if {![package vsatisfies [package provide Tcl] 8.4]} {return}
+package ifneeded bee 0.1 [list source [file join $dir bee.tcl]]