• Recommended way to import files?

    From Mark@21:1/5 to All on Thu Jun 1 00:54:55 2023
    Suppose I have a project:

    app.tcl
    liba.tcl
    libb.tcl

    and in app.tcl I use functions from liba.tcl and libb.tcl (in namespaces liba:: and libb:: respectively).

    Is there a standard recommended way to import these files?

    At the moment I do this (in app.tcl):

    set name [info script]
    if {[file type $name] == "link"} {
    set name [file readlink $name]
    }
    set ::APP_PATH [file normalize [file dirname $name]]

    foreach filename {
    liba.tcl
    liba.tcl
    } {
    source -encoding utf-8 $::APP_PATH/$filename
    }


    But this seems a bit cumbersome.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ralf Fassel@21:1/5 to All on Thu Jun 1 10:52:58 2023
    * Mark <m.n.summerfield@googlemail.com>
    | Suppose I have a project:

    | app.tcl
    | liba.tcl
    | libb.tcl

    | and in app.tcl I use functions from liba.tcl and libb.tcl (in
    | namespaces liba:: and libb:: respectively).

    | Is there a standard recommended way to import these files?

    I think I would make liba and libb packages:

    - liba.tcl
    package provide liba 1.0

    - libb.tcl
    package provide libb 1.0

    - new file: pkgIndex.tcl
    package ifneeded liba 1.0 [list source -encoding utf-8 [file join $dir liba.tcl]]
    package ifneeded libb 1.0 [list source -encoding utf-8 [file join $dir libb.tcl]]

    - app.tcl
    # optional (if not installed in auto_path)
    lappend auto_path [file dirname [info script]]
    # load liba and libb
    package require liba
    package require libb

    If 'liba' and 'libb' are not only short versions for demo purposes,
    maybe it would be a good idea to name the packages with some prefix
    (app_liba etc), since plain 'liba' and 'libb' are rather generic...

    HTH
    R'

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mark@21:1/5 to Ralf Fassel on Thu Jun 1 02:21:48 2023
    On Thursday, June 1, 2023 at 9:53:03 AM UTC+1, Ralf Fassel wrote:
    * Mark <m.n.sum...@googlemail.com>
    | Suppose I have a project:

    | app.tcl
    | liba.tcl
    | libb.tcl

    | and in app.tcl I use functions from liba.tcl and libb.tcl (in
    | namespaces liba:: and libb:: respectively).

    | Is there a standard recommended way to import these files?
    I think I would make liba and libb packages:

    - liba.tcl
    package provide liba 1.0

    - libb.tcl
    package provide libb 1.0

    - new file: pkgIndex.tcl
    package ifneeded liba 1.0 [list source -encoding utf-8 [file join $dir liba.tcl]]
    package ifneeded libb 1.0 [list source -encoding utf-8 [file join $dir libb.tcl]]

    - app.tcl
    # optional (if not installed in auto_path)
    lappend auto_path [file dirname [info script]]
    # load liba and libb
    package require liba
    package require libb

    If 'liba' and 'libb' are not only short versions for demo purposes,
    maybe it would be a good idea to name the packages with some prefix (app_liba etc), since plain 'liba' and 'libb' are rather generic...

    HTH
    R'

    Thanks. Although it actually seems more involved that what I've been doing. Also, in pkgIndex.tcl where does $dir's value come from?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mark@21:1/5 to All on Thu Jun 1 03:49:25 2023
    Splitting an application into multiple files must surely be a common requirement (and common practice). So I'm assuming that how to do this is documented. But where?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mark@21:1/5 to All on Thu Jun 1 04:10:50 2023
    Anyway, here's the structure I'm using and which seems to work based on your advice.

    # file: pkgIndex.tcl
    set dir [file dirname [info script]]
    package ifneeded app 1.0 [list source -encoding utf-8 [file join $dir app.tcl]] package ifneeded util 1.0 [list source -encoding utf-8 [file join $dir util.tcl]]

    # file: myapp.tcl # this is the file that's to be invoked, e.g., ./myapp.tcl lappend auto_path [file dirname [info script]]
    package require app
    app::main

    # file: app.tcl
    package provide app 1.0
    package require util
    namespace eval app {}

    proc app::main {} {
    puts "app.tcl main [util::commas 1234567890]"
    }

    # file: util.tcl
    package provide util 1.0
    namespace eval util {}

    proc util::commas {x} {
    regsub -all \\d(?=(\\d{3})+([regexp -inline {\.\d*$} $x]$)) $x {\0,}
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rich@21:1/5 to Mark on Thu Jun 1 13:07:39 2023
    Mark <m.n.summerfield@googlemail.com> wrote:
    Anyway, here's the structure I'm using and which seems to work based
    on your advice.


    If you are willing to restrict your filenames to a particular pattern,
    the module system is even easier to use than creating 'packages'.

    man tm

    for the manpage on the module system.

    You'd name the files

    liba-1.0.0.tm
    libb-1.0.0.tm

    And include a "package provide" with the same version number inside.

    And in your app.tcl you'd tell tm where to search:

    tcl::tm::path add [file dirname [info script]]

    And then you can just do:

    package require liba
    package require libb

    in app.tcl to pull both in.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Schelte@21:1/5 to Rich on Thu Jun 1 15:25:47 2023
    On 01/06/2023 15:07, Rich wrote:
    And include a "package provide" with the same version number inside.

    That would be redundant, because the version number is already specified
    in the file name and Tcl will pick it up from there. So there is no need
    to include a "package provide" command in a module code.


    Schelte.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rich@21:1/5 to Schelte on Thu Jun 1 13:58:37 2023
    Schelte <nospam@wanadoo.nl> wrote:
    On 01/06/2023 15:07, Rich wrote:
    And include a "package provide" with the same version number inside.

    That would be redundant, because the version number is already
    specified in the file name and Tcl will pick it up from there. So
    there is no need to include a "package provide" command in a module
    code.

    Hmm, learn something new every day.

    I've been adding 'package provide #' commands that I didn't need to
    add. Now I know to stop doing that for modules.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From clt.to.davebr@dfgh.net@21:1/5 to All on Thu Jun 1 14:12:33 2023
    The fileutil package from tcllib has fullnormalize that handles all links in a path

    package require fileutil
    set ::APP_PATH [file dir [fileutil::fullnormalize [info script]]]


    you might consider cd into the directory while loading resources:

    set dir [pwd]
    cd [file dir [fileutil::fullnormalize [info sript]]]
    try {foreach fn {liba.tcl libb.tcl} {source $fn}} finally {cd $dir}

    dave b

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ralf Fassel@21:1/5 to All on Thu Jun 1 16:57:55 2023
    * Mark <m.n.summerfield@googlemail.com>
    | On Thursday, June 1, 2023 at 9:53:03 AM UTC+1, Ralf Fassel wrote:
    | > * Mark <m.n.sum...@googlemail.com>
    | > - new file: pkgIndex.tcl
    | > package ifneeded liba 1.0 [list source -encoding utf-8 [file join $dir liba.tcl]]
    | > package ifneeded libb 1.0 [list source -encoding utf-8 [file join $dir libb.tcl]]
    --<snip-snip>--
    | Also, in pkgIndex.tcl where does $dir's value come from?

    $dir is automatically provided by the mechanism which loads the
    pkgIndex.tcl. If you look into other pkgIndexes (eg. from tcllib),
    you'll see that they don't set 'dir' either.
    You should omit that line from your pkgIndex.tcl.

    HTH
    R'

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mark@21:1/5 to All on Thu Jun 1 07:27:58 2023
    I read the docs for tm and they say "A Tcl Module is a Tcl Package contained in a single file, and no other files required by it". This seems a bit restrictive compared to the package approach. And anyway, I'm only really looking at how to spread an
    application over multiple files; all the tcl files are for the application, they're not self-contained as such.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Robert Heller@21:1/5 to Mark on Thu Jun 1 14:57:31 2023
    At Thu, 1 Jun 2023 07:27:58 -0700 (PDT) Mark <m.n.summerfield@googlemail.com> wrote:



    I read the docs for tm and they say "A Tcl Module is a Tcl Package contained in a single file, and no other files required by it". This seems a bit restrictive compared to the package approach. And anyway, I'm only really looking at how to spread an application over multiple files; all the tcl files are for the application, they're not self-contained as such.

    You can have a "master" .tm file, that source's any number of other files:


    foo-1.2.3.tm:

    set dir [file dirname [info script]]

    source [file join $dir foo-a.tcl]
    source [file join $dir foo-b.tcl]
    source [file join $dir foo-c.tcl]

    so

    package require foo 1.2.3

    will load foo-a.tcl, foo-b.tcl, and foo-c.tcl.




    --
    Robert Heller -- Cell: 413-658-7953 GV: 978-633-5364
    Deepwoods Software -- Custom Software Services
    http://www.deepsoft.com/ -- Linux Administration Services
    heller@deepsoft.com -- Webhosting Services

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mark@21:1/5 to All on Thu Jun 1 07:37:01 2023
    I've now tried using modules and despite the docs it seems to work fine and requires one less file (no need for pkgIndex.tcl)

    # file: myapp.tcl
    tcl::tm::path add [file dirname [info script]]
    package require app
    app::main

    # file: app-1.0.0.tm
    package provide app 1.0
    package require util
    namespace eval app {}

    proc app::main {} {
    puts "app.tcl main [util::commas 1234567890]"
    }

    # file: util-1.0.0.tm
    package provide util 1.0
    namespace eval util {}

    proc util::commas {x} {
    regsub -all \\d(?=(\\d{3})+([regexp -inline {\.\d*$} $x]$)) $x {\0,}
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ralf Fassel@21:1/5 to All on Thu Jun 1 17:00:35 2023
    * Mark <m.n.summerfield@googlemail.com>
    | I've now tried using modules and despite the docs it seems to work
    | fine and requires one less file (no need for pkgIndex.tcl)

    Even better, thanks for sharing!

    R'

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rich@21:1/5 to Mark on Thu Jun 1 16:39:20 2023
    Mark <m.n.summerfield@googlemail.com> wrote:
    I've now tried using modules and despite the docs it seems to work fine and requires one less file (no need for pkgIndex.tcl)

    # file: myapp.tcl
    tcl::tm::path add [file dirname [info script]]
    package require app
    app::main

    # file: app-1.0.0.tm
    package provide app 1.0
    package require util
    namespace eval app {}

    proc app::main {} {
    puts "app.tcl main [util::commas 1234567890]"
    }

    # file: util-1.0.0.tm
    package provide util 1.0
    namespace eval util {}

    proc util::commas {x} {
    regsub -all \\d(?=(\\d{3})+([regexp -inline {\.\d*$} $x]$)) $x {\0,}
    }

    As I just learned from a post a few hours ago, you don't need the
    "package provide" lines inside module (.tm) files. So you could drop
    those lines as well.

    And you do not /need/ to make the main 'app' a module file (unless you
    really want to do so). The /secondary/ files can be modules, and the
    main app just a script that loads the modules.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Robert Heller@21:1/5 to Mark on Thu Jun 1 19:01:01 2023
    This is a "normal" warning. It can be safely ignored. nagelfar has no way of "knowing" about procs defined in a file other than the one it is currently processing.

    At Thu, 1 Jun 2023 11:40:18 -0700 (PDT) Mark <m.n.summerfield@googlemail.com> wrote:


    Although the above works fine, it causes nagelfar to complain:

    $ nagelfar.tcl -quiet -H -tab 4 -len 76 -Wunusedvar *.{tcl,tm}
    myapp.tcl: 8: W Unknown command "app::main"
    app-1.0.0.tm: 9: W Unknown command "util::commas"



    --
    Robert Heller -- Cell: 413-658-7953 GV: 978-633-5364
    Deepwoods Software -- Custom Software Services
    http://www.deepsoft.com/ -- Linux Administration Services
    heller@deepsoft.com -- Webhosting Services

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mark@21:1/5 to All on Thu Jun 1 11:40:18 2023
    Although the above works fine, it causes nagelfar to complain:

    $ nagelfar.tcl -quiet -H -tab 4 -len 76 -Wunusedvar *.{tcl,tm}
    myapp.tcl: 8: W Unknown command "app::main"
    app-1.0.0.tm: 9: W Unknown command "util::commas"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mark@21:1/5 to All on Thu Jun 1 11:19:01 2023
    Thanks for the help.

    So now I'm still only using 3 files but with even less boilerplate than before. And this is a nice scalable solution.

    # file: myapp.tcl # this is the "driver" the user runs: ./myapp.tcl tcl::tm::path add [file dirname [info script]]
    package require app
    app::main

    # file: app-1.0.0.tm # this is the application module for main
    package require util
    namespace eval app {}

    proc app::main {} {
    puts "app-1.0.0.tm main [util::commas 1234567890]"
    }

    # file: util-1.0.0.tm # this is a supporting module
    namespace eval util {}

    proc util::commas {x} {
    regsub -all \\d(?=(\\d{3})+([regexp -inline {\.\d*$} $x]$)) $x {\0,}
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From pd@21:1/5 to All on Fri Jun 2 02:55:04 2023
    El jueves, 1 de junio de 2023 a las 16:28:02 UTC+2, Mark escribió:
    I read the docs for tm and they say "A Tcl Module is a Tcl Package contained in a single file, and no other files required by it". This seems a bit restrictive compared to the package approach. And anyway, I'm only really looking at how to spread an
    application over multiple files; all the tcl files are for the application, they're not self-contained as such.

    so what's the real difference between a package and a module in terms of use? it seems modules are just an evolution over packages intended to the same use.

    After reading the replies in this thread (and without reading any doc), I have a doubt, if package provide is not required in a module, how tcl knows what names you want to export in the module? is it the case that all symbols defined in the module are
    public by default (and thus imported when you do a package require)?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rich@21:1/5 to eukelade@gmail.com on Fri Jun 2 12:37:15 2023
    pd <eukelade@gmail.com> wrote:
    El jueves, 1 de junio de 2023 a las 16:28:02 UTC+2, Mark escribió:
    I read the docs for tm and they say "A Tcl Module is a Tcl Package
    contained in a single file, and no other files required by it".
    This seems a bit restrictive compared to the package approach. And
    anyway, I'm only really looking at how to spread an application over
    multiple files; all the tcl files are for the application, they're
    not self-contained as such.

    so what's the real difference between a package and a module in terms
    of use?

    Use?

    By an end user: no difference (other than needing the tm::path setup
    properly to find them -- which should be handled by the app. code).

    By a creator: modules are slightly simpler to create, but offer
    slightly less flexibility to the creator in the process.

    Modules also potentially might be slightly faster to source on first
    use, as they are a set of files in a directory, and the filenames
    define the module names and versions so a single glob of a single
    directory can retreive all the names and versions.

    For packages, they are each in an individual directory, and the
    pkgIndex.tcl in each has to be sourced to find the names and versions,
    so finding packages involves a directory tree walk and sourcing of
    potentially a lot of pkgIndex.tcl files.

    it seems modules are just an evolution over packages intended to the
    same use.

    In effect, yes.

    After reading the replies in this thread (and without reading any
    doc), I have a doubt, if package provide is not required in a module,

    After Shelte indicated this was so, I did a quick test. It is not
    required in a module. Tcl picks up the version from the module
    filename.

    how tcl knows what names you want to export in the module?

    The same way as it knows for a package, by what you define in the
    module file. If the module defines a namespace (and it should, to
    avoid name collisions) then the names to be exported are the names
    given to the namespace export command that should be part of the module
    file.

    is it the case that all symbols defined in the module are public by
    default (and thus imported when you do a package require)?

    No, importing expects a namespace, and importing requires that the
    nameespace first export the names that should be 'importable'. I.e.,
    as with globals in Tcl, namespace names are not normally importable
    unless you specify them to be so.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From et99@21:1/5 to All on Fri Jun 2 11:34:20 2023
    On 6/2/2023 2:55 AM, pd wrote:
    El jueves, 1 de junio de 2023 a las 16:28:02 UTC+2, Mark escribió:
    I read the docs for tm and they say "A Tcl Module is a Tcl Package contained in a single file, and no other files required by it". This seems a bit restrictive compared to the package approach. And anyway, I'm only really looking at how to spread an
    application over multiple files; all the tcl files are for the application, they're not self-contained as such.

    so what's the real difference between a package and a module in terms of use? it seems modules are just an evolution over packages intended to the same use.

    After reading the replies in this thread (and without reading any doc), I have a doubt, if package provide is not required in a module, how tcl knows what names you want to export in the module? is it the case that all symbols defined in the module
    are public by default (and thus imported when you do a package require)?


    One can use a [package provide] command if one really
    wants to, but it's not necessary. If used, it does add
    a slight maintenance cost, since it will need to be
    updated if/when a new version is added. It has to
    agree with the version that the file name is encoded with.

    Modules, like packages, are intended to be code that
    forms a library where ordinarily more than one program
    will be using the code. For example, the http package
    is actually provided as a module. A module is simply
    a lite weight package, that is pure tcl and can be
    loaded via a source command.

    One can use the *list* subcommand to find which of the
    directories are built in searches similar to how the
    global auto_path is setup. For example,

    $ tclsh
    % join [tcl::tm::path list] \n
    /usr/lib/tcltk/tcl8/site-tcl
    /usr/lib/tcltk/tcl8/8.0
    /usr/lib/tcltk/tcl8/8.1
    /usr/lib/tcltk/tcl8/8.2
    /usr/lib/tcltk/tcl8/8.3
    /usr/lib/tcltk/tcl8/8.4
    /usr/lib/tcltk/tcl8/8.5
    /usr/lib/tcltk/tcl8/8.6
    /usr/lib/tcl8/site-tcl
    /usr/lib/tcl8/8.0
    /usr/lib/tcl8/8.1
    /usr/lib/tcl8/8.2
    /usr/lib/tcl8/8.3
    /usr/lib/tcl8/8.4
    /usr/lib/tcl8/8.5
    /usr/lib/tcl8/8.6
    /usr/share/tcltk/tcl8/site-tcl
    /usr/share/tcltk/tcl8/8.0
    /usr/share/tcltk/tcl8/8.1
    /usr/share/tcltk/tcl8/8.2
    /usr/share/tcltk/tcl8/8.3
    /usr/share/tcltk/tcl8/8.4
    /usr/share/tcltk/tcl8/8.5
    /usr/share/tcltk/tcl8/8.6
    /usr/share/tcltk/tcl8.6/tcl8

    $ ls /usr/share/tcltk/tcl8.6/tcl8
    http-2.9.0.tm msgcat-1.6.1.tm platform-1.0.14.tm tcltest-2.5.0.tm

    So, if one wants to supply a module that does not
    need a [tcl::tm::path add] one can place a module
    into one of the *known* directories listed above.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)