Allow environment variables in config files

:Namespace Boot

    (⎕ML ⎕IO)←1

    :section Startup/Shutdown

    ∇ {msroot}Run root
      :If 0≠⎕NC'msroot' ⋄ MSRoot←msroot ⋄ :EndIf
      AppRoot←folderize root  ⍝ application (website) root
      Load AppRoot ⍝ load essential objects
      ms←Init ConfigureServer AppRoot ⍝ read configuration and create server instance
      Configure ms
      ms.Run
    ∇

    ∇ {msroot}RunWC2 root
      :If 0≠⎕NC'msroot' ⋄ MSRoot←msroot ⋄ :EndIf
      AppRoot←folderize root  ⍝ application (website) root
      Load AppRoot ⍝ load essential objects
      !!!ms←1 Init ConfigureServer AppRoot ⍝ read configuration and create server instance
      Configure ms
      {ms.Run}
    ∇

    ∇ Load AppRoot;filterOut;files;HTML;f;failed;dir;name;file;folder;callingEnv
      ⍝ Load required objects for MiServer

      HtmlRenderer←{0::⍵ ⋄ HtmlRenderer}0

      :If 0=#.⎕NC'Files' ⋄ ⎕SE.SALT.Load MSRoot,'Utils/Files -target=# -nolink' ⋄ :EndIf

      filterOut←{⍺←'' ⋄ ⍺{0∊⍴⍺:⍵ ⋄ ⍺{∊¨↓⍵⌿⍨~⍵[;2]∊eis ⍺}↑⎕NPARTS¨⍵}⊃#.Files.Dir ⍵,'/*.dyalog'}

      files←'Boot'filterOut MSRoot,'Core'
      files,←'Files'filterOut MSRoot,'Utils' ⍝ find utility libraries
      files,←filterOut MSRoot,'Extensions'

      failed←''
      :For f :In files
          {326=⎕DR ⍵: ⋄ '***'≡3↑⍵:failed,←⊂(('<.+>'⎕S{1↓¯1↓⍵.Match})⍵)}⎕SE.SALT.Load f,' -target=# -nolink' ⍝ do not reload already loaded spaces
      :EndFor

      :For file :In failed
          disperror ⎕SE.SALT.Load∊'"',file,'" -target=# -nolink'
      :EndFor

      HTML←'_JQ' '_JS'{⍵[⍋⍺⍳(↑⎕NPARTS¨⍵)[;2]]}filterOut MSRoot,'HTML' ⍝ prioritize loading of _JQ and _JS

      #.SupportedHtml5Elements.Build_html_namespace

      ⍝↓↓↓ Some controls may require controls in other folders.
      ⍝    So we attempt to load everything, and keep track of what failed
      ⍝    and then go back and try to load the failed controls again their
      failed←''
      :For f :In HTML
          (folder name)←2↑⎕NPARTS f
          disperror ⎕SE.SALT.Load f,' -target=# -nolink'
          :If #.Files.DirExists dir←folder,name,'/'
              dir∘{326=⎕DR ⍵: ⋄ '***'≡3↑⍵:failed,←⊂⍺(('<.+>'⎕S{1↓¯1↓⍵.Match})⍵)}¨⎕SE.SALT.Load dir,'* -nolink -target=#.',name
          :EndIf
      :EndFor

      :For (f file) :In failed
          disperror ⎕SE.SALT.Load∊'"',file,'" -nolink -target=#.',f
      :EndFor

      LoadFromFolder MSRoot,'Loadable'

      'Pages'#.⎕NS'' ⍝ Container Space for loaded classes
      #.Pages.(MiPage RESTfulPage)←#.(MiPage RESTfulPage)

      'CachedPages'#.⎕NS'' ⍝ Container for cached pages

      BuildEAWC ⍝ build the Easy As ⎕WC namespace

⍝ Now load any code from the MiSite

      :If ~0∊⍴AppRoot
          :Trap 22
              :For class :In filterOut AppRoot,'Code' ⍝ Classes in application folder
                  disperror ⎕SE.SALT.Load class,' -target=#'
              :EndFor

              :If #.Files.DirExists AppRoot,'/Code/Templates/'
                  disperror ⎕SE.SALT.Load AppRoot,'/Code/Templates/* -target=#.Pages'
              :EndIf
          :EndTrap
      :EndIf
    ∇

    ∇ Cleanup
      #.⎕EX¨classes
      #.⎕EX¨utils
      #.⎕EX'Pages'
      #.⎕EX'CachedPages'
      #.⎕EX¨'MiServer' 'HTTPRequest'
    ∇

    ∇ ms←{HtmlRenderer}Init Config;path;class;classes;e;res;mask
     ⍝ Create instances of MiServer, Session and Authentication Handlers

      HtmlRenderer←{0::⍵ ⋄ HtmlRenderer}0  ⍝ using HTMLRenderer?

      :If 0≠HtmlRenderer
          ms←⎕NS''
          ms.Config←Config
          ms.Log←{⎕←⍵}
      :Else
          ms←⎕NEW(#⍎Config.ClassName)Config
          path←MSRoot,'Extensions/'

          :If 0≠⍴Config.SessionHandler
              class←⎕SE.SALT.Load path,Config.SessionHandler
              ms.SessionHandler←⎕NEW class ms
          :EndIf

          :If 0≠⍴Config.Authentication
              class←⎕SE.SALT.Load path,Config.Authentication
              ms.Authentication←⎕NEW class ms
          :EndIf

          :If 0≠⍴Config.SupportedEncodings
              {}⎕SE.SALT.Load path,'ContentEncoder'
              :For e :In Config.SupportedEncodings
                  class←⎕SE.SALT.Load path,e
                  ms.Encoders,←⎕NEW class
              :EndFor
              :If ∨/mask←0≠1⊃¨res←ms.Encoders.Init
                  2 ms.Log'Content Encoding Initialization failed for:',∊' ',¨mask/ms.Encoders.Encoding
                  ms.Encoders←(~mask)/ms.Encoders
              :EndIf
          :EndIf
          Config.UseContentEncoding∧←0≠⍴ms.Encoders

          :If 0≠⍴Config.Logger
              class←⎕SE.SALT.Load path,Config.Logger
              ms.Logger←⎕NEW class ms
          :EndIf
      :EndIf
    ∇

    ∇ End;classes;z;m
      ⍝ Clean up the workspace

      :If 9=⎕NC'ms'
          :Trap 0
              ms.End
          :EndTrap
          {}try'⎕EX⍕⊃⊃⎕CLASS ms.SessionHandler'
          {}try'⎕EX⍕⊃⊃⎕CLASS ms.Authentication'
          {}try'⎕EX⍕⊃⊃⎕CLASS ms.Logger'
          {}try'⎕EX⍕¨∪∊ ⎕CLASS¨ms.Encoders'
          ⎕EX⍕⊃⊃⎕CLASS ms
          ⎕EX'ms'
      :EndIf

      :If 9=⎕NC'SQA'
          {}try'SQA.Close''.'''
      :EndIf

      :If 0≠⍴classes←↓#.⎕NL 9.4
      :AndIf 0≠⍴classes←(m←2=⊃∘⍴¨z←⎕CLASS¨#⍎¨classes)/classes
      :AndIf 0≠⎕NC'#.MiPage'
          classes←(#.MiPage≡¨2 1∘⊃¨m/z)/classes
          #.⎕EX↑⍕¨classes ⍝ Erase loaded classes
      :EndIf

      ⎕EX'#.MiPage'
      ⎕EX'AppRoot'
      {}try'#.DRC.Close ''.'''
      ⎕EX'#.DRC'
      #.Conga.UnloadSharedLib
      {}⎕WA
    ∇

    ∇ BuildEAWC;src;sources;fields;source;list;mask;refs;target
     ⍝ Build the Easy As ⎕WC namespace from core classes and its own source
     ⍝ Also build the #._ namespace with shortcuts
      sources←#._html #._SF #._JQ #._DC #._JS
⍝      fields←''
      '_'#.⎕NS''
      :For source :In sources
          list←source.⎕NL ¯9.4
          list←list/⍨'_'≠⊃¨list
          :If ∨/mask←0≠refs←source{6::0 ⋄ (⍕⍺){('.'∊1↓s↓r)<⍺≡(s←⍴⍺)↑r←⍕⍵}t←⍺⍎⍵:t ⋄ 0}¨list
              #._⍎∊(mask/list){'⋄',⍺,'←',⍕⍵}¨mask/refs
          :EndIf
      :EndFor
    ∇

    ∇ {root}LoadFromFolder path;type;name;nsName;parts;ns
    ⍝ Loads an APL "project" folder
      root←{6::⍵ ⋄ root}#
      :For name type :In ↓{⍵[⍒⍵[;2];]}⍉↑0 1 #.Files.Dir path,'/*'
          nsName←∊1↓parts←1 ⎕NPARTS name
          :If 1=type ⍝ directory?
              :Select ⊃root.⎕NC nsName
              :Case 9 ⋄ ns←⍕root⍎nsName
              :Case 0 ⋄ ns←nsName root.⎕NS''
              :Else ⋄ Log'"',name,'" is not a namespace'
              :EndSelect
              ⎕SE.SALT.Load name,'/* -target=',⍕ns
          :Else
              :If ~∨/∊(⎕NSI,¨'.')⍷⍨¨⊂'.',(2⊃1 ⎕NPARTS name),'.' ⍝ don't load it if we're being called from it
                  ⎕SE.SALT.Load name,' -target=',⍕root
              :EndIf
          :EndIf
      :EndFor
    ∇

    :endsection

    :section Configuration

    ∇ Configure ms
      ConfigureDatasources ms
      ConfigureVirtual ms
      ConfigureResources ms
      ConfigureContentTypes ms
      ms AddConfiguration'MappingHandlers'
    ∇

    ∇ ms AddConfiguration name;conf
      conf←ReadConfiguration name
      {ms.Config⍎name,'←⍵'}conf
    ∇

    ∇ r←ns Setting pars;name;num;default;mask
    ⍝ returns setting from a config style namespace or provides a default if it doesn't exist
    ⍝ pars - name [num] [default]
    ⍝ ns - namespace reference
    ⍝ name - name of the setting
    ⍝ num - 1 if setting is numeric scalar, (,1) if numeric vector is allowed, 0 otherwise
    ⍝ default - default value if not found
      pars←eis pars
      (name num)←2↑pars,(⍴pars)↓'' 0 ''
      :If 2<⍴pars ⋄ default←3⊃pars
      :Else ⋄ default←(1+num)⊃''⍬
      :EndIf
      r←(⍴ns)⍴⊂default
      :If ∨/mask←0≠⊃¨ns.⎕NC⊂name
          (mask/r)←(((⍴⍴num)∘tonum)⍣(⊃num))¨(mask/ns).⍎⊂name
      :EndIf
      :If 0=⍴⍴r ⋄ r←⊃r ⋄ :EndIf
    ∇

    ∇ SubstEnv config;getEnv;subst;n;v
    ⍝ substitute environment variables for items with values im the form $envName$
    ⍝ config is a configuration result from ReadConfiguration
    ⍝   it should be either a namespace reference, or a vector of namespace references
      getEnv←{2 ⎕NQ'.' 'GetEnvironment'⍵}
      subst←{
          0∊⍴⍵:⍵ ⋄
          '$'∧.=(⊣/,⊢/)(2⌈≢⍵)↑⍵:getEnv 1↓¯1↓⍵ ⋄
          ⍵}
      :Select ⊃⎕NC'config'
      :Case 2
          :If 9.1∧.={⎕NC⊂,'⍵'}¨config
              SubstEnv¨config
          :EndIf
      :Case 9
          :For n :In config.⎕NL ¯2
              :Select ≡v←config⍎n
              :Case 1
                  v←subst v
              :Case 2
                  v←subst¨v
              :Else
                  ∘∘∘
              :EndSelect
              ⍎'config.',n,'←v'
          :EndFor
      :EndSelect
    ∇

    ∇ config←{element}ReadConfiguration type;serverconfig;file;siteconfig;thing;ind;mask
    ⍝ Attempt to read configuration file
    ⍝ 1) from server root MSRoot
    ⍝ 2) from site root AppRoot
    ⍝ merging the two if they both exist - site settings overrule server settings
      config←''
      :If #.Files.Exists file←MSRoot,'Config/',type,'.xml'
          config←serverconfig←(#.XML.ToNS #.Files.ReadText file)⍎type
      :EndIf
      :If #.Files.Exists file←AppRoot,'Config/',type,'.xml'
          siteconfig←(#.XML.ToNS #.Files.ReadText file)⍎type
          :If 0∊⍴config
              config←siteconfig
          :ElseIf 0=⎕NC'element'
              {}{try'serverconfig.',⍵,'←siteconfig.',⍵}¨siteconfig.⎕NL ¯2
          :Else ⍝ element specifies the element(s) to search on
              :For thing :In siteconfig
                  :If 0≠thing.⎕NC element
                      :If ∨/mask←0≠∊serverconfig.⎕NC⊂element
                          :If (+/mask)<ind←((mask/serverconfig)⍎¨⊂element)⍳⊂thing⍎element
                              serverconfig,←thing
                          :Else
                              serverconfig[(mask/⍳⍴mask)[ind]]←thing
                          :EndIf
                      :Else
                          serverconfig,←thing
                      :EndIf
                  :EndIf
              :EndFor
              config←serverconfig
          :EndIf
      :EndIf
      :If config≢'' ⋄ SubstEnv config ⋄ :EndIf
    ∇

    ∇ Config←ConfigureServer AppRoot;file
    ⍝ configure server level settings, setting defaults for needed ones that are not supplied
      Config←ReadConfiguration'Server'

      Config.AllowedHTTPMethods←{⍵⊆⍨~⍵∊' ,'}#.Strings.lc Config Setting'AllowedHTTPMethods' 0 'get,post'
      Config.AppRoot←AppRoot
      Config.Authentication←Config Setting'Authentication' 0 'SimpleAuth'
      Config.CertFile←Config Setting'CertFile' 0 ''
      Config.ClassName←Config Setting'ClassName' 0 'MiServer'
      Config.CloseOnCrash←Config Setting'CloseOnCrash' 1 0
      Config.Debug←Config Setting'Debug' 1 0
      Config.DecodeBuffers←Config Setting'DecodeBuffers' 1 1 ⍝ allow Conga to decode HTTP messages (1)
      Config.DefaultExtension←Config Setting'DefaultExtension' 0 '.mipage'
      Config.DefaultPage←Config Setting'DefaultPage' 0 'index.mipage'
      Config.DirectFileSize←{⍵[⍋⍵]}0⌈⌊2↑Config Setting'DirectFileSize'(,1)⍬
      Config.FIFOMode←Config Setting'FIFOMode' 1 0 ⍝ Conga FIFO mode default to off (0)
      Config.FormatHtml←Config Setting'FormatHtml' 1 0
      Config.Host←Config Setting'Host' 0 'localhost'
      Config.HTTPCacheTime←'m'#.Dates.ParseTime Config Setting'HTTPCacheTime' 0 '0' ⍝ default to off (0)
      Config.IdleTimeout←'s'#.Dates.ParseTime Config Setting'IdleTimeout' 0 '0' ⍝ default to none (0)
      Config.KeyFile←Config Setting'KeyFile' 0 ''
      Config.Lang←Config Setting'Lang' 0 'en'
      Config.LogMessageLevel←Config Setting'LogMessageLevel' 1 1 ⍝ default to error messages only
      Config.Logger←Config Setting'Logger' 0 ''
      Config.MSRoot←MSRoot
      Config.Name←Config Setting'Name' 0 'MiServer'
      Config.Port←Config Setting'Port' 1 8080
      Config.Ports←Config Setting'Ports'(,1)⍬
      Config.Production←Config Setting'Production' 1 0 ⍝ production mode?  (0/1 = development debug framework en/disabled)
      Config.RESTful←Config Setting'RESTful' 1 0 ⍝ RESTful web service?
      Config.RootCertDir←Config Setting'RootCertDir' 0 ''
      Config.Root←AppRoot
      Config.SSLFlags←Config Setting'SSLFlags' 1(32+64)  ⍝ Accept Without Validating, RequestClientCertificate
      Config.Secure←Config Setting'Secure' 1 0
      Config.Server←Config Setting'Server' 0 ''
      Config.SessionHandler←Config Setting'SessionHandler' 0 'SimpleSessions'
      Config.SessionTimeout←'m'#.Dates.ParseTime Config Setting'SessionTimeout' 0 '30m' ⍝ 30 minute timeout
      Config.SupportedEncodings←{(⊂'')~⍨1↓¨(⍵=⊃⍵)⊂⍵}',',Config Setting'SupportedEncodings' 0
      Config.TrapErrors←Config Setting'TrapErrors' 1 0
      Config.WaitTimeout←#.Dates.ParseTime Config Setting'WaitTimeout' 0 '5000ms' ⍝ 5000 msec (5 second timeout)
      Config.UseContentEncoding←Config Setting'UseContentEncoding' 1 0 ⍝ aka HTTP Compression default off (0)

      :If 0≠⎕NC'#.DrA' ⍝ Transfer DrA config options
          {}#.DrA.SetDefaults
          #.DrA.AppName←Config.Name
          #.DrA.MailMethod←Config Setting'MailMethod' 0 'NONE'
          #.DrA.MailRecipient←Config Setting'MailRecipient' 0
          #.DrA.Mode←Config.Debug
          #.DrA.NoUser←Config Setting'NoUser' 1 1 ⍝ run without user interaction
          #.DrA.Path←AppRoot ⍝ Where to put log files
          #.DrA.SMTP_Gateway←Config Setting'SMTP_Gateway' 0
          #.DrA.UseHTTP←Config Setting'UseHTTP' 1 0
      :EndIf
    ∇

    ∇ ConfigureDatasources ms;file;ds;name;tmp;orig;dyalog
      ⍝ load any datasource definitions
      :If ~0∊⍴ms.Datasources←'Name'ReadConfiguration'Datasources'
          :For ds :In ms.Datasources
              :For name :In ds.⎕NL ¯2
                  orig←tmp←ds.⍎name
                  tmp←SubstPath tmp
                  :If orig≢tmp
                      ⍎'ds.',name,'←tmp'
                  :EndIf
              :EndFor
          :EndFor

          :Trap 0
              :If 0=#.⎕NC'SQA'
                  dyalog←('/\'[1+'Win'≡3↑1⊃#.⎕WG'APLVersion']){⍵,(-⍺=¯1↑⍵)↓⍺}2 ⎕NQ'.' 'GetEnvironment' 'DYALOG'
                  'SQA'#.⎕CY dyalog,'ws/sqapl' ⍝ copy in SQA
              :EndIf
              :If 0≠1⊃#.SQA.Init'' ⍝ and initialize
                  1 ms.Log'SQA failed to initialize'
              :EndIf
          :EndTrap
      :EndIf
    ∇

    ∇ ConfigureVirtual ms;file;virtual;mask;inds;v
      ⍝ load virtual (alias) definitions if any
      ms.Config.Virtual←''
      :If ~0∊⍴virtual←'alias'ReadConfiguration'Virtual'
          :If 0∊mask←{⍵=⍳⍴⍵}inds←{⍵⍳⍵}virtual.alias ⍝ check for duplicate aliases, keep first
              1 ms.Log'Duplicate virtual aliases defined (first occurrence will be used): ',1↓∊',',¨(~mask)/virtual.alias
              virtual←mask/virtual
          :EndIf
          ⍝ substitute server and site root if found
          virtual.path←SubstPath¨virtual.path
          :For v :In virtual
              :If ~#.Files.DirExists v.path ⍝ check if mapped path exists
                  1 ms.Log'Virtual path not found: ',v.path
                  virtual~←v
              :Else
                  :If #.Files.DirExists AppRoot,v.alias ⍝ check if alias conflicts with local path
                      1 ms.Log'Virtual alias "',v.alias,'" overrides site path of same name.'
                  :EndIf
              :EndIf
          :EndFor
          ms.Config.Virtual←virtual
      :EndIf
    ∇

    ∇ ConfigureResources ms;file;mask;inds;names;uses;map;n;res;f;missing;order;resources;files;which
      ⍝ load resource definitions if any
      ms.Config.Resources←0 3⍴⊂''
      :If ~0∊⍴res←'name'ReadConfiguration'Resources'
          :If 0∊mask←{⍵=⍳⍴⍵}inds←{⍵⍳⍵}names←res.name ⍝ check for duplicate aliases, keep first
              1 ms.Log'Duplicate resources defined (first occurence will be used): ',1↓∊',',¨(~mask)/res.name
              res←mask/res
          :EndIf
          ⍝ build the dependency map
          uses←{(eis⍣(⊃0<⍴⍵))⍵}¨res Setting'uses'
          inds←res.name∘⍳¨uses
          n←⊃⍴res
          :If ∨/mask←∨/¨missing←n<inds
              1 ms.Log'Invalid resource dependency found for:',∊' ',¨mask/res.name
              inds←(~missing)/¨inds
          :EndIf
          order←OrderResources inds
          inds←(order∘⍳¨inds)[order]
          res←res[order]
          map←,(2⍴n)⍴(1+n)↑1 ⍝ identity matrix
          map[∊(n×¯1+⍳n)+¨inds]←1
          map←(2⍴n)⍴map
          map←↓{({⍺∨⍺∨.∧⍵}⍣≡)⍨⍵}map
          ⍝ ms.Config.Resources[;1] resource name, [;2] scripts used [;3] styles used
          f←{SubstPath¨{⍵~⊂''}∘∪¨⊃¨,/¨map/¨⊂eis¨⍵}
          ms.Config.Resources←res.name,(f res Setting'script'),[1.1]f res Setting'style'
          resources←∪⊃,/,0 1↓ms.Config.Resources
          files←ms.Config Virtual¨resources
          :If ∨/missing←~#.Files.Exists∘{⍵/⍨∧\⍵≠'?'}¨files
              which←(0 1↓ms.Config.Resources)∊¨⊂missing/resources
              mask←∨/∨/¨which
              1 ms.Log'Resource files not found:'
              1 ms.Log¨↓⍕(1,mask⌿which)/¨mask⌿ms.Config.Resources
              (mask⌿ms.Config.Resources)←(mask⌿ms.Config.Resources)~¨⊂missing/resources
          :EndIf
      :EndIf
    ∇

    ∇ file←Config Virtual page;mask;f;ind;t;path;root
      :Access public shared
    ⍝ checks for virtual directory
      root←(-'/\'∊⍨¯1↑root)↓root←Config.Root
      page←('/\'∊⍨1↑page)↓page
      file←root,'/',page
      :If 0<⍴Config.Virtual
          ind←Config.Virtual.alias⍳⊂t←{(¯1+⍵⍳'/')⍴⍵}page
          :If ind≤⍴Config.Virtual.alias
              path←ind⊃Config.Virtual.path
              file←#.Files.Normalize path,('/\'∊⍨¯1↑path)↓(⍴t)↓page
          :EndIf
      :EndIf
    ∇

    ∇ r←OrderResources inds;n;mask
      n←⍳⍴inds
      r←n/⍨mask←0∘∊∘⍴¨inds ⍝ roots with no dependencies
      inds/⍨←~mask
      n/⍨←~mask
      :While ~0∊⍴inds
          mask←∧/¨inds∊¨⊂r
          r,←mask/n
          inds/⍨←~mask
          n/⍨←~mask
      :EndWhile
    ∇

    ∇ ConfigureContentTypes ms;file;ct;inds;exts;types;mask;exp;n
      ⍝ load supported content types
      ms.Config.ContentTypes←0 2⍴⊂''
      :If ~0∊⍴ct←ReadConfiguration'ContentTypes'
          exts←#.Strings.lc¨ct Setting'ext'
          types←ct Setting'type'
          :If ~0∊⍴inds←{⍵/⍳⍴⍵}∊','∊¨exts
              mask←(⍴exts)⍴1
              exp←{⎕ML←3 ⋄ (⍵≠',')⊂⍵}¨exts[inds]
              mask[inds]←n←⍬∘⍴∘⍴¨exp
              (exts types)←mask∘/¨exts types
              exts[(n/inds)++\~∊n↑¨1]←⊃,/exp
          :EndIf
          ms.Config.ContentTypes←exts,[1.1]types
      :Else
          1 ms.Log'No content types defined?'
      :EndIf
    ∇

    :Class ConfigSpace
        :field public config

        ∇ make filename;ns;n
          :Access public
          :Implements constructor
          ns←#.XML.ToNS #.Files.ReadText filename
          'Config file needs a single root node'⎕SIGNAL 11/⍨1≠⍴n←ns.⎕NL ¯9
          config←ns⍎⊃n
        ∇

        ∇ r←Get args
          :Access public
          r←config Setting args
        ∇

        ∇ r←ns Setting pars;name;num;default;mask
    ⍝ returns setting from a config style namespace or provides a default if it doesn't exist
    ⍝ pars - name [num] [default]
    ⍝ ns - namespace reference
    ⍝ name - name of the setting
    ⍝ num - 1 if setting is numeric scalar, (,1) if numeric vector is allowed, 0 otherwise
    ⍝ default - default value if not found
          pars←eis pars
          (name num)←2↑pars,(⍴pars)↓'' 0 ''
          :If 2<⍴pars ⋄ default←3⊃pars
          :Else ⋄ default←(1+num)⊃''⍬
          :EndIf
          r←(⍴ns)⍴⊂default
          :If ∨/mask←0≠⊃¨ns.⎕NC⊂name
              (mask/r)←(((⍴⍴num)∘tonum)⍣(⊃num))¨(mask/ns).⍎⊂name
          :EndIf
          :If 0=⍴⍴r ⋄ r←⊃r ⋄ :EndIf
        ∇

        eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple

          tonum←{⍺←0
              1∊⍺:tonumvec ⍵
              w←⍵ ⋄ ((w='-')/w)←'¯'
              ⊃⊃{~∧/⍺:⎕SIGNAL 11 ⋄ ⍵}/⎕VFI w}

        ∇ r←tonumvec v;to;minus;digits;c;mask
    ⍝ tonum vector version
    ⍝ allows for specific of ranges and comma or space delimited numbers
    ⍝ tonumvec '8080-8090'  or '5,7-9,11-15'
          r←⍬
          ⎕SIGNAL 11/⍨~∧/v∊⎕D,'., -¯'
          to←{⍺←⍵ ⋄ ⍺,⍺+(¯1*⍺>⍵)×⍳|⍺-⍵}
          v←('^\s*|\s*$'⎕R'')('\s+'⎕R' ')('\s*-\s*'⎕R'-')v
          minus←'-'=v
          digits←v∊⎕D,'.'
          ((minus>(minus∨{1↓⍵,0}digits)∧{¯1↓0,⍵}digits)/v)←⊂'¯'
          ((' '=v)/v)←','
          (('-'=v)/v)←⊂' to '
          :Trap 0
              :For c :In {⎕ML←3 ⋄ ⍵⊂⍨⍵≠','}∊v
                  r,←⍎∊c
              :EndFor
          :Else
              ⎕SIGNAL 11
          :EndTrap
        ∇

    :Endclass

    :endsection

    :section Utilities
    disperror←{326=⎕DR ⍵: ⋄ '***'≡3↑⍵:⎕←⍵}
    isWin←'Win'≡3↑1⊃#.⎕WG'APLVersion'
    fileSep←'/\'[1+isWin]
    isRelPath←{{~'/\'∊⍨(⎕IO+2×isWin∧':'∊⍵)⊃⍵}3↑⍵}
        MSRoot←{('.'=⊃⍵)∨isRelPath ⍵:'.',fileSep,⍵ ⋄ ⍵}{(1-⌊/'/\'⍳⍨⌽⍵)↓⍵}⎕WSID
      tonum←{⍺←0
          1∊⍺:tonumvec ⍵
          w←⍵ ⋄ ((w='-')/w)←'¯'
          ⊃⊃{~∧/⍺:⎕SIGNAL 11 ⋄ ⍵}/⎕VFI w}
    try←{0::'' ⋄⍎⍵}
    empty←{0∊⍴⍵}
    notEmpty←~∘empty
    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    isRef←{(0∊⍴⍴⍵)∧326=⎕DR ⍵}
    folderize←{{11 19 22::⍵,'/'↓⍨'/\'∊⍨¯1↑⍵ ⋄ ∊1 ⎕NPARTS⊃{⍺,(('/'=¯1↑⍺)<⍵=1)/'/'}/0 1 ⎕NINFO ⍵}∊⍕⍵} ⍝ append trailing file separator unless empty and left arg←1
    makeSitePath←{folderize ⍺{((isRelPath ⍵)/⍺),⍵},(2×'./'≡2↑⍵)↓⍵}
    subdirs←{⊃{(⍵=1)/⍺}/0 1(⎕NINFO⍠1)⍵,'/*'}
    Log←{⎕←⍵}

    ∇ {r}←AutoStatus setting
      ⍝ Set Dyalog/Windows AutoStatus setting
      :If r←isWin
          :Trap 0
              :If setting≠r←⎕SE.mb.tools.status_error.Checked
                  1 ⎕NQ ⎕SE.mb.tools.status_error'Select'
              :EndIf
          :EndTrap
      :EndIf
    ∇

    ∇ r←tonumvec v;to;minus;digits;c;mask
    ⍝ tonum vector version
    ⍝ allows for specific of ranges and comma or space delimited numbers
    ⍝ tonumvec '8080-8090'  or '5,7-9,11-15'
      r←⍬
      ⎕SIGNAL 11/⍨~∧/v∊⎕D,'., -¯'
      to←{⍺←⍵ ⋄ ⍺,⍺+(¯1*⍺>⍵)×⍳|⍺-⍵}
      v←('^\s*|\s*$'⎕R'')('\s+'⎕R' ')('\s*-\s*'⎕R'-')v
      minus←'-'=v
      digits←v∊⎕D,'.'
      ((minus>(minus∨{1↓⍵,0}digits)∧{¯1↓0,⍵}digits)/v)←⊂'¯'
      ((' '=v)/v)←','
      (('-'=v)/v)←⊂' to '
      :Trap 0
          :For c :In {⎕ML←3 ⋄ ⍵⊂⍨⍵≠','}∊v
              r,←⍎∊c
          :EndFor
      :Else
          ⎕SIGNAL 11
      :EndTrap
    ∇

    ∇ r←SubstPath r
      r←(#.Strings.subst∘('%ServerRoot%'(¯1↓MSRoot)))r
      r←(#.Strings.subst∘('%SiteRoot%'(¯1↓AppRoot)))r
    ∇

    ∇ r←isRunning
      :Trap r←0
          r←ms.TID∊⎕TNUMS
      :EndTrap
    ∇

    ∇ r←Oops;dmx;ends;xsi
    ⍝ debugging framework to bubble up to user's code when rendering fails
      r←'⎕SIGNAL 811'
      ends←{(,⍺)≡(-⍴,⍺)↑⍵}
      :If {0::0 ⋄ #.HtmlPage∊∊⎕CLASS ⍵}⊃⊃⎕RSI
          r←'⎕TRAP←(800 ''C'' ''→FAIL'')(811 ''E'' ''⎕SIGNAL 801'')(813 ''E'' ''⎕SIGNAL 803'')(812 ''S'')(85 ''N'')(0 ''S'')'
          ⎕←''
          ⎕←'*** MiServer Debug ***'
          ⎕←↑⎕DMX.DM
          ⎕←''
          ⎕←'      ⎕SIGNAL 800 ⍝ to ignore this error and carry on'
          ⎕←'      or Press Ctrl-Enter to invoke debugger'
      :Else
          :Select ⎕DMX.EN
          :Case 801
              xsi←⎕XSI
              :If '.HandleMSP'ends 2⊃xsi
                  r←'→FAIL'
              :ElseIf '.HandleMSP'ends 4⊃xsi
                  :If '.Render'ends 3⊃xsi
                      r←'⎕SIGNAL 813'
                  :EndIf
              :ElseIf '.HandleMSP'ends 3⊃xsi
                  :If '.Wrap'ends 2⊃xsi
                      r←'⎕SIGNAL 813'
                  :EndIf
              :Else
                  r←'⎕SIGNAL 811'
              :EndIf
          :Case 803
              r←'⎕SIGNAL 812'
              ⎕←'Press Ctrl-Enter to invoke debugger'
          :Else
              :Trap 0
                  dmx←⎕DMX
                  ⎕←'*** MiServer Debug ***'
                  ⎕←'' 'occurred at:',⍪dmx.(EM(2⊃DM))
                  ⎕←'' 'SI Stack is ',(⍕¯1+⍴⎕XSI),' levels deep'
                  ⎕←''
              :EndTrap
              ⎕←'      ⎕SIGNAL 800 ⍝ to ignore this error and carry on'
              ⎕←'      ⎕SIGNAL 801 ⍝ to cut back and debug'
              r←''
          :EndSelect
      :EndIf
    ∇
    :endsection

:EndNamespace

:Namespace Boot

    (⎕ML ⎕IO)←1

    :section Startup/Shutdown

    ∇ {msroot}Run root
      :If 0≠⎕NC'msroot' ⋄ MSRoot←msroot ⋄ :EndIf
      AppRoot←folderize root  ⍝ application (website) root
      Load AppRoot ⍝ load essential objects
      ms←Init ConfigureServer AppRoot ⍝ read configuration and create server instance
      Configure ms
      ms.Run
    ∇

    ∇ {msroot}RunWC2 root
      :If 0≠⎕NC'msroot' ⋄ MSRoot←msroot ⋄ :EndIf
      AppRoot←folderize root  ⍝ application (website) root
      Load AppRoot ⍝ load essential objects
      !!!ms←1 Init ConfigureServer AppRoot ⍝ read configuration and create server instance
      Configure ms
      {ms.Run}
    ∇

    ∇ Load AppRoot;filterOut;files;HTML;f;failed;dir;name;file;folder;callingEnv
      ⍝ Load required objects for MiServer

      HtmlRenderer←{0::⍵ ⋄ HtmlRenderer}0

      :If 0=#.⎕NC'Files' ⋄ ⎕SE.SALT.Load MSRoot,'Utils/Files -target=# -nolink' ⋄ :EndIf

      filterOut←{⍺←'' ⋄ ⍺{0∊⍴⍺:⍵ ⋄ ⍺{∊¨↓⍵⌿⍨~⍵[;2]∊eis ⍺}↑⎕NPARTS¨⍵}⊃#.Files.Dir ⍵,'/*.dyalog'}

      files←'Boot'filterOut MSRoot,'Core'
      files,←'Files'filterOut MSRoot,'Utils' ⍝ find utility libraries
      files,←filterOut MSRoot,'Extensions'

      failed←''
      :For f :In files
          {326=⎕DR ⍵: ⋄ '***'≡3↑⍵:failed,←⊂(('<.+>'⎕S{1↓¯1↓⍵.Match})⍵)}⎕SE.SALT.Load f,' -target=# -nolink' ⍝ do not reload already loaded spaces
      :EndFor

      :For file :In failed
          disperror ⎕SE.SALT.Load∊'"',file,'" -target=# -nolink'
      :EndFor

      HTML←'_JQ' '_JS'{⍵[⍋⍺⍳(↑⎕NPARTS¨⍵)[;2]]}filterOut MSRoot,'HTML' ⍝ prioritize loading of _JQ and _JS

      #.SupportedHtml5Elements.Build_html_namespace

      ⍝↓↓↓ Some controls may require controls in other folders.
      ⍝    So we attempt to load everything, and keep track of what failed
      ⍝    and then go back and try to load the failed controls again their
      failed←''
      :For f :In HTML
          (folder name)←2↑⎕NPARTS f
          disperror ⎕SE.SALT.Load f,' -target=# -nolink'
          :If #.Files.DirExists dir←folder,name,'/'
              dir∘{326=⎕DR ⍵: ⋄ '***'≡3↑⍵:failed,←⊂⍺(('<.+>'⎕S{1↓¯1↓⍵.Match})⍵)}¨⎕SE.SALT.Load dir,'* -nolink -target=#.',name
          :EndIf
      :EndFor

      :For (f file) :In failed
          disperror ⎕SE.SALT.Load∊'"',file,'" -nolink -target=#.',f
      :EndFor

      LoadFromFolder MSRoot,'Loadable'

      'Pages'#.⎕NS'' ⍝ Container Space for loaded classes
      #.Pages.(MiPage RESTfulPage)←#.(MiPage RESTfulPage)

      'CachedPages'#.⎕NS'' ⍝ Container for cached pages

      BuildEAWC ⍝ build the Easy As ⎕WC namespace

⍝ Now load any code from the MiSite

      :If ~0∊⍴AppRoot
          :Trap 22
              :For class :In filterOut AppRoot,'Code' ⍝ Classes in application folder
                  disperror ⎕SE.SALT.Load class,' -target=#'
              :EndFor

              :If #.Files.DirExists AppRoot,'/Code/Templates/'
                  disperror ⎕SE.SALT.Load AppRoot,'/Code/Templates/* -target=#.Pages'
              :EndIf
          :EndTrap
      :EndIf
    ∇

    ∇ Cleanup
      #.⎕EX¨classes
      #.⎕EX¨utils
      #.⎕EX'Pages'
      #.⎕EX'CachedPages'
      #.⎕EX¨'MiServer' 'HTTPRequest'
    ∇

    ∇ ms←{HtmlRenderer}Init Config;path;class;classes;e;res;mask
     ⍝ Create instances of MiServer, Session and Authentication Handlers

      HtmlRenderer←{0::⍵ ⋄ HtmlRenderer}0  ⍝ using HTMLRenderer?

      :If 0≠HtmlRenderer
          ms←⎕NS''
          ms.Config←Config
          ms.Log←{⎕←⍵}
      :Else
          ms←⎕NEW(#⍎Config.ClassName)Config
          path←MSRoot,'Extensions/'

          :If 0≠⍴Config.SessionHandler
              class←⎕SE.SALT.Load path,Config.SessionHandler
              ms.SessionHandler←⎕NEW class ms
          :EndIf

          :If 0≠⍴Config.Authentication
              class←⎕SE.SALT.Load path,Config.Authentication
              ms.Authentication←⎕NEW class ms
          :EndIf

          :If 0≠⍴Config.SupportedEncodings
              {}⎕SE.SALT.Load path,'ContentEncoder'
              :For e :In Config.SupportedEncodings
                  class←⎕SE.SALT.Load path,e
                  ms.Encoders,←⎕NEW class
              :EndFor
              :If ∨/mask←0≠1⊃¨res←ms.Encoders.Init
                  2 ms.Log'Content Encoding Initialization failed for:',∊' ',¨mask/ms.Encoders.Encoding
                  ms.Encoders←(~mask)/ms.Encoders
              :EndIf
          :EndIf
          Config.UseContentEncoding∧←0≠⍴ms.Encoders

          :If 0≠⍴Config.Logger
              class←⎕SE.SALT.Load path,Config.Logger
              ms.Logger←⎕NEW class ms
          :EndIf
      :EndIf
    ∇

    ∇ End;classes;z;m
      ⍝ Clean up the workspace

      :If 9=⎕NC'ms'
          :Trap 0
              ms.End
          :EndTrap
          {}try'⎕EX⍕⊃⊃⎕CLASS ms.SessionHandler'
          {}try'⎕EX⍕⊃⊃⎕CLASS ms.Authentication'
          {}try'⎕EX⍕⊃⊃⎕CLASS ms.Logger'
          {}try'⎕EX⍕¨∪∊ ⎕CLASS¨ms.Encoders'
          ⎕EX⍕⊃⊃⎕CLASS ms
          ⎕EX'ms'
      :EndIf

      :If 9=⎕NC'SQA'
          {}try'SQA.Close''.'''
      :EndIf

      :If 0≠⍴classes←↓#.⎕NL 9.4
      :AndIf 0≠⍴classes←(m←2=⊃∘⍴¨z←⎕CLASS¨#⍎¨classes)/classes
      :AndIf 0≠⎕NC'#.MiPage'
          classes←(#.MiPage≡¨2 1∘⊃¨m/z)/classes
          #.⎕EX↑⍕¨classes ⍝ Erase loaded classes
      :EndIf

      ⎕EX'#.MiPage'
      ⎕EX'AppRoot'
      {}try'#.DRC.Close ''.'''
      ⎕EX'#.DRC'
      #.Conga.UnloadSharedLib
      {}⎕WA
    ∇

    ∇ BuildEAWC;src;sources;fields;source;list;mask;refs;target
     ⍝ Build the Easy As ⎕WC namespace from core classes and its own source
     ⍝ Also build the #._ namespace with shortcuts
      sources←#._html #._SF #._JQ #._DC #._JS
⍝      fields←''
      '_'#.⎕NS''
      :For source :In sources
          list←source.⎕NL ¯9.4
          list←list/⍨'_'≠⊃¨list
          :If ∨/mask←0≠refs←source{6::0 ⋄ (⍕⍺){('.'∊1↓s↓r)<⍺≡(s←⍴⍺)↑r←⍕⍵}t←⍺⍎⍵:t ⋄ 0}¨list
              #._⍎∊(mask/list){'⋄',⍺,'←',⍕⍵}¨mask/refs
          :EndIf
      :EndFor
    ∇

    ∇ {root}LoadFromFolder path;type;name;nsName;parts;ns
    ⍝ Loads an APL "project" folder
      root←{6::⍵ ⋄ root}#
      :For name type :In ↓{⍵[⍒⍵[;2];]}⍉↑0 1 #.Files.Dir path,'/*'
          nsName←∊1↓parts←1 ⎕NPARTS name
          :If 1=type ⍝ directory?
              :Select ⊃root.⎕NC nsName
              :Case 9 ⋄ ns←⍕root⍎nsName
              :Case 0 ⋄ ns←nsName root.⎕NS''
              :Else ⋄ Log'"',name,'" is not a namespace'
              :EndSelect
              ⎕SE.SALT.Load name,'/* -target=',⍕ns
          :Else
              :If ~∨/∊(⎕NSI,¨'.')⍷⍨¨⊂'.',(2⊃1 ⎕NPARTS name),'.' ⍝ don't load it if we're being called from it
                  ⎕SE.SALT.Load name,' -target=',⍕root
              :EndIf
          :EndIf
      :EndFor
    ∇

    :endsection

    :section Configuration

    ∇ Configure ms
      ConfigureDatasources ms
      ConfigureVirtual ms
      ConfigureResources ms
      ConfigureContentTypes ms
      ms AddConfiguration'MappingHandlers'
    ∇

    ∇ ms AddConfiguration name;conf
      conf←ReadConfiguration name
      {ms.Config⍎name,'←⍵'}conf
    ∇

    ∇ r←ns Setting pars;name;num;default;mask
    ⍝ returns setting from a config style namespace or provides a default if it doesn't exist
    ⍝ pars - name [num] [default]
    ⍝ ns - namespace reference
    ⍝ name - name of the setting
    ⍝ num - 1 if setting is numeric scalar, (,1) if numeric vector is allowed, 0 otherwise
    ⍝ default - default value if not found
      pars←eis pars
      (name num)←2↑pars,(⍴pars)↓'' 0 ''
      :If 2<⍴pars ⋄ default←3⊃pars
      :Else ⋄ default←(1+num)⊃''⍬
      :EndIf
      r←(⍴ns)⍴⊂default
      :If ∨/mask←0≠⊃¨ns.⎕NC⊂name
          (mask/r)←(((⍴⍴num)∘tonum)⍣(⊃num))¨(mask/ns).⍎⊂name
      :EndIf
      :If 0=⍴⍴r ⋄ r←⊃r ⋄ :EndIf
    ∇

    ∇ config←{element}ReadConfiguration type;serverconfig;file;siteconfig;thing;ind;mask
    ⍝ Attempt to read configuration file
    ⍝ 1) from server root MSRoot
    ⍝ 2) from site root AppRoot
    ⍝ merging the two if they both exist - site settings overrule server settings
      config←''
      :If #.Files.Exists file←MSRoot,'Config/',type,'.xml'
          config←serverconfig←(#.XML.ToNS #.Files.ReadText file)⍎type
      :EndIf
      :If #.Files.Exists file←AppRoot,'Config/',type,'.xml'
          siteconfig←(#.XML.ToNS #.Files.ReadText file)⍎type
          :If 0∊⍴config
              config←siteconfig
          :ElseIf 0=⎕NC'element'
              {}{try'serverconfig.',⍵,'←siteconfig.',⍵}¨siteconfig.⎕NL ¯2
          :Else ⍝ element specifies the element(s) to search on
              :For thing :In siteconfig
                  :If 0≠thing.⎕NC element
                      :If ∨/mask←0≠∊serverconfig.⎕NC⊂element
                          :If (+/mask)<ind←((mask/serverconfig)⍎¨⊂element)⍳⊂thing⍎element
                              serverconfig,←thing
                          :Else
                              serverconfig[(mask/⍳⍴mask)[ind]]←thing
                          :EndIf
                      :Else
                          serverconfig,←thing
                      :EndIf
                  :EndIf
              :EndFor
              config←serverconfig
          :EndIf
      :EndIf
    ∇

    ∇ Config←ConfigureServer AppRoot;file
    ⍝ configure server level settings, setting defaults for needed ones that are not supplied
      Config←ReadConfiguration'Server'

      Config.AllowedHTTPMethods←{⍵⊆⍨~⍵∊' ,'}#.Strings.lc Config Setting'AllowedHTTPMethods' 0 'get,post'
      Config.AppRoot←AppRoot
      Config.Authentication←Config Setting'Authentication' 0 'SimpleAuth'
      Config.CertFile←Config Setting'CertFile' 0 ''
      Config.ClassName←Config Setting'ClassName' 0 'MiServer'
      Config.CloseOnCrash←Config Setting'CloseOnCrash' 1 0
      Config.Debug←Config Setting'Debug' 1 0
      Config.DecodeBuffers←Config Setting'DecodeBuffers' 1 1 ⍝ allow Conga to decode HTTP messages (1)
      Config.DefaultExtension←Config Setting'DefaultExtension' 0 '.mipage'
      Config.DefaultPage←Config Setting'DefaultPage' 0 'index.mipage'
      Config.DirectFileSize←{⍵[⍋⍵]}0⌈⌊2↑Config Setting'DirectFileSize'(,1)⍬
      Config.FIFOMode←Config Setting'FIFOMode' 1 0 ⍝ Conga FIFO mode default to off (0)
      Config.FormatHtml←Config Setting'FormatHtml' 1 0
      Config.Host←Config Setting'Host' 0 'localhost'
      Config.HTTPCacheTime←'m'#.Dates.ParseTime Config Setting'HTTPCacheTime' 0 '0' ⍝ default to off (0)
      Config.IdleTimeout←'s'#.Dates.ParseTime Config Setting'IdleTimeout' 0 '0' ⍝ default to none (0)
      Config.KeyFile←Config Setting'KeyFile' 0 ''
      Config.Lang←Config Setting'Lang' 0 'en'
      Config.LogMessageLevel←Config Setting'LogMessageLevel' 1 1 ⍝ default to error messages only
      Config.Logger←Config Setting'Logger' 0 ''
      Config.MSRoot←MSRoot
      Config.Name←Config Setting'Name' 0 'MiServer'
      Config.Port←Config Setting'Port' 1 8080
      Config.Ports←Config Setting'Ports'(,1)⍬
      Config.Production←Config Setting'Production' 1 0 ⍝ production mode?  (0/1 = development debug framework en/disabled)
      Config.RESTful←Config Setting'RESTful' 1 0 ⍝ RESTful web service?
      Config.RootCertDir←Config Setting'RootCertDir' 0 ''
      Config.Root←AppRoot
      Config.SSLFlags←Config Setting'SSLFlags' 1(32+64)  ⍝ Accept Without Validating, RequestClientCertificate
      Config.Secure←Config Setting'Secure' 1 0
      Config.Server←Config Setting'Server' 0 ''
      Config.SessionHandler←Config Setting'SessionHandler' 0 'SimpleSessions'
      Config.SessionTimeout←'m'#.Dates.ParseTime Config Setting'SessionTimeout' 0 '30m' ⍝ 30 minute timeout
      Config.SupportedEncodings←{(⊂'')~⍨1↓¨(⍵=⊃⍵)⊂⍵}',',Config Setting'SupportedEncodings' 0
      Config.TrapErrors←Config Setting'TrapErrors' 1 0
      Config.WaitTimeout←#.Dates.ParseTime Config Setting'WaitTimeout' 0 '5000ms' ⍝ 5000 msec (5 second timeout)
      Config.UseContentEncoding←Config Setting'UseContentEncoding' 1 0 ⍝ aka HTTP Compression default off (0)

      :If 0≠⎕NC'#.DrA' ⍝ Transfer DrA config options
          {}#.DrA.SetDefaults
          #.DrA.AppName←Config.Name
          #.DrA.MailMethod←Config Setting'MailMethod' 0 'NONE'
          #.DrA.MailRecipient←Config Setting'MailRecipient' 0
          #.DrA.Mode←Config.Debug
          #.DrA.NoUser←Config Setting'NoUser' 1 1 ⍝ run without user interaction
          #.DrA.Path←AppRoot ⍝ Where to put log files
          #.DrA.SMTP_Gateway←Config Setting'SMTP_Gateway' 0
          #.DrA.UseHTTP←Config Setting'UseHTTP' 1 0
      :EndIf
    ∇

    ∇ ConfigureDatasources ms;file;ds;name;tmp;orig;dyalog
      ⍝ load any datasource definitions
      :If ~0∊⍴ms.Datasources←'Name'ReadConfiguration'Datasources'
          :For ds :In ms.Datasources
              :For name :In ds.⎕NL ¯2
                  orig←tmp←ds.⍎name
                  tmp←SubstPath tmp
                  :If orig≢tmp
                      ⍎'ds.',name,'←tmp'
                  :EndIf
              :EndFor
          :EndFor

          :Trap 0
              :If 0=#.⎕NC'SQA'
                  dyalog←('/\'[1+'Win'≡3↑1⊃#.⎕WG'APLVersion']){⍵,(-⍺=¯1↑⍵)↓⍺}2 ⎕NQ'.' 'GetEnvironment' 'DYALOG'
                  'SQA'#.⎕CY dyalog,'ws/sqapl' ⍝ copy in SQA
              :EndIf
              :If 0≠1⊃#.SQA.Init'' ⍝ and initialize
                  1 ms.Log'SQA failed to initialize'
              :EndIf
          :EndTrap
      :EndIf
    ∇

    ∇ ConfigureVirtual ms;file;virtual;mask;inds;v
      ⍝ load virtual (alias) definitions if any
      ms.Config.Virtual←''
      :If ~0∊⍴virtual←'alias'ReadConfiguration'Virtual'
          :If 0∊mask←{⍵=⍳⍴⍵}inds←{⍵⍳⍵}virtual.alias ⍝ check for duplicate aliases, keep first
              1 ms.Log'Duplicate virtual aliases defined (first occurrence will be used): ',1↓∊',',¨(~mask)/virtual.alias
              virtual←mask/virtual
          :EndIf
          ⍝ substitute server and site root if found
          virtual.path←SubstPath¨virtual.path
          :For v :In virtual
              :If ~#.Files.DirExists v.path ⍝ check if mapped path exists
                  1 ms.Log'Virtual path not found: ',v.path
                  virtual~←v
              :Else
                  :If #.Files.DirExists AppRoot,v.alias ⍝ check if alias conflicts with local path
                      1 ms.Log'Virtual alias "',v.alias,'" overrides site path of same name.'
                  :EndIf
              :EndIf
          :EndFor
          ms.Config.Virtual←virtual
      :EndIf
    ∇

    ∇ ConfigureResources ms;file;mask;inds;names;uses;map;n;res;f;missing;order;resources;files;which
      ⍝ load resource definitions if any
      ms.Config.Resources←0 3⍴⊂''
      :If ~0∊⍴res←'name'ReadConfiguration'Resources'
          :If 0∊mask←{⍵=⍳⍴⍵}inds←{⍵⍳⍵}names←res.name ⍝ check for duplicate aliases, keep first
              1 ms.Log'Duplicate resources defined (first occurence will be used): ',1↓∊',',¨(~mask)/res.name
              res←mask/res
          :EndIf
          ⍝ build the dependency map
          uses←{(eis⍣(⊃0<⍴⍵))⍵}¨res Setting'uses'
          inds←res.name∘⍳¨uses
          n←⊃⍴res
          :If ∨/mask←∨/¨missing←n<inds
              1 ms.Log'Invalid resource dependency found for:',∊' ',¨mask/res.name
              inds←(~missing)/¨inds
          :EndIf
          order←OrderResources inds
          inds←(order∘⍳¨inds)[order]
          res←res[order]
          map←,(2⍴n)⍴(1+n)↑1 ⍝ identity matrix
          map[∊(n×¯1+⍳n)+¨inds]←1
          map←(2⍴n)⍴map
          map←↓{({⍺∨⍺∨.∧⍵}⍣≡)⍨⍵}map
          ⍝ ms.Config.Resources[;1] resource name, [;2] scripts used [;3] styles used
          f←{SubstPath¨{⍵~⊂''}∘∪¨⊃¨,/¨map/¨⊂eis¨⍵}
          ms.Config.Resources←res.name,(f res Setting'script'),[1.1]f res Setting'style'
          resources←∪⊃,/,0 1↓ms.Config.Resources
          files←ms.Config Virtual¨resources
          :If ∨/missing←~#.Files.Exists∘{⍵/⍨∧\⍵≠'?'}¨files
              which←(0 1↓ms.Config.Resources)∊¨⊂missing/resources
              mask←∨/∨/¨which
              1 ms.Log'Resource files not found:'
              1 ms.Log¨↓⍕(1,mask⌿which)/¨mask⌿ms.Config.Resources
              (mask⌿ms.Config.Resources)←(mask⌿ms.Config.Resources)~¨⊂missing/resources
          :EndIf
      :EndIf
    ∇

    ∇ file←Config Virtual page;mask;f;ind;t;path;root
      :Access public shared
    ⍝ checks for virtual directory
      root←(-'/\'∊⍨¯1↑root)↓root←Config.Root
      page←('/\'∊⍨1↑page)↓page
      file←root,'/',page
      :If 0<⍴Config.Virtual
          ind←Config.Virtual.alias⍳⊂t←{(¯1+⍵⍳'/')⍴⍵}page
          :If ind≤⍴Config.Virtual.alias
              path←ind⊃Config.Virtual.path
              file←#.Files.Normalize path,('/\'∊⍨¯1↑path)↓(⍴t)↓page
          :EndIf
      :EndIf
    ∇

    ∇ r←OrderResources inds;n;mask
      n←⍳⍴inds
      r←n/⍨mask←0∘∊∘⍴¨inds ⍝ roots with no dependencies
      inds/⍨←~mask
      n/⍨←~mask
      :While ~0∊⍴inds
          mask←∧/¨inds∊¨⊂r
          r,←mask/n
          inds/⍨←~mask
          n/⍨←~mask
      :EndWhile
    ∇

    ∇ ConfigureContentTypes ms;file;ct;inds;exts;types;mask;exp;n
      ⍝ load supported content types
      ms.Config.ContentTypes←0 2⍴⊂''
      :If ~0∊⍴ct←ReadConfiguration'ContentTypes'
          exts←#.Strings.lc¨ct Setting'ext'
          types←ct Setting'type'
          :If ~0∊⍴inds←{⍵/⍳⍴⍵}∊','∊¨exts
              mask←(⍴exts)⍴1
              exp←{⎕ML←3 ⋄ (⍵≠',')⊂⍵}¨exts[inds]
              mask[inds]←n←⍬∘⍴∘⍴¨exp
              (exts types)←mask∘/¨exts types
              exts[(n/inds)++\~∊n↑¨1]←⊃,/exp
          :EndIf
          ms.Config.ContentTypes←exts,[1.1]types
      :Else
          1 ms.Log'No content types defined?'
      :EndIf
    ∇

    :Class ConfigSpace
        :field public config

        ∇ make filename;ns;n
          :Access public
          :Implements constructor
          ns←#.XML.ToNS #.Files.ReadText filename
          'Config file needs a single root node'⎕SIGNAL 11/⍨1≠⍴n←ns.⎕NL ¯9
          config←ns⍎⊃n
        ∇

        ∇ r←Get args
          :Access public
          r←config Setting args
        ∇

        ∇ r←ns Setting pars;name;num;default;mask
    ⍝ returns setting from a config style namespace or provides a default if it doesn't exist
    ⍝ pars - name [num] [default]
    ⍝ ns - namespace reference
    ⍝ name - name of the setting
    ⍝ num - 1 if setting is numeric scalar, (,1) if numeric vector is allowed, 0 otherwise
    ⍝ default - default value if not found
          pars←eis pars
          (name num)←2↑pars,(⍴pars)↓'' 0 ''
          :If 2<⍴pars ⋄ default←3⊃pars
          :Else ⋄ default←(1+num)⊃''⍬
          :EndIf
          r←(⍴ns)⍴⊂default
          :If ∨/mask←0≠⊃¨ns.⎕NC⊂name
              (mask/r)←(((⍴⍴num)∘tonum)⍣(⊃num))¨(mask/ns).⍎⊂name
          :EndIf
          :If 0=⍴⍴r ⋄ r←⊃r ⋄ :EndIf
        ∇

        eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple

          tonum←{⍺←0
              1∊⍺:tonumvec ⍵
              w←⍵ ⋄ ((w='-')/w)←'¯'
              ⊃⊃{~∧/⍺:⎕SIGNAL 11 ⋄ ⍵}/⎕VFI w}

        ∇ r←tonumvec v;to;minus;digits;c;mask
    ⍝ tonum vector version
    ⍝ allows for specific of ranges and comma or space delimited numbers
    ⍝ tonumvec '8080-8090'  or '5,7-9,11-15'
          r←⍬
          ⎕SIGNAL 11/⍨~∧/v∊⎕D,'., -¯'
          to←{⍺←⍵ ⋄ ⍺,⍺+(¯1*⍺>⍵)×⍳|⍺-⍵}
          v←('^\s*|\s*$'⎕R'')('\s+'⎕R' ')('\s*-\s*'⎕R'-')v
          minus←'-'=v
          digits←v∊⎕D,'.'
          ((minus>(minus∨{1↓⍵,0}digits)∧{¯1↓0,⍵}digits)/v)←⊂'¯'
          ((' '=v)/v)←','
          (('-'=v)/v)←⊂' to '
          :Trap 0
              :For c :In {⎕ML←3 ⋄ ⍵⊂⍨⍵≠','}∊v
                  r,←⍎∊c
              :EndFor
          :Else
              ⎕SIGNAL 11
          :EndTrap
        ∇

    :Endclass

    :endsection

    :section Utilities
    disperror←{326=⎕DR ⍵: ⋄ '***'≡3↑⍵:⎕←⍵}
    isWin←'Win'≡3↑1⊃#.⎕WG'APLVersion'
    fileSep←'/\'[1+isWin]
    isRelPath←{{~'/\'∊⍨(⎕IO+2×isWin∧':'∊⍵)⊃⍵}3↑⍵}
        MSRoot←{('.'=⊃⍵)∨isRelPath ⍵:'.',fileSep,⍵ ⋄ ⍵}{(1-⌊/'/\'⍳⍨⌽⍵)↓⍵}⎕WSID
      tonum←{⍺←0
          1∊⍺:tonumvec ⍵
          w←⍵ ⋄ ((w='-')/w)←'¯'
          ⊃⊃{~∧/⍺:⎕SIGNAL 11 ⋄ ⍵}/⎕VFI w}
    try←{0::'' ⋄⍎⍵}
    empty←{0∊⍴⍵}
    notEmpty←~∘empty
    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    isRef←{(0∊⍴⍴⍵)∧326=⎕DR ⍵}
    folderize←{{11 19 22::⍵,'/'↓⍨'/\'∊⍨¯1↑⍵ ⋄ ∊1 ⎕NPARTS⊃{⍺,(('/'=¯1↑⍺)<⍵=1)/'/'}/0 1 ⎕NINFO ⍵}∊⍕⍵} ⍝ append trailing file separator unless empty and left arg←1
    makeSitePath←{folderize ⍺{((isRelPath ⍵)/⍺),⍵},(2×'./'≡2↑⍵)↓⍵}
    subdirs←{⊃{(⍵=1)/⍺}/0 1(⎕NINFO⍠1)⍵,'/*'}
    Log←{⎕←⍵}

    ∇ {r}←AutoStatus setting
      ⍝ Set Dyalog/Windows AutoStatus setting
      :If r←isWin
          :Trap 0
              :If setting≠r←⎕SE.mb.tools.status_error.Checked
                  1 ⎕NQ ⎕SE.mb.tools.status_error'Select'
              :EndIf
          :EndTrap
      :EndIf
    ∇

    ∇ r←tonumvec v;to;minus;digits;c;mask
    ⍝ tonum vector version
    ⍝ allows for specific of ranges and comma or space delimited numbers
    ⍝ tonumvec '8080-8090'  or '5,7-9,11-15'
      r←⍬
      ⎕SIGNAL 11/⍨~∧/v∊⎕D,'., -¯'
      to←{⍺←⍵ ⋄ ⍺,⍺+(¯1*⍺>⍵)×⍳|⍺-⍵}
      v←('^\s*|\s*$'⎕R'')('\s+'⎕R' ')('\s*-\s*'⎕R'-')v
      minus←'-'=v
      digits←v∊⎕D,'.'
      ((minus>(minus∨{1↓⍵,0}digits)∧{¯1↓0,⍵}digits)/v)←⊂'¯'
      ((' '=v)/v)←','
      (('-'=v)/v)←⊂' to '
      :Trap 0
          :For c :In {⎕ML←3 ⋄ ⍵⊂⍨⍵≠','}∊v
              r,←⍎∊c
          :EndFor
      :Else
          ⎕SIGNAL 11
      :EndTrap
    ∇

    ∇ r←SubstPath r
      r←(#.Strings.subst∘('%ServerRoot%'(¯1↓MSRoot)))r
      r←(#.Strings.subst∘('%SiteRoot%'(¯1↓AppRoot)))r
    ∇

    ∇ r←isRunning
      :Trap r←0
          r←ms.TID∊⎕TNUMS
      :EndTrap
    ∇

    ∇ r←Oops;dmx;ends;xsi
    ⍝ debugging framework to bubble up to user's code when rendering fails
      r←'⎕SIGNAL 811'
      ends←{(,⍺)≡(-⍴,⍺)↑⍵}
      :If {0::0 ⋄ #.HtmlPage∊∊⎕CLASS ⍵}⊃⊃⎕RSI
          r←'⎕TRAP←(800 ''C'' ''→FAIL'')(811 ''E'' ''⎕SIGNAL 801'')(813 ''E'' ''⎕SIGNAL 803'')(812 ''S'')(85 ''N'')(0 ''S'')'
          ⎕←''
          ⎕←'*** MiServer Debug ***'
          ⎕←↑⎕DMX.DM
          ⎕←''
          ⎕←'      ⎕SIGNAL 800 ⍝ to ignore this error and carry on'
          ⎕←'      or Press Ctrl-Enter to invoke debugger'
      :Else
          :Select ⎕DMX.EN
          :Case 801
              xsi←⎕XSI
              :If '.HandleMSP'ends 2⊃xsi
                  r←'→FAIL'
              :ElseIf '.HandleMSP'ends 4⊃xsi
                  :If '.Render'ends 3⊃xsi
                      r←'⎕SIGNAL 813'
                  :EndIf
              :ElseIf '.HandleMSP'ends 3⊃xsi
                  :If '.Wrap'ends 2⊃xsi
                      r←'⎕SIGNAL 813'
                  :EndIf
              :Else
                  r←'⎕SIGNAL 811'
              :EndIf
          :Case 803
              r←'⎕SIGNAL 812'
              ⎕←'Press Ctrl-Enter to invoke debugger'
          :Else
              :Trap 0
                  dmx←⎕DMX
                  ⎕←'*** MiServer Debug ***'
                  ⎕←'' 'occurred at:',⍪dmx.(EM(2⊃DM))
                  ⎕←'' 'SI Stack is ',(⍕¯1+⍴⎕XSI),' levels deep'
                  ⎕←''
              :EndTrap
              ⎕←'      ⎕SIGNAL 800 ⍝ to ignore this error and carry on'
              ⎕←'      ⎕SIGNAL 801 ⍝ to cut back and debug'
              r←''
          :EndSelect
      :EndIf
    ∇
    :endsection

:EndNamespace


bug fix

bug fix

:Class ejCheckboxAdvanced : MiPageSample
⍝ Control:: _SF.ejCheckbox
⍝ Description:: multiple checkboxes with callback that reads the current state and modify others

⍝ This example shows how to use the SyncFusion CheckBox widget

⍝ Three series of checkboxes are displayed in a table. They can all be ticked.
⍝ The first one of each line is tristate, that is it can be in one of three states.
⍝ It is ticked if all the boxes to its right are ticked, it is unticked if all the ones to its
⍝ right are not ticked and it is shown as a square if there is a mixture of ticked and unticked
⍝ to its right.
⍝ Furthermore, if it is clicked it turns ON or OFF the boxes to its right to match its own state.
⍝ Finally, a single box on a fourth row is used to turn ON/OFF all the boxes on top of it.
⍝ It is also tristate and will show if all the boxes above are all ON or all OFF or mixed.

⍝ To specify that a check box is checked we set the option 'checked' to TRUE using a 1.
⍝ To specify that a check box can be in 3 states we set the 'enableTriState' option to TRUE.
⍝ When a box is tristate the option 'checkState' is used to specify which of the 3 states it is in:
⍝ Check, Uncheck or Indeterminate. Note that if the 'checked' option of a tristate box is also
⍝ set to TRUE it will display as ticked even if the option 'checkState' is set to Uncheck.

    ∇ Compose;cb1;i;tbl;grp;cols;head;body;foot;last;allids;td;tr;c4;c0;r4
      :Access Public
    ⍝ We create a series of SyncFusion check boxes in a table

      Add _.p'Click boxes and see that the left and bottom boxes react automatically.'

      tbl←Add _.table

    ⍝ Column-wise formatting
    ⍝ Columns that did not receive formatting remain with the default
      grp←tbl.Add _.colgroup
      cols←grp.Add¨5/_.col ⍝ we don't actually need a col for each column
      cols.Set⊂'width="20"'
      cols[1].Set'style=background-color:lightgrey' 'align=center'

    ⍝ Header
      head←tbl.Add _.thead
      (head.Add _.tr).Add¨_.th,¨'S/U' '' 'C1' 'C2' 'C3' 'C4'

    ⍝ Body
      allids←,'C',¨⍕¨10 20 30∘.+0,⍳4
      body←tbl.Add _.tbody
      AllCB←⍬                           ⍝ All but the last check box will be here
      :For i :In 1 6 11
          tr←body.Add _.tr              ⍝ add a row to the table
          td←tr.Add _.td                ⍝ add a column to the row
          c0←(i⊃allids)td.Add _.ejCheckBox 0 ⍝ to which we add a check box
          'enableTriState'c0.Set _false ⍝ which is tristate
        ⍝ If TriState is disabled its checkState value is ignored
          'checkState'c0.Set'indeterminate'
          'showRoundedCorner'c0.Set _true
          tr.Add _.td                   ⍝ add an empty column for spacing
          r4←tr.Add¨4/_.td              ⍝ add 4 more columns for the 2-state boxes
          c4←allids[i+⍳4]r4.Add⊂_.ejCheckBox 0
          c4.Set⊂'size' 'medium'        ⍝ make them a bit bigger
          AllCB,←c0,c4
      :EndFor

    ⍝ We tell the server to call program 'chkBoxes' when any box changes
      AllCB.On⊂'change' 'chkBoxes'

    ⍝ Footer: add a box for all of them
      foot←'all'tbl.Add _.tfoot
      last←(foot.Add _.tr).Add¨2/_.td              ⍝ we only add 2, uneven rows allowed
      LastCB←1⌷last.Add(_.ejCheckBox 0)'Set all'   ⍝ we add a check box and some text
      LastCB.id←'forall'
      'enableTriState'LastCB.Set _false
      'checkState'LastCB.Set'indeterminate'
      'showRoundedCorner'LastCB.Set _true
    ⍝ We also tell the server to call program 'chkBoxes' when it changes and to record
    ⍝ 'state' which is to 'true' if it (the "model") is checked (FALSE otherwise).
      LastCB.On'change' 'chkBoxes'('state' 'model' 'checked')

    ⍝ Caption always goes on top, even if added later
      tbl.Add _.caption'Select any box'

    ⍝ Remember the ids and their values here
      Values←{0}¨Allids←'#',¨allids
    ∇

    UCI←  'uncheck'  'check'  'indeterminate'

    ∇ js←chkBoxes;i;mb;b;row;new;c0;uci;previous
      :Access public
      js←''
      :If _selector≡'#forall'                                    ⍝ turn everything ON or OFF for everything?
          previous←Values
          Values[]←new←'false'≢Get'state'                        ⍝ keep their setting on the server
          js←⊃,/(⊂'checked')((Values≠previous)/AllCB).Update new ⍝ reset those that have changed
      :Else         ⍝ must be a box above
          i←Allids⍳⊂_selector                                    ⍝ which one is it?
          :If ∨/b←i=1 6 11                                       ⍝ is it one of the left column?
              Values[row←i+0,⍳4]←new←1≠Values[i]                 ⍝ yes, what's its value?
              js←'enableTriState'(AllCB[i]).Update _false        ⍝ turn OFF triState for that box
              js,←⊃,/(⊂'checked')(AllCB[row]).Update new          ⍝ and set its checked state

          :Else     ⍝ it's one of the non tri-state boxes
              Values[i]←~Values[i]
              mb←0 1↓3 5⍴Values
             ⍝ Is the line itself all ON or OFF?
              b←<\6 11 16>i
              :If Values[i←b/1 6 11]≠new←0 1 ¯1[uci←0 4⍳+/b⌿mb]  ⍝ has it changed?
                  c0←i⊃AllCB
                  js←'enableTriState'c0.Update uci⊃_false _false _true
                  :If new≠¯1
                      js,←'checked'c0.Update new
                  :EndIf
                  Values[i]←new
              :EndIf
          :EndIf
      :EndIf

     ⍝ Finally, set the ALL button properly
      uci←0 12⍳+/,0 1↓3 5⍴Values                                 ⍝ what about the ALL button?
      js,←'enableTriState'LastCB.Update _false _false _true[uci] ⍝ prevent user from setting indeterminate
      :If uci<3
          js,←'checked'LastCB.Update uci-1
      :EndIf
    ∇

:EndClass

:Class ejCheckboxAdvanced : MiPageSample
⍝ Control:: _SF.ejCheckbox
⍝ Description:: multiple checkboxes with callback that reads the current state and modify others

⍝ This example shows how to use the SyncFusion CheckBox widget

⍝ Three series of checkboxes are displayed in a table. They can all be ticked.
⍝ The first one of each line is tristate, that is it can be in one of three states.
⍝ It is ticked if all the boxes to its right are ticked, it is unticked if all the ones to its
⍝ right are not ticked and it is shown as a square if there is a mixture of ticked and unticked
⍝ to its right.
⍝ Furthermore, if it is clicked it turns ON or OFF the boxes to its right to match its own state.
⍝ Finally, a single box on a fourth row is used to turn ON/OFF all the boxes on top of it.
⍝ It is also tristate and will show if all the boxes above are all ON or all OFF or mixed.

⍝ To specify that a check box is checked we set the option 'checked' to TRUE using a 1.
⍝ To specify that a check box can be in 3 states we set the 'enableTriState' option to TRUE.
⍝ When a box is tristate the option 'checkState' is used to specify which of the 3 states it is in:
⍝ Check, Uncheck or Indeterminate. Note that if the 'checked' option of a tristate box is also
⍝ set to TRUE it will display as ticked even if the option 'checkState' is set to Uncheck.

    ∇ Compose;cb1;i;tbl;grp;cols;head;body;foot;last;allids;td;tr;c4;c0;r4
      :Access Public
    ⍝ We create a series of SyncFusion check boxes in a table

      Add _.p'Click boxes and see that the left and bottom boxes react automatically.'

      tbl←Add _.table

    ⍝ Column-wise formatting
    ⍝ Columns that did not receive formatting remain with the default
      grp←tbl.Add _.colgroup
      cols←grp.Add¨5/_.col ⍝ we don't actually need a col for each column
      cols.Set⊂'width="20"'
      cols[1].Set'style=background-color:lightgrey' 'align=center'

    ⍝ Header
      head←tbl.Add _.thead
      (head.Add _.tr).Add¨_.th,¨'S/U' '' 'C1' 'C2' 'C3' 'C4'

    ⍝ Body
      allids←,'C',¨⍕¨10 20 30∘.+0,⍳4
      body←tbl.Add _.tbody
      AllCB←⍬                           ⍝ All but the last check box will be here
      :For i :In 1 6 11
          tr←body.Add _.tr              ⍝ add a row to the table
          td←tr.Add _.td                ⍝ add a column to the row
          c0←(i⊃allids)td.Add _.ejCheckBox 0 ⍝ to which we add a check box
          'enableTriState'c0.Set _false ⍝ which is tristate
        ⍝ If TriState is disabled its checkState value is ignored
          'checkState'c0.Set'indeterminate'
          'showRoundedCorner'c0.Set _true
          tr.Add _.td                   ⍝ add an empty column for spacing
          r4←tr.Add¨4/_.td              ⍝ add 4 more columns for the 2-state boxes
          c4←allids[i+⍳4]r4.Add⊂_.ejCheckBox 0
          c4.Set⊂'size' 'medium'        ⍝ make them a bit bigger
          AllCB,←c0,c4
      :EndFor

    ⍝ We tell the server to call program 'chkBoxes' when any box changes
      AllCB.On⊂'change' 'chkBoxes'

    ⍝ Footer: add a box for all of them
      foot←'all'tbl.Add _.tfoot
      last←(foot.Add _.tr).Add¨2/_.td              ⍝ we only add 2, uneven rows allowed
      LastCB←1⌷last.Add(_.ejCheckBox 0)'Set all'   ⍝ we add a check box and some text
      LastCB.id←'forall'
      'enableTriState'LastCB.Set _false
      'checkState'LastCB.Set'indeterminate'
      'showRoundedCorner'LastCB.Set _true
    ⍝ We also tell the server to call program 'chkBoxes' when it changes and to record
    ⍝ 'state' which is to 'true' if it (the "model") is checked (FALSE otherwise).
      LastCB.On'change' 'chkBoxes'('state' 'model' 'checked')

    ⍝ Caption always goes on top, even if added later
      tbl.Add _.caption'Select any box'

    ⍝ Remember the ids and their values here
      Values←{0}¨Allids←'#',¨allids
    ∇

    UCI←  'uncheck'  'check'  'indeterminate'

    ∇ js←chkBoxes;i;mb;b;row;new;c0;uci;previous
      :Access public
      js←''
      :If _selector≡'#forall'                                    ⍝ turn everything ON or OFF for everything?
          previous←Values
          Values[]←new←'false'≢Get'state'                        ⍝ keep their setting on the server
          js←⊃,/(⊂'checked')((Values≠previous)/AllCB).Update new ⍝ reset those that have changed
      :Else         ⍝ must be a box above
          i←Allids⍳⊂_selector                                    ⍝ which one is it?
          :If ∨/b←i=1 6 11                                       ⍝ is it one of the left column?
              Values[row←i+0,⍳4]←new←1≠Values[i]                 ⍝ yes, what's its value?
              js←'enableTriState'(AllCB[i]).Update _false        ⍝ turn OFF triState for that box
              js←⊃,/(⊂'checked')(AllCB[row]).Update new          ⍝ and set its checked state

          :Else     ⍝ it's one of the non tri-state boxes
              Values[i]←~Values[i]
              mb←0 1↓3 5⍴Values
             ⍝ Is the line itself all ON or OFF?
              b←<\6 11 16>i
              :If Values[i←b/1 6 11]≠new←0 1 ¯1[uci←0 4⍳+/b⌿mb]  ⍝ has it changed?
                  c0←i⊃AllCB
                  js←'enableTriState'c0.Update uci⊃_false _false _true
                  :If new≠¯1
                      js,←'checked'c0.Update new
                  :EndIf
                  Values[i]←new
              :EndIf
          :EndIf
      :EndIf

     ⍝ Finally, set the ALL button properly
      uci←0 12⍳+/,0 1↓3 5⍴Values                                 ⍝ what about the ALL button?
      js,←'enableTriState'LastCB.Update _false _false _true[uci] ⍝ prevent user from setting indeterminate
      :If uci<3
          js,←'checked'LastCB.Update uci-1
      :EndIf
    ∇

:EndClass


ejDateTimePickerAdvanced QA speed-up by using the new SendKeys shift-states.

 msg←Test dummy;ts;in;out

 K←Selenium.Keys

 ts←∊⍕¨100+1↓¯2↓⎕TS
 ts[1 4 7 10]←'// :'
 (in out)←(⍕¨1 ¯1+⊃⎕TS),¨⊂ts

 'in'∘SendKeys¨K.End('Shift'K.Home)(in,K.Tab,out,K.Enter)

 :If 0∊⍴msg←'output'WaitFor'Going back in time?' 'Input validation failed'

     'out'∘SendKeys¨K.Home('Control' 'Shift'K.Right)('Shift'K.Left)(⍕2+⊃⎕TS)K.Enter

     msg←'output'WaitFor'The difference in days is: 3'
 :EndIf

 msg←Test dummy;ts;in;out
 dtp←Selenium.Find'dtp'

 ts←∊⍕¨100+1↓¯2↓⎕TS
 ts[1 4 7 10]←'// :'
 (in out)←(⍕¨1 ¯1+⊃⎕TS),¨⊂ts

 'in'SendKeys Selenium.Keys.(End,20/Backspace),in
 'out'SendKeys Selenium.Keys.(End,20/Backspace),out

 :If 0∊⍴msg←'output'WaitFor'Going back in time?' 'Input validation failed'
     'out'∘SendKeys¨Selenium.Keys.(Home,4/Delete),(⍕2+⊃⎕TS),Selenium.Keys.Tab
     msg←'output'WaitFor'The difference in days is: 3'
 :EndIf


Install latest HttpCommand

Install latest HttpCommand


fixed #28, game of life demo fails

fixed #28, game of life demo fails

#!/usr/bin/env apl

⍝ Conway's game of life

⍝ This example was inspired by the impressive demo at
⍝ http://www.youtube.com/watch?v=a9xAKttWgP4

⍝ Create a matrix:
⍝     0 1 1
⍝     1 1 0
⍝     0 1 0
creature ← (3 3 ⍴ ⍳ 9) ∊ 1 2 3 4 7   ⍝ Original creature from demo
creature ← (3 3 ⍴ ⍳ 9) ∊ 1 3 6 7 8   ⍝ Glider

⍝ Place the creature on a larger board, near the centre
board ← ¯1 ⊖ ¯2 ⌽ 5 7 ↑ creature

⍝ A function to move from one generation to the next
life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +⌿ 1 0 ¯1 ∘.⊖ 1 0 ¯1 ⌽¨ ⊂⍵}

⍝ Compute n-th generation and format it as a
⍝ character matrix
gen ← {' #'[(life ⍣ ⍵) board]}

⍝ Show first three generations
⎕ ← (gen 1) (gen 2) (gen 3)

#!/usr/bin/env apl

⍝ Conway's game of life

⍝ This example was inspired by the impressive demo at
⍝ http://www.youtube.com/watch?v=a9xAKttWgP4

⍝ Create a matrix:
⍝     0 1 1
⍝     1 1 0
⍝     0 1 0
creature ← (3 3 ⍴ ⍳ 9) ∊ 1 2 3 4 7   ⍝ Original creature from demo
creature ← (3 3 ⍴ ⍳ 9) ∊ 1 3 6 7 8   ⍝ Glider

⍝ Place the creature on a larger board, near the centre
board ← ¯1 ⊖ ¯2 ⌽ 5 7 ↑ creature

⍝ A function to move from one generation to the next
life ← {∨/ 1 ⍵ ∧ 3 4 = ⊂+/ +⌿ 1 0 ¯1 ∘.⊖ 1 0 ¯1 ⌽¨ ⊂⍵}

⍝ Compute n-th generation and format it as a
⍝ character matrix
gen ← {' #'[(life ⍣ ⍵) board]}

⍝ Show first three generations
⎕ ← (gen 1) (gen 2) (gen 3)


File info includes modes, uid and gid.

File info includes modes, uid and gid.

Added Unix-to-APL time conversion.