27 files changed, 9 insertions(+), 1054 deletions(-)
M bin/docgen.tcl
D doc/doctools/gen/cap.man
D doc/doctools/gen/db/main.man
D doc/doctools/gen/irc/main.man
D doc/doctools/gen/main.man
D doc/doctools/gen/plugins.man
D doc/doctools/gen/router.man
D doc/doctools/gen/spdx/lib.man
D doc/doctools/gen/ui/main.man
D doc/md/cap.md
D doc/md/db/main.md
D doc/md/irc/main.md
D doc/md/main.md
D doc/md/plugins.md
D doc/md/router.md
D doc/md/spdx/lib.md
D doc/md/ui/main.md
M src/cap.tcl
D src/db/main.tcl
D src/irc/main.tcl
M src/main.tcl
D src/plugins.tcl
D src/router.tcl
D src/spdx/dump.tcldict.gz
D src/spdx/lib.tcl
D src/ui/main.tcl
D testplugin/manifest.tcl
M bin/docgen.tcl => bin/docgen.tcl +9 -0
@@ 25,12 25,21 @@ foreach dir [list \
cd [file join $basedir src]
set files [glob -nocomplain {*}$files]
+set okfiles {}
foreach file $files {
set opath [file join $basedir doc doctools gen $file]
file mkdir [file dirname $opath]
+
+ set fd [open $file r]
+ if [regexp -line {^[ \t]*#\*\*\*} [read $fd]] {
+ lappend okfiles $file
+ }
+ close $fd
}
+set files $okfiles
+
file mkdir [file join $basedir doc doctools gen]
exec -- [file join $basedir bin docextract.tcl] -o [file join $basedir doc doctools gen] -all {*}$files
D doc/doctools/gen/cap.man => doc/doctools/gen/cap.man +0 -5
@@ 1,5 0,0 @@
-[manpage_begin cap tclircc 0.0.1]
-[titledesc {Library cap.tcl}]
-[description]
-[manpage_end]
-
D doc/doctools/gen/db/main.man => doc/doctools/gen/db/main.man +0 -5
@@ 1,5 0,0 @@
-[manpage_begin db_thread tclircc 0.0.1]
-[titledesc {Thread tclircc::db}]
-[description]
-[manpage_end]
-
D doc/doctools/gen/irc/main.man => doc/doctools/gen/irc/main.man +0 -5
@@ 1,5 0,0 @@
-[manpage_begin irc_thread tclircc 0.0.1]
-[titledesc {Thread tclircc::irc}]
-[description]
-[manpage_end]
-
D doc/doctools/gen/main.man => doc/doctools/gen/main.man +0 -17
@@ 1,17 0,0 @@
-[manpage_begin main_thread tclircc 0.0.1]
-[titledesc {Thread main}]
-[description]
-[para]
-This is the application entrypoint. It does the following.
-[list_begin itemized]
-[item]
-brings up the routing system
-[item]
-brings up the database
-[item]
-loads the other core threads
-[item]
-opens a window
-[list_end]
-[manpage_end]
-
D doc/doctools/gen/plugins.man => doc/doctools/gen/plugins.man +0 -5
@@ 1,5 0,0 @@
-[manpage_begin plugins tclircc 0.0.1]
-[titledesc {Component plugins.tcl}]
-[description]
-[manpage_end]
-
D doc/doctools/gen/router.man => doc/doctools/gen/router.man +0 -5
@@ 1,5 0,0 @@
-[manpage_begin router tclircc 0.0.1]
-[titledesc {Component router.tcl}]
-[description]
-[manpage_end]
-
D doc/doctools/gen/spdx/lib.man => doc/doctools/gen/spdx/lib.man +0 -5
@@ 1,5 0,0 @@
-[manpage_begin spdx tclircc 0.0.1]
-[titledesc {Library spdx/lib.tcl}]
-[description]
-[manpage_end]
-
D doc/doctools/gen/ui/main.man => doc/doctools/gen/ui/main.man +0 -5
@@ 1,5 0,0 @@
-[manpage_begin ui_thread tclircc 0.0.1]
-[titledesc {Thread tclircc::ui}]
-[description]
-[manpage_end]
-
D doc/md/cap.md => doc/md/cap.md +0 -19
@@ 1,19 0,0 @@
----
-toc: false
----
-
-[//000000001]: # (cap \- )
-[//000000002]: # (Generated from file 'doctools' by tcllib/doctools with format 'markdown')
-[//000000003]: # (cap\(tclircc\) 0\.0\.1 "")
-
-# NAME
-
-cap \- Library cap\.tcl
-
-# <a name='toc'></a>Table Of Contents
-
- - [Table Of Contents](#toc)
-
- - [Description](#section1)
-
-# <a name='description'></a>DESCRIPTION
D doc/md/db/main.md => doc/md/db/main.md +0 -19
@@ 1,19 0,0 @@
----
-toc: false
----
-
-[//000000001]: # (db\_thread \- )
-[//000000002]: # (Generated from file 'doctools' by tcllib/doctools with format 'markdown')
-[//000000003]: # (db\_thread\(tclircc\) 0\.0\.1 "")
-
-# NAME
-
-db\_thread \- Thread tclircc::db
-
-# <a name='toc'></a>Table Of Contents
-
- - [Table Of Contents](#toc)
-
- - [Description](#section1)
-
-# <a name='description'></a>DESCRIPTION
D doc/md/irc/main.md => doc/md/irc/main.md +0 -19
@@ 1,19 0,0 @@
----
-toc: false
----
-
-[//000000001]: # (irc\_thread \- )
-[//000000002]: # (Generated from file 'doctools' by tcllib/doctools with format 'markdown')
-[//000000003]: # (irc\_thread\(tclircc\) 0\.0\.1 "")
-
-# NAME
-
-irc\_thread \- Thread tclircc::irc
-
-# <a name='toc'></a>Table Of Contents
-
- - [Table Of Contents](#toc)
-
- - [Description](#section1)
-
-# <a name='description'></a>DESCRIPTION
D doc/md/main.md => doc/md/main.md +0 -29
@@ 1,29 0,0 @@
----
-toc: false
----
-
-[//000000001]: # (main\_thread \- )
-[//000000002]: # (Generated from file 'doctools' by tcllib/doctools with format 'markdown')
-[//000000003]: # (main\_thread\(tclircc\) 0\.0\.1 "")
-
-# NAME
-
-main\_thread \- Thread main
-
-# <a name='toc'></a>Table Of Contents
-
- - [Table Of Contents](#toc)
-
- - [Description](#section1)
-
-# <a name='description'></a>DESCRIPTION
-
-This is the application entrypoint\. It does the following\.
-
- - brings up the routing system
-
- - brings up the database
-
- - loads the other core threads
-
- - opens a window
D doc/md/plugins.md => doc/md/plugins.md +0 -19
@@ 1,19 0,0 @@
----
-toc: false
----
-
-[//000000001]: # (plugins \- )
-[//000000002]: # (Generated from file 'doctools' by tcllib/doctools with format 'markdown')
-[//000000003]: # (plugins\(tclircc\) 0\.0\.1 "")
-
-# NAME
-
-plugins \- Component plugins\.tcl
-
-# <a name='toc'></a>Table Of Contents
-
- - [Table Of Contents](#toc)
-
- - [Description](#section1)
-
-# <a name='description'></a>DESCRIPTION
D doc/md/router.md => doc/md/router.md +0 -19
@@ 1,19 0,0 @@
----
-toc: false
----
-
-[//000000001]: # (router \- )
-[//000000002]: # (Generated from file 'doctools' by tcllib/doctools with format 'markdown')
-[//000000003]: # (router\(tclircc\) 0\.0\.1 "")
-
-# NAME
-
-router \- Component router\.tcl
-
-# <a name='toc'></a>Table Of Contents
-
- - [Table Of Contents](#toc)
-
- - [Description](#section1)
-
-# <a name='description'></a>DESCRIPTION
D doc/md/spdx/lib.md => doc/md/spdx/lib.md +0 -19
@@ 1,19 0,0 @@
----
-toc: false
----
-
-[//000000001]: # (spdx \- )
-[//000000002]: # (Generated from file 'doctools' by tcllib/doctools with format 'markdown')
-[//000000003]: # (spdx\(tclircc\) 0\.0\.1 "")
-
-# NAME
-
-spdx \- Library spdx/lib\.tcl
-
-# <a name='toc'></a>Table Of Contents
-
- - [Table Of Contents](#toc)
-
- - [Description](#section1)
-
-# <a name='description'></a>DESCRIPTION
D doc/md/ui/main.md => doc/md/ui/main.md +0 -19
@@ 1,19 0,0 @@
----
-toc: false
----
-
-[//000000001]: # (ui\_thread \- )
-[//000000002]: # (Generated from file 'doctools' by tcllib/doctools with format 'markdown')
-[//000000003]: # (ui\_thread\(tclircc\) 0\.0\.1 "")
-
-# NAME
-
-ui\_thread \- Thread tclircc::ui
-
-# <a name='toc'></a>Table Of Contents
-
- - [Table Of Contents](#toc)
-
- - [Description](#section1)
-
-# <a name='description'></a>DESCRIPTION
M src/cap.tcl => src/cap.tcl +0 -9
@@ 1,9 1,3 @@
-#***
-# [manpage_begin cap tclircc 0.0.1]
-# [titledesc {Library cap.tcl}]
-# [description]
-
-
# cap.status:
# - sent (we have sent CAP LS 302, no terminal reply)
# - ack-wait (we've sent off the first REQ, wait for an ACK to update state and send END. will only transition when cap.req-inflight == {})
@@ 320,6 314,3 @@ namespace eval ::cap {
irc::msg send $chan CAP LS 302
}
}
-
-#***
-# [manpage_end]
D src/db/main.tcl => src/db/main.tcl +0 -46
@@ 1,46 0,0 @@
-#***
-# [manpage_begin db_thread tclircc 0.0.1]
-# [titledesc {Thread tclircc::db}]
-# [description]
-
-package require sqlite3
-package require logger
-
-set log [logger::init tclircc::db]
-
-switch -- [set tcl_platform(platform)] {
- "windows" {
- set data_dir [file join [set env(APPDATA)] tclircc]
- }
- "unix" {
- if [info exists env(XDG_CONFIG_HOME)] {
- set data_dir [file join [set env(XDG_CONFIG_HOME)] tclircc]
- } elseif [info exists env(HOME)] {
- set data_dir [file join [set env(HOME)] .config tclircc]
- } else {
- return -code error {Missing $HOME or $XDG_CONFIG_HOME, can't store config!}
- }
- }
- default {
- return -code error {Unknown platform, can't store config.}
- }
-}
-
-file mkdir $data_dir
-
-proc path_to_core {} {
- global data_dir
- return [file join $data_dir core.db]
-}
-
-proc backup {} {
- global log data_dir
- set backup_dir [string cat $data_dir _backup_ [clock microseconds]]
- ${log}::info "backing up data directory to $backup_dir..."
- file copy $data_dir $backup_dir
- ${log}::info "backup to $backup_dir complete!"
- return $backup_dir
-}
-
-#***
-# [manpage_end]
D src/irc/main.tcl => src/irc/main.tcl +0 -12
@@ 1,12 0,0 @@
-#***
-# [manpage_begin irc_thread tclircc 0.0.1]
-# [titledesc {Thread tclircc::irc}]
-# [description]
-
-package require logger
-
-source [file join $path irc.tcl]
-source [file join $path cap.tcl]
-
-#***
-# [manpage_end]
M src/main.tcl => src/main.tcl +0 -92
@@ 1,94 1,2 @@
#!/bin/env tclsh
-#***
-# [manpage_begin main_thread tclircc 0.0.1]
-# [titledesc {Thread main}]
-# [description]
-# [para]
-# This is the application entrypoint. It does the following.
-# [list_begin itemized]
-
-
-
-set path [file dirname [dict get [info frame 0] file]]
-set version v0.0.1
-proc src {file} {
- global path
- uplevel [list source [file join $path $file]]
-}
-
-package require Thread
-thread::id; # avoid use-after-free
-package require logger
-set log [logger::init tclircc::main]
-package require sqlite3
-
-${log}::info "tclircc $version <https://amehut.dev/~aleteoryx/tclircc>"
-${log}::info "running from $path"
-
-#***
-# [item]
-# brings up the routing system
-src router.tcl
-
-proc on_routes_update {} {
- puts "routes updated!"
- r::debug
-}
-
-proc start_thread {name} {
- global path
- global version
- global log
-
- ${log}::debug "starting $name thread..."
-
- set thread [thread::create -preserved]
- router::manage $thread tclircc::$name
- thread::send $thread [list variable path $path version $version]
-
- if {[thread::send $thread \
- [list source [file join $path $name main.tcl] ] result] == 1} {
- ${log}::critical "couldn't start $name thread: $result"
- exit -1
- }
-
- ${log}::debug "started $name thread."
-}
-
-#***
-# [item]
-# brings up the database
-start_thread db
-
-thread::send [r::ns tclircc::db] {path_to_core} core_db_path
-sqlite3 core_db $core_db_path -create true -fullmutex true
-src migrate_core.tcl
-
-src plugins.tcl
-
-#***
-# [item]
-# loads the other core threads
-start_thread irc
-start_thread ui
-
-plugins::load [file join $path .. testplugin]
-
-#***
-# [item]
-# opens a window
-${log}::debug "opening initial window..."
-r::exec tclircc::ui {
- mk_toplevel name
- expr {$name}
-} {
- ${log}::debug "initial window opened: $result"
-}
-
-${log}::info "entering event loop"
-vwait nil
-
-#***
-# [list_end]
-# [manpage_end]
D src/plugins.tcl => src/plugins.tcl +0 -396
@@ 1,396 0,0 @@
-#***
-# [manpage_begin plugins tclircc 0.0.1]
-# [titledesc {Component plugins.tcl}]
-# [description]
-
-package require sha256
-package require html
-package require htmlparse
-if {[info procs ::spdx::*] == {}} {
- global path
- source [file join $path "spdx/lib.tcl"]
-}
-
-# plugin manifest format:
-# tcl script that must set the following variables at a minimum:
-# - name: string
-# - slug: string
-# must match /^[-_a-zA-Z0-9]+$/
-# - namespace: string
-# must match /^[-_a-zA-Z0-9]+$/
-# - version: string[]
-# ordered like {1} > {0}, {0 1} < {1 0}. {1 1} > {1}
-# may set:
-# - description: string
-# - author: string[]
-# should contain a list of names with optional website or email, e.g.
-# {{Aleteoryx <https://aleteoryx.me>}
-# {Alice P Hacker <aph@example.com>}}
-# - license: string
-# may begin with spdx: to indicate an spdx id or file: to indicate
-# a file path relative to the plugin root. .. and root references are
-# stripped. if neither prefix applies, assumed to be literal license
-# text.
-
-
-namespace eval plugins {
- variable plugins
- variable log [logger::init tclircc::plugins]
-
- # generate the script to exfiltrate data from the manifest
- variable mf_required {
- name 1
- namespace {[regexp {^[_a-zA-Z0-9-]+$} $val]}
- slug {[regexp {^[_a-zA-Z0-9-]+$} $val]}
- version {$val != ""} }
- variable mf_optional {
- description 1
- authors 1
- license 1 }
- variable mf_procs {
-
- }
- variable manifest_reader {
- set manifest_dict {}
- }
- foreach {key check} $mf_required {
- set chunk {
- if {[info exists %key]} {
- set val [set %key]
- if %check {
- dict set manifest_dict %key [set %key]
- } else { return -code error "invalid value for %key: $val" }
- } else { return -code error "missing key in manifest: %key" }
- }
-
- regsub -all "%key" $chunk [list $key] chunk
- regsub -all "%check" $chunk [list $check] chunk
-
- append manifest_reader $chunk
- }
- foreach {key check} $mf_optional {
- set chunk {
- if {[info exists %key]} {
- set val [set %key]
- if %check {
- dict set manifest_dict %key [set %key]
- }
- }
- }
-
- regsub -all "%key" $chunk [list $key] chunk
- regsub -all "%check" $chunk [list $check] chunk
-
- append manifest_reader $chunk
- }
- foreach {key _} $mf_procs {
- set chunk {
- if {%key in [info procs %key]} {
- dict set manifest_dict procs %key [list [info args %key] [info body %key]]
- }
- }
-
- regsub -all "%key" $chunk [list $key] chunk
-
- append manifest_reader $chunk
- }
- append manifest_reader { set manifest_dict }
- # generation done
-
- proc vercmp {v1 v2} {
- # normalize the lists, check for equality
- if {[list {*}$v1] == [list {*}$v2]} { return 0 }
- # compare every element
- for {set n 0} {$n < min([llength $v1], [llength $v2])} {incr n} {
- if [set cmp [string compare [lindex $v1 $n] [lindex $v2 $n]]] {
- return $cmp
- }
- }
- # they can't be equal, so the longer one must be the newer one
- if {[llength $v1] > [llength $v2]} { return 1 }
- return -1
- }
-
- # TODO: parse markdown to html, parse plaintext <links> to html
- proc format_license {license dir} {
- switch -glob -- $license {
- spdx:* {
- set license [string range $license 5 end]
- if ![spdx::exists $license] {
- return "<b>License:</b> <i>Unknown (invalid SPDX code \"[html::html_entities $license\"])</i>"
- }
- set license_name [spdx::get_name $license]
- set license_uri [spdx::get_spdx_uri $license]
- set license [spdx::get_text $license]
- }
- file:* {
- set license [string range $license 5 end]
- set license_name [file tail [file rootname $license]]
- set license_path [file join $dir [string trim [file normalize /$license] /]]
- set license_uri "file://$license_path"
- if [catch {
- set fd [open $license_path r]
- set license [read $fd]
- close $fd
- unset fd
- }] {
- set license_path [html::html_entities $license_path]
- return "<b>License:</b> <i>Unreadable (couldn't load file \"<a href=\"$license_uri\">$license_path</a>\")</i>"
- }
- }
- * {
- set license_name custom
- set license_uri {}
- }
- }
-
- set license [html::html_entities $license]
- regsub -all {<((?:https?|ftps?|gopher|gemini|spartan|file)://(?:(?!>).)+)>} $license {<a href="\1">\1</a>} license
- regsub -all {<([^@]+@(?:(?!>)[^@])+?)>} $license {<a href="mailto:\1">\1</a>} license
- set license_name [html::html_entities $license_name]
- set license_uri [html::html_entities $license_uri]
- return "<b>License:</b> <i><a href=\"$license_uri\">$license_name</a></i>\n\n$license"
- }
-
- proc enroll_interp {tid iid} {}
-
- proc load {dir {skip_prompt 0}} {
- variable log
- variable manifest_reader
-
- set dir [file normalize $dir]
- ${log}::info "attempting to load plugin from \"$dir\"..."
-
- set mf_path [file join $dir manifest.tcl]
- if {!([file isfile $mf_path] && [file readable $mf_path])} {
- ${log}::error "couldn't load plugin: manifest \"$mf_path\" not present or not readable!"
- return 0
- }
- set mf_fd [open $mf_path]
- set manifest [read $mf_fd]
- close $mf_fd
-
- interp create -safe mf_exec
- # prevent escapes
- interp hide mf_exec package
- interp hide mf_exec interp
- # prevent hanging the thread
- interp hide mf_exec vwait
- interp hide mf_exec after
- interp hide mf_exec update
- interp hide mf_exec gets
- interp hide mf_exec read
-
- # this is more than enough for any reasonable manifest
- interp limit mf_exec time -seconds [expr {[clock seconds] + 2}]
- interp limit mf_exec command -value 10000
-
- # untrusted code time
- ${log}::debug "evaluating manifest..."
- set untrusted_result [catch {
- set mf_result [catch { interp eval mf_exec $manifest } result opts]
- if {$mf_result != 0} {
- ${log}::error "couldn't load plugin: manifest return code $mf_result: $result"
- return 0
- }
-
- set mf_valid [catch { interp eval mf_exec $manifest_reader } result opts]
- if {$mf_valid != 0} {
- ${log}::error "couldn't validate plugin: $result"
- return 0
- }
-
- # hooray, we have the manifest!
- set manifest $result
- interp delete mf_exec
- } result opts]
- switch -- $untrusted_result {
- 0 { } 2 { return -code 2 result }
- 1 {
- ${log}::error "couldn't load plugin: $result"
- return 0
- }
- default {
- ${log}::alert "unexpected return code from plugin handling: $untrusted_result"
- ${log}::alert "return options: $opts"
- ${log}::alert "THIS MAY INDICATE A PLUGIN SANDBOX COMPROMISE!"
- return 0
- }
- }
-
- set pl_name [dict get $manifest name]
- set pl_namespace [dict get $manifest namespace]
- set pl_slug [dict get $manifest slug]
- set pl_version [dict get $manifest version]
-
- set pl_qslug "${pl_namespace}::${pl_slug}"
- set pl_verstr "v[join $pl_version .]"
-
- # qualified slug
- dict set manifest qslug $pl_qslug
- dict set manifest verstr $pl_verstr
-
- ${log}::debug "manifest loaded: $pl_name (${pl_qslug}) $pl_verstr"
-
- set stored [core_db eval {
- SELECT manifest_hash, version, trusted_until FROM plugins
- WHERE slug = $pl_slug
- AND namespace = $pl_namespace}]
-
- dict set manifest license [format_license [dict get $manifest license] $dir]
-
- if ![llength $stored] {
- tailcall int_load_new $skip_prompt $manifest $dir
- } else {
- lassign $stored st_mf_hash st_version st_trusted_till
- switch -- [vercmp $pl_version $st_version] {
- 0 { tailcall int_load_existing $manifest $dir $mf_path $st_mf_hash $st_trusted_till }
- 1 { tailcall int_load_updated $skip_prompt $manifest $dir $st_version $st_trusted_till }
- -1 { tailcall int_load_old $manifest $dir $st_version }
- }
- }
- }
-
- proc int_load_new {skip_prompt manifest dir} {
- variable log
- ${log}::info "installing new plugin [dict get $manifest qslug]"
-
- if {!$skip_prompt} {
- set manifest [list $manifest]
- set dir [list $dir]
- r::exec tclircc::ui {
- set manifest %manifest
- prompt -type yesno \
- -title "Plugin Installation" \
- -icon "question" \
- -message "Do you want to install \"[dict get $manifest name]\"?" \
- -detail [string cat \
- "[dict get $manifest qslug] [dict get $manifest verstr]" \
- "\nBy [join [dict get $manifest authors] ,]." \
- "\n\n[dict get $manifest license]" ]
- } {
- set manifest %manifest
- switch -- $result {
- yes {
- ::plugins::install $manifest %dir
- }
- no {
- set log %log
- ${log}::info "user cancelled installation of plugin [dict get $manifest qslug]"
- }
- }
- }
- } else {
- install $manifest $dir
- }
- }
- proc int_load_existing {manifest dir mf_path mf_hash trusted_till} {
- variable log
- ${log}::debug "loading existing plugin [dict get $manifest qslug]"
-
- if {$trusted_till >= [clock seconds]} {
- install $manifest $dir
- } elseif {[sha2::sha256 -file $mf_path] == $mf_hash} {
- install $manifest $dir
- } else {
- ${log}::warn "plugin may have been tampered with!"
-
- set manifest [list $manifest]
- set dir [list $dir]
- r::exec tclircc::ui {
- set manifest %manifest
- prompt -type yesno \
- -title "Plugin Tampering Detected!" \
- -icon "warning" \
- -message "\"[dict get $manifest name]\" ([dict get $manifest qslug]) has been edited since last load!" \
- -detail [string cat \
- "This may indicate a security compromise, do you want to continue loading it?" \
- " (If you are doing plugin development, set the \"Trusted Until\" option!)"]
- } {
- set manifest %manifest
- set log %log
- switch -- $result {
- yes {
- ${log}::warn "loading [dict get $manifest qslug] despite potential tampering!"
- ::plugins::install $manifest %dir
- }
- no {
- ${log}::info "not loading [dict get $manifest qslug] due to tampering"
- }
- }
- }
- }
- }
- proc int_load_updated {skip_prompt manifest dir version trusted_till} {
- variable log
- ${log}::info "updating plugin [dict get $manifest qslug] (v[join $version .] -> [dict get $manifest verstr])"
-
- if {!$skip_prompt && [clock seconds] > $trusted_till} {
- set manifest [list $manifest]
- set dir [list $dir]
- r::exec tclircc::ui {
- set manifest %manifest
- prompt -type yesno \
- -title "Plugin Update" \
- -icon "question" \
- -message "Do you want to update \"[dict get $manifest name]\"?" \
- -detail [string cat \
- "[dict get $manifest qslug] [dict get $manifest verstr]" \
- "\nBy [join [dict get $manifest authors] ,]." \
- "\n\n[dict get $manifest license]" ]
- } {
- set manifest %manifest
- switch -- $result {
- yes {
- ::plugins::install $manifest %dir
- }
- no {
- set log %log
- ${log}::info "user cancelled update of plugin [dict get $manifest qslug]"
- }
- }
- }
- } else {
- install $manifest $dir
- }
- }
- proc int_load_old {manifest dir version} {
- variable log
- ${log}::warn "attempting to load older version of [dict get $manifest qslug] (v[join $version .] -> [dict get $manifest verstr])"
-
- set verstr [list "v[join $version .]"]
- set manifest [list $manifest]
- set dir [list $dir]
- r::exec tclircc::ui {
- set manifest %manifest
- tk_messageBox -type yesno \
- -title "Plugin Downgrade!" \
- -icon "warning" \
- -message [string cat \
- "Are you sure you want to downgrade \"[dict get $manifest name]" \
- "\" ([dict get $manifest qslug])? A database backup will be made." ] \
- -detail [string cat \
- "Last saw " %verstr ", attempting to load [dict get $manifest verstr]."]
- } {
- set manifest %manifest
- set log %log
- switch -- $result {
- yes {
- ${log}::warn "downgrading [dict get $manifest qslug]! making a backup!"
- r::exec tclircc::db backup {
- ::plugins::install $manifest %dir
- }
- }
- no {
- ${log}::info "not downgrading [dict get $manifest qslug]"
- }
- }
- }
- }
- proc install {manifest dir} {
-
- }
-}
-
-#***
-# [manpage_end]
D src/router.tcl => src/router.tcl +0 -133
@@ 1,133 0,0 @@
-#***
-# [manpage_begin router tclircc 0.0.1]
-# [titledesc {Component router.tcl}]
-# [description]
-
-namespace eval router {
- variable routes [list main [thread::id]]
- variable log [logger::init tclircc::router]
-
- variable r_ns_script {
- namespace eval ::r {
- variable ns
- variable self
- proc update {} {
- variable ns
- thread::send -head [set ns(main)] ::router::update
- }
- proc ns {name} {
- variable ns
- set ns($name)
- }
- proc debug {} {
- variable log
- parray r::ns
- }
- proc util_regsub_prep {data} {
- set first -1
- while {[set first [string first \\ $data $first+1]] != -1} {
- if {[string index $data $first+1] ni {0 1 2 3 4 5 6 7 8 9}} {
- continue
- }
- set data [string replace $data $first $first \\\\]
- incr first
- }
- set first -1
- while {[set first [string first & $data $first+1]] != -1} {
- set data [string replace $data $first $first {\&}]
- incr first
- }
-
- return $data
- }
- proc util_pctsub {script} {
- set new_script {}
- set idx 0
- while {[set subst_start [string first "%" $script $idx]] != -1} {
- append new_script [string range $script $idx $subst_start-1]
- if {[string index $script $subst_start+1] == "%"} {
- append new_script %
- set idx [expr {$subst_start + 2}]
- continue
- }
- regexp -start [expr {$subst_start + 1}] -indices {[^A-Za-z0-9_:]} $script match
- if {[llength $match]} {
- lassign $match subst_end
- set subst_end $subst_end-1
- } else {
- set subst_end [string length $script]
- }
- upvar [string range $script $subst_start+1 $subst_end] subst_data
- append new_script $subst_data
- set idx [expr $subst_end + 1]
- }
- append new_script [string range $script $idx end]
-
- return $new_script
- }
- proc exec {name script {followup {}}} {
- variable ns
- variable self
-
- set script [uplevel [list ::r::util_pctsub $script]]
- set followup [uplevel [list ::r::util_pctsub $followup]]
-
- if [string length $followup] {
- set realscript {
- set result [eval %script]
- set script [concat [list set result $result] ";" %followup]
- thread::send -async [r::ns %self] $script
- }
- regsub %self $realscript [util_regsub_prep [list $self]] realscript
- regsub %followup $realscript [util_regsub_prep [list $followup]] realscript
- regsub %script $realscript [util_regsub_prep [list $script]] realscript
- set script $realscript
- }
-
- thread::send -async [set ns($name)] $script
- }
- }
- }
- eval $r_ns_script
- set ::r::self "main"
-
- proc manage {tid name} {
- variable routes
- variable log
- variable r_ns_script
- dict set routes $name $tid
- ${log}::debug "managing thread $tid as \"$name\""
-
- thread::send -head $tid [concat $r_ns_script ";" [list set ::r::self $name]]
-
- ::router::update
- }
- proc unmanage {name} {
- variable routes
- variable log
- dict unset routes $name
- ${log}::debug "unmanaging thread $tid (\"$name\")"
- ::router::update
- }
- proc update {} {
- variable routes
- dict for {name tid} $routes {
- if ![thread::exists $tid] {dict unset routes $name}
- }
- dict for {name tid} $routes {
- set payload {array unset r::ns; }
- dict for {n t} $routes {
- append payload [list array set r::ns [list $n $t]] ";\n"
- }
- append payload {
- if {"on_routes_update" in [info procs on_routes_update]} \
- on_routes_update
- }
- thread::send -head $tid $payload
- }
- ::update
- }
-}
-
-#***
-# [manpage_end]
D src/spdx/dump.tcldict.gz => src/spdx/dump.tcldict.gz +0 -0
D src/spdx/lib.tcl => src/spdx/lib.tcl +0 -66
@@ 1,66 0,0 @@
-#***
-# [manpage_begin spdx tclircc 0.0.1]
-# [titledesc {Library spdx/lib.tcl}]
-# [description]
-
-namespace eval spdx {
- variable log [logger::init spdx]
-
- variable db
-
- proc load {} {
- variable log
- variable db
-
- if {[array exists db]} { return }
-
- ${log}::debug "loading database..."
-
- set dump_path [dict get [info frame [info frame]] file]
- set dump_path [file dirname $dump_path]
- set dump_path [file join $dump_path dump.tcldict.gz]
-
- set dump [open $dump_path]
- zlib push gunzip $dump
- set data [read $dump]
- close $dump
- unset dump
- unset dump_path
-
- foreach {name info} $data {
- set db($name) $info
- }
- unset data
-
- ${log}::debug "database loaded with [array size db] licenses!"
- }
-
- proc exists {id} {
- variable db
- ::spdx::load
- expr {$id in [array names db -exact $id]}
- }
- proc get_name {id} {
- variable db
- ::spdx::load
- dict get [set db($id)] name
- }
- proc get_text {id} {
- variable db
- ::spdx::load
- dict get [set db($id)] text
- }
- proc get_spdx_uri {id} {
- variable db
- ::spdx::load
- dict get [set db($id)] spdx_uri
- }
- proc get_see_also {id} {
- variable db
- ::spdx::load
- dict get [set db($id)] see_also
- }
-}
-
-#***
-# [manpage_end]
D src/ui/main.tcl => src/ui/main.tcl +0 -79
@@ 1,79 0,0 @@
-#***
-# [manpage_begin ui_thread tclircc 0.0.1]
-# [titledesc {Thread tclircc::ui}]
-# [description]
-
-variable toplevel_count 0
-variable toplevel_tabs
-variable toplevel_classes
-
-package require logger
-set log [logger::init tclircc::ui]
-
-${log}::debug "loading tk"
-package require Tk
-wm withdraw .
-# TODO: tray handling
-# use tktray on linux, use TWAPI on windows (packaged with magicsplat)
-
-proc tab_update {window} {
- variable toplevel_tabs
-
- if ![llength [set toplevel_tabs($window)]] {
- destroy $window.tabs
- destroy $window.msg1 $window.msg2
-
- wm title $window "tclircc"
-
- label $window.msg1 -text "No tabs open!"
- label $window.msg2 -text "Use the titlebar to open a new tab."
-
- pack configure $window.msg1 -side top -pady {20 10}
- pack configure $window.msg2 -side top -pady {10 50} -padx {50}
- }
-}
-
-proc mk_toplevel {varname {takefocus 0}} {
- variable toplevel_count
- variable toplevel_tabs
- variable version
- upvar $varname window
- set window .win$toplevel_count
- toplevel $window -takefocus $takefocus
- incr toplevel_count
-
- ### MENUBAR ###
- menu $window.menu
- $window configure -menu "$window.menu"
-
- menu $window.menu.conn
- menu $window.menu.irc
- menu $window.menu.plugin
-
- menu $window.menu.me
- $window.menu.me add command -label "About" -command [subst {
- tk_messageBox -title "About tclircc" \
- -message "tclircc $version" \
- -detail "by Aleteoryx\nhttps://amehut.dev/~aleteoryx/tclircc\n\nThis software is in the public domain." \
- -type ok \
- -parent $window }]
-
- $window.menu add cascade -label "chat" -menu $window.menu.irc
- $window.menu add cascade -label "connection" -menu $window.menu.conn
- $window.menu add cascade -label "plugins" -menu $window.menu.plugin
- $window.menu add cascade -label "tclircc" -menu $window.menu.me
-
- ### TABS ###
- set toplevel_tabs($window) {}
-
- tab_update $window
-}
-
-proc prompt {args} {
- uplevel tk_messageBox $args
-}
-
-${log}::debug "ui init done!"
-
-#***
-# [manpage_end]
D testplugin/manifest.tcl => testplugin/manifest.tcl +0 -7
@@ 1,7 0,0 @@
-set name "test plugin"
-set slug "test"
-set namespace "aleteoryx"
-set version {0 0}
-
-set authors {Aleteoryx <alyx@aleteoryx.me>}
-set license {i hate you <https://example.com> by <amity@aleteoryx.me> < > &}