#! /bin/sh # -*- tcl -*- \ exec tclsh "$0" ${1+"$@"} # @@ Meta Begin # Application nns 1.2 # Meta platform tcl # Meta summary Nano Name Service Client # Meta description This application connects to a name service demon # Meta description and either registers a name with associated data # Meta description (until exit) or searches for entries matching a # Meta description glob pattern. Operations to identify client and # Meta description server are made available as well. It will survive # Meta description the loss of the nameserver and automatically reconnect # Meta description and continue when it comes back (bind and search). # Meta description # Meta subject {name service} client # Meta require {Tcl 8.4} # Meta require logger # Meta require nameserv::auto # Meta require struct::matrix # Meta author Andreas Kupries # Meta license BSD # @@ Meta End package provide nns 1.2 # nns - Nano Name Service Client # === = ======================== # # Use cases # --------- # # (1) Register something at a nano name service # (2) Query protocol and feature information. # (3) Provide application version, and protocol information # (4) Search service for entries matching a glob-pattern # # Command syntax # -------------- # # (Ad 1) nns bind ?-host NAME|IP? ?-port PORT? name data # (Ad 2) nns ident ?-host NAME|IP? ?-port PORT? # (Ad 3) nns who # (Ad 4) nns search ?-host NAME|IP? ?-port PORT? ?-continuous? ?pattern? # # Register a name with data. If no port is specified the default # port 38573 is used to connect to it. If no host is specified # the default (localhost) is used to connect to it. # ### ### ### ######### ######### ######### ## Requirements lappend auto_path [file join [file dirname [file dirname \ [file normalize [info script]]]] modules] package require nameserv::auto 0.3 ;# Need auto-restoring search. package require struct::matrix logger::initNamespace ::nns namespace eval ::nns { log::setlevel info } # ### ### ### ######### ######### ######### ## Process application command line proc ::nns::ProcessCommandLine {} { global argv variable xcmd variable xname variable xdata variable xpat * variable xwatch 0 # Process the options, perform basic validation. if {[llength $argv] < 1} Usage set cmd [lindex $argv 0] set argv [lrange $argv 1 end] switch -exact -- $cmd { bind - ident - who - search {set xcmd $cmd} default Usage } while {[llength $argv]} { set opt [lindex $argv 0] if {![string match "-*" $opt]} break switch -exact -- $opt { -host { if {$xcmd == "who"} Usage if {[llength $argv] < 2} Usage set host [lindex $argv 1] set argv [lrange $argv 2 end] nameserv::auto::configure -host $host } -port { if {$xcmd == "who"} Usage if {[llength $argv] < 2} Usage # Todo: Check non-zero unsigned short integer set port [lindex $argv 1] set argv [lrange $argv 2 end] nameserv::auto::configure -port $port } -continuous { set xwatch 1 set argv [lrange $argv 1 end] } -debug { # Undocumented. Activate the logger services provided # by various packages. logger::setlevel debug set argv [lrange $argv 1 end] } default Usage } } # Additional validation, and extraction of the non-option # arguments. Of which this application has none. switch -exact -- $xcmd { bind { if {[llength $argv] != 2} Usage foreach {xname xdata} $argv break } search { if {[llength $argv] > 1} Usage if {[llength $argv] == 1} { set xpat [lindex $argv 0] } } who - ident { if {[llength $argv] != 0} Usage } } return } proc ::nns::Usage {{sfx {}}} { global argv0 ; append argv0 $sfx set blank [blank $argv0] puts stderr "$argv0 wrong#args, expected: bind ?-host NAME|IP? ?-port PORT? NAME DATA" puts stderr "$blank ident ?-host NAME|IP? ?-port PORT?" puts stderr "$blank search ?-host NAME|IP? ?-port PORT? ?-continuous? ?PATTERN?" puts stderr "$blank who" exit 1 } proc ::nns::ArgError {text} { global argv0 puts stderr "$argv0: $text" #puts $::errorInfo exit 1 } proc ::nns::blank {s} { regsub -all -- {[^ ]} $s { } s return $s } # ### ### ### ######### ######### ######### proc ::nns::My {} { # Quick access to format the identity of the name service the # client talks to. return "[nameserv::auto::cget -host] @[nameserv::auto::cget -port]" } proc ::nns::Connection {message args} { # args = tag event details, ignored log::info $message return } proc ::nns::MonitorConnection {} { uevent::bind nameserv lost-connection [list ::nns::Connection "Disconnected name service at [My]"] uevent::bind nameserv re-connection [list ::nns::Connection "Reconnected2 name service at [My]"] return } # ### ### ### ######### ######### ######### ## Main proc ::nns::Do.bind {} { global argv0 variable xname variable xdata MonitorConnection log::info "Binding with name service at [My]: $xname = $xdata" nameserv::auto::bind $xname $xdata vwait ::forever # Not reached. return } proc ::nns::Do.ident {} { set sp [nameserv::auto::server_protocol] set sf [join [nameserv::auto::server_features] {, }] if {[llength $sf] > 1} { set sf [linsert $sf end-1 and] } puts "Server [My]" puts " Protocol: $sp" puts " Features: $sf" return } proc ::nns::Do.search {} { variable xpat variable xwatch struct::matrix M M add columns 2 if {$xwatch} { MonitorConnection set contents [nameserv::auto::search -continuous $xpat] $contents configure -command [list ::nns::Do.search.change $contents] vwait ::forever # Not reached. } else { Do.search.print [nameserv::auto::search $xpat] } return } proc ::nns::Do.search.change {res type response} { # Ignoring the arguments, we simply print the full results every # time. if {$type eq "stop"} { # Cannot happen for nameserv::auto client, we are free to panic. $res destroy log::critical {Bad event 'stop' <=> Lost connection, search closed} return } # Clear screen ... puts -nonewline stdout "\033\[H\033\[J"; # Home + Erase Down flush stdout ::nns::Do.search.print [$res getall] return } proc ::nns::Do.search.print {contents} { log::info "Searching at name service at [My]" if {![llength $contents]} { log info "Nothing found..." return } catch {M delete rows [M rows]} foreach {name data} $contents { M add row [list $name $data] } foreach line [split [M format 2string] \n] { log::info $line } return } proc ::nns::Do.who {} { # FUTURE: access and print the metadata contained in ourselves. global argv0 puts "$argv0 [package require nns] (Client Protocol [nameserv::auto::protocol])" return } # ### ### ### ######### ######### ######### ## Invoking the functionality. ::nns::ProcessCommandLine if {[catch { ::nns::Do.$::nns::xcmd } msg]} { ::nns::ArgError $msg } # ### ### ### ######### ######### ######### exit