-*- mode: org -*-
#+TITLE:       spine (doc_reform) default paths
#+DESCRIPTION: documents - structuring, publishing in multiple formats & search
#+FILETAGS:    :spine:paths:
#+AUTHOR:      Ralph Amissah
#+EMAIL:       [[mailto:ralph.amissah@gmail.com][ralph.amissah@gmail.com]]
#+COPYRIGHT:   Copyright (C) 2015 - 2020 Ralph Amissah
#+LANGUAGE:    en
#+STARTUP:     content hideblocks hidestars noindent entitiespretty
#+OPTIONS:     H:3 num:nil toc:t \n:nil @:t ::t |:t ^:nil _:nil -:t f:t *:t <:t
#+PROPERTY:    header-args  :exports code
#+PROPERTY:    header-args+ :noweb yes
#+PROPERTY:    header-args+ :eval no
#+PROPERTY:    header-args+ :results no
#+PROPERTY:    header-args+ :cache no
#+PROPERTY:    header-args+ :padline no

[[./spine.org][spine]]  [[./][org/]]
* 0. source paths
** _module template_                                      :module:paths_source:

#+BEGIN_SRC d :tangle "../src/doc_reform/io_in/paths_source.d"
/++
  read configuration files<BR>
  - read config files<BR>
  meta_config_files.d
+/
module doc_reform.io_in.paths_source;
import
  std.array,
  std.file,
  std.path,
  std.regex,
  std.stdio,
  std.conv : to;
import
  doc_reform.meta.defaults,
  doc_reform.meta.rgx;
<<template_paths_src_0>>
<<template_paths_src_1>>
<<template_paths_src_2>>
<<template_paths_src_3>>
<<template_paths_pod>>
<<template_paths_pod_shallow>>
<<template_paths_pods>>
#+END_SRC

** _manifest_                                                        :manifest:

#+NAME: template_paths_src_0
#+BEGIN_SRC d
template PodManifest() {
  mixin spineRgxIn;
  static auto rgx = RgxI();
  @safe auto PodManifest(O)(
    O       _opt_action,
    string  _pth=""
  ) {
    struct ManifestFile_ {
      string pod_manifest_filename() {
        return "pod.manifest";
      }
      @safe string pod_manifest_path() {
        string _manifest_path;
        if ((isValidPath(_pth) && exists(_pth) != 0 && _pth.isDir)
        && (exists(_pth.chainPath(pod_manifest_filename).array) != 0
        && (_pth.chainPath(pod_manifest_filename).array).isFile)) {
          _manifest_path = _pth;
        } else if (_pth.match(rgx.src_pth_contents)
        && exists(_pth) != 0 && _pth.isFile) {
          _manifest_path = _pth.dirName;
        } else if (_pth.match(rgx.src_pth_pod_sst_or_ssm)
        && exists(_pth) != 0 && (_pth.isFile)) {
          if (auto m = _pth.match(rgx.src_pth_pod_sst_or_ssm)) {
            _manifest_path = m.captures["podpath"];
          }
        } else  {
          if (_opt_action.verbose || _opt_action.very_verbose || _opt_action.debug_do) {
            writeln("WARNING, src is not a pod, issue with manifest_path: ", _pth); // remove? unless can distinguish pod
          }
          _manifest_path = "";
        }
        return _manifest_path;
      }
      @safe string pods_collection_root_path() {
        return (pod_manifest_path.length > 0) ? ((chainPath(pod_manifest_path, "..")).asNormalizedPath).array.to!string : "";
      }
      @safe string pod_manifest_file_with_path() {
        string _k;
        if  (exists(pod_manifest_path.chainPath(pod_manifest_filename).array)!=0) {
          _k = pod_manifest_path.chainPath(pod_manifest_filename).array;
        } else if (exists(pod_manifest_path)!=0) {
          _k = pod_manifest_path;
        }
        if (exists(_k)==0) {
          writeln("ERROR >> Processing Skipped! Manifest not found: ", _k);
          _k = null;
        }
        return _k;
      }
    }
    return ManifestFile_();
  }
}
#+END_SRC

** _path matters (pod, manifest and source files)_                   :manifest:

tree pod
pod
└─ [sisudoc filename]
    ├── conf
    │   └── sisu_document_make
    ├── media
    │   ├── audio
    │   ├── image
    │   ├── text
    │   │   └── en
    │   │      ...
    │   │       ├── [conf]
    │   │       └── [image]
    │   └── video
    └── pod.manifest

#+NAME: template_paths_src_1
#+BEGIN_SRC d
template PathMatters() {
  mixin spineRgxIn;
  mixin InternalMarkup;
  static auto rgx  = RgxI();
  static auto mkup = InlineMarkup();
  @safe auto PathMatters(O,E)(
    O        _opt_action,
    E        _env,
    string   _pth,
    string   _fns              = "",
    char[][] _manifest_fn_list = [[]],
  ) {
    @safe auto _manifested = PodManifest!()(_opt_action, _pth);
    struct ManifestMatters_ {
      auto env() {
        auto _env = _env;
        struct Env_ {
          auto pwd() {
            return _env["pwd"];
          }
          auto home() {
            return _env["home"];
          }
        }
        return Env_();
      }
      @safe auto opt() {
        struct Opt_ {
          auto action() {
            return _opt_action;
          }
        }
        return Opt_();
      }
      @safe bool src_is_pod() {
        return (_manifested.pod_manifest_path.length > 0) ? true : false;
      }
      @safe auto pod() {
        struct Pod_ {
          @safe bool src_is_pod() {
            return (_manifested.pod_manifest_path.length > 0) ? true : false;
          }
          @safe string collection_root() {
            return _manifested.pods_collection_root_path;
          }
          @safe string manifest_filename() {
            return _manifested.pod_manifest_filename;
          }
          @safe string manifest_path() {
            return _manifested.pod_manifest_path;
          }
          @safe string pod_name_with_path() {
            return _manifested.pod_manifest_path.baseName;
          }
          @safe string manifest_file_with_path() {
            return _manifested.pod_manifest_file_with_path;
          }
          @safe string[] config_dr_document_make_dirs() {
            string[] _config_dirs;
            return _config_dirs;
          }
          @safe string[] config_local_site_dirs() {
            string[] _config_dirs;
            return _config_dirs;
          }
          @safe string[] image_dirs() {
            string[] _image_dirs;
            return _image_dirs;
          }
          @safe auto manifest_list_of_filenames() {
            return _manifest_fn_list;
          }
          @safe string[] manifest_list_of_languages() {
            string[] _lngs;
            foreach (filename_; manifest_list_of_filenames) {
              string _k = "en";
              if (auto m = (filename_).match(rgx.language_code_and_filename)) {
                _k = m.captures[1].to!string;
              }
              _lngs ~= _k; // all the languages from the manifest list of filenames with paths
            }
            return _lngs;
          }
        }
        return Pod_();
      }
      @safe auto src() {
        string _fns = _fns; // required here by dmd & not by ldc (for D:2078)
        auto _opt_action = _opt_action;
        auto _env = _env;
        struct SRC_ {
          @safe bool is_pod() {
            return (_manifested.pod_manifest_path.length > 0) ? true : false;
          }
          @safe string path_and_fn() {
            return _fns;
          }
          @safe string pod_name_with_path() {
            return (is_pod) ? _manifested.pod_manifest_path : "";
          }
          @safe string pods_collection_root_path() {
            return (is_pod) ? _manifested.pods_collection_root_path : "";
          }
          @safe string pod_name() {
            return pod_name_with_path.baseName;
          }
          @safe string filename() {
            return path_and_fn.baseName;
          }
          @safe string filename_base() {
            return filename.stripExtension;
          }
          @safe string filename_extension() {
            return filename.match(rgx.src_pth_sst_or_ssm).captures["extension"];
          }
          @safe string lng() {
            string _k;
            if (auto m = path_and_fn.match(rgx.language_code_and_filename)) {
              _k = m.captures[1];
            } else {_k = "en"; }
            return _k;
          }
          @safe string doc_uid() {
            string _uid;
            if (is_pod && !(pod_name_with_path.empty)) {
              if (pod_name_with_path.baseName == filename_base) {
                _uid = filename_base ~ "." ~ filename_extension ~ mkup.uid_sep ~ lng;
              } else {
                _uid = pod_name_with_path.baseName ~ mkup.uid_sep ~ filename_base ~ "." ~ filename_extension ~ mkup.uid_sep ~ lng;
              }
            } else {
              _uid = mkup.uid_sep ~ filename_base ~ "." ~ filename_extension ~ mkup.uid_sep ~ lng;
            }
            return _uid;
          }
          @safe string doc_uid_out() {
            string _uid;
            if (is_pod && !(pod_name_with_path.empty)) {
              if (pod_name_with_path.baseName == filename_base) {
                _uid = filename_base ~ "." ~ lng;
              } else {
                _uid = pod_name_with_path.baseName ~ mkup.uid_sep ~ filename_base ~ "." ~ lng;
              }
            } else {
              _uid = "_" ~ filename_base ~ "." ~ lng;
            }
            return _uid;
          }
          @safe string docname_composite_unique_per_src_doc() {
            string _fn;
            if (pod_name_with_path.baseName == filename_base) {
              _fn = filename_base ~ mkup.uid_sep ~ filename_extension ~ mkup.uid_sep ~ lng;
            } else if (!(pod_name_with_path.empty)) {
              _fn = pod_name_with_path.baseName ~ mkup.uid_sep ~ filename_base ~ mkup.uid_sep ~ filename_extension ~ mkup.uid_sep ~ lng;
            } else {
              _fn = "_" ~ mkup.uid_sep ~ filename_base ~ mkup.uid_sep ~ filename_extension ~ mkup.uid_sep ~ lng;
            }
            return _fn;
          }
          @safe string docname_composite_unique_per_src_pod() {
          /+
            z pod name if any + src filename (without lng code)
             filename ~ mkup.uid_sep ~ lng
             * unique per src pod
             used by
             - pod (multilingual collection)
             - sqlite discrete index (multilingual collection)
          +/
            string _fn;
            if (pod_name_with_path.baseName == filename_base) {
              _fn = filename_base ~ mkup.uid_sep ~ filename_extension;
            } else if (!(pod_name_with_path.empty)) {
              _fn = pod_name_with_path.baseName ~ mkup.uid_sep ~ filename_base ~ mkup.uid_sep ~ filename_extension;
            } else {
              _fn = "_" ~ mkup.uid_sep ~ filename_base ~ mkup.uid_sep ~ filename_extension;
            }
            return _fn;
          }
          @safe string language() {
            return lng();
          }
          @safe string file_with_absolute_path() {
            return _env["pwd"].chainPath(path_and_fn).array;
          }
          @safe string absolute_path_to_src() {
            return (_env["pwd"].chainPath(path_and_fn)).dirName.array;
          }
          @safe string path_to_doc_root_path_to_lang_and_filename() {
            return _env["pwd"].chainPath(path_and_fn).array;
          }
          @safe string base_dir() {
            string _dir;
            if (
              auto m = (absolute_path_to_src)
              .match(regex(r"[/](?P<dir>(?:[a-zA-Z0-9._-]+))/pod/" ~ filename.stripExtension))
            ) {
              _dir = ((path_and_fn.chainPath("../../")).asNormalizedPath).array;
              assert(_dir == m.captures["dir"]);
            } else {
              _dir = ((path_and_fn.chainPath("../../../")).asNormalizedPath).array;
              assert(_dir == absolute_path_to_src
                .match(rgx.src_base_parent_dir_name).captures["dir"]);
            }
            if (_opt_action.debug_do) {
              writeln("--> (base_dir)  ", _dir);
            }
            return _dir;
          }
          @safe string base_parent_dir_path() {
            string _dir;
            if (
              auto m = (absolute_path_to_src)
              .match(regex(r"[/](?P<dir>(?:[a-zA-Z0-9._-]+))/pod/" ~ filename.stripExtension))
            ) {
              _dir = ((path_and_fn.chainPath("../../")).asNormalizedPath).array;
            } else {
              _dir = ((path_and_fn.chainPath("../../../")).asNormalizedPath).array;
            }
            return _dir;
          }
          @safe string base_dir_path() {
            string _dir;
            if (
              auto m = (absolute_path_to_src)
              .match(rgx.src_formalised_file_path_parts)
            ) {
              _dir = ((m.captures["pth"]).asNormalizedPath).array;
            } else if (
             auto m = (absolute_path_to_src)
             .match(regex(r"[/](?P<dir>(?:[a-zA-Z0-9._-]+))/pod/" ~ filename.stripExtension))
            ) {
              _dir = ((path_and_fn.chainPath("../")).asNormalizedPath).array;
            } else {
              _dir = ((path_and_fn.chainPath("../../")).asNormalizedPath).array;
            }
            if (_opt_action.debug_do) {
              writeln("--> (base_dir_path) ", _dir);
            }
            return _dir;
          }
          @safe string media_dir_path() {
            string _dir = ((base_dir_path.chainPath("media")).asNormalizedPath).array;
            return _dir;
          }
          @safe string image_dir_path() {
            string _paths;
            string[] _possible_img_pths = [ "./image", "../image", "../../image" ];
            string _img_pth_found = "";
            if (is_pod) {
              _img_pth_found = ((file_with_absolute_path.dirName ~ "/../../image").asNormalizedPath).array;
            } else {
              string _img_pth(string _possible_img_pth) {
                return ((file_with_absolute_path.dirName ~ "/" ~ _possible_img_pth).asNormalizedPath).array;
              }
              foreach(_possible_img_pth; _possible_img_pths) {
                if (exists(_img_pth(_possible_img_pth))) {
                  _img_pth_found = _img_pth(_possible_img_pth);
                  break;
                } else {
                  _paths ~= " " ~ _img_pth(_possible_img_pth);
                }
              }
            }
            if (_img_pth_found.empty) {
              writeln("WARNING not image path found, searched: ", _paths);
            }
            return _img_pth_found;
          }
          @safe auto conf_dir_path() {
            return ((base_dir_path.chainPath("conf")).asNormalizedPath).array;
          }
          @safe auto base_parent_dir() {
            string _dir;
            if (
              auto m = (absolute_path_to_src)
              .match(regex(r"[/](?P<dir>(?:[a-zA-Z0-9._-]+))/pod/" ~ filename.stripExtension))
            ) {
              _dir = m.captures["dir"];
            } else {
              _dir = (absolute_path_to_src).match(rgx.src_base_parent_dir_name).captures["dir"];
            }
            if (_opt_action.debug_do) {
              writeln("--> (base_parent_dir) ", _dir);
            }
            return _dir;
          }
          @safe string[] config_dirs() {
            string[] _config_dirs;
            if (is_pod) {
            } else {}
            return _config_dirs;
          }
          @safe string[] image_dirs() {
            string[] _image_dirs;
            if (is_pod) {
            } else {}
            return _image_dirs;
          }
        }
        return SRC_();
      }
      @safe auto output() {
        /+
          - command line if output path set
          - config file if found and set set
            - search for and if exists read config
              - default paths to config related to:
                - source markup path;
                - current dir;
                - home dir
              - get output path if set
          - (program) default within current directory?
        +/
        auto _env = _env;
        struct Out_ {
          @safe auto path() {
            auto _output_path = _env["pwd"];
            if ((_opt_action.output_dir_set.length > 0)
              && isValidPath(_opt_action.output_dir_set)
            ) {
              _output_path = ((_opt_action.output_dir_set).asNormalizedPath).array;
              if (!exists(_output_path)) {
                try {
                  _output_path.mkdirRecurse;
                // } catch (ErrnoException ex) {
                } catch (Exception ex) {
                  // Handle error
                }
              }
              assert(_output_path.isDir,
                "not a directory: " ~ _output_path);
              // TODO always test that is a directory and it is writable
            }
            return _output_path;
          }
        }
        return Out_();
      }
    }
    return ManifestMatters_();
  }
}
#+END_SRC

** _config_ (dr_document_make & config_local_site)                         :config:

#+NAME: template_paths_src_2
#+BEGIN_SRC d
template configFilePaths() {
  mixin spineRgxIn;
  static auto rgx = RgxI();
  @safe auto configFilePaths(M,E)(
    M      _manifested,
    E      _env,
    string _cli_config_path_set = ""
  ) {
    struct ConfFilePaths {
      @safe string config_filename_document() {
        return "dr_document_make";
      }
      @safe string config_filename_site() {
        return "config_local_site";
      }
      @safe auto possible_config_path_locations() {
        struct _ConfFilePaths {
          @safe string[] dr_document_make() {
            /+ FIX clean up conf paths ↓ +/
            /+ config local site (file system only, not in pod) +/
            /+ return paths +/
            string[] _possible_config_path_locations;
            if (_cli_config_path_set.empty) {
              if (_manifested.src.is_pod) {
                /+ config document in pod +/
                string _dr_doc_conf_pod;
                string _dr_doc_conf_pod_text;
                _dr_doc_conf_pod = asNormalizedPath(chainPath(
                  to!string(_env["pwd"]),
                  _manifested.pod.manifest_path ~ "/conf"
                )).array;
                _dr_doc_conf_pod_text = asNormalizedPath(chainPath(
                  to!string(_env["pwd"]),
                  _manifested.pod.manifest_path ~ "/media/text/" ~ _manifested.src.lng ~ "/conf"
                )).array;
                /+ return paths +/
                _possible_config_path_locations = [
                  _dr_doc_conf_pod_text,
                  _dr_doc_conf_pod,
                ];
              } else {
                /+ config document (& or local site) on filesystem +/
                string _dr_doc_conf_pwd   = ((chainPath(to!string(_env["pwd"]), "dr_doc/conf")).asNormalizedPath).array; // think about
                string _dr_doc_conf_pwd_a = ((chainPath(to!string(_env["pwd"]), "conf")).asNormalizedPath).array;
                string _dr_doc_conf_pwd_b = ((chainPath(to!string(_env["pwd"]), "../conf")).asNormalizedPath).array;
                string _dr_doc_conf_pwd_c = ((chainPath(to!string(_env["pwd"]), "../../conf")).asNormalizedPath).array;
                string _dr_doc_conf_pwd_d = ((chainPath(to!string(_env["pwd"]), "../../../conf")).asNormalizedPath).array;
                /+ return paths +/
                _possible_config_path_locations = [
                  _dr_doc_conf_pwd,
                  _dr_doc_conf_pwd_a,
                  _dr_doc_conf_pwd_b,
                  _dr_doc_conf_pwd_c,
                  _dr_doc_conf_pwd_d,
                ];
              }
            } else if (_cli_config_path_set.isDir) {
               _possible_config_path_locations = [_cli_config_path_set ];
            // } else if (_cli_config_path_set.isFile) { // use file, taken care of elsewhere
            }
            /+ FIX clean up conf paths ↑
            (compare pwd to doc path location, and build config path)
            +/
            return _possible_config_path_locations;
          }
          @safe string[] config_local_site() {
            /+ FIX clean up conf paths ↓ +/
            /+ config local site (file system only, not in pod) +/
            string[] _possible_config_path_locations;
            if (_cli_config_path_set.empty) {
              string _dot_pwd        = ((chainPath(to!string(_env["pwd"]), ".dr")).asNormalizedPath).array;
              string _underscore_pwd = ((chainPath(to!string(_env["pwd"]), "_dr")).asNormalizedPath).array;
              string _dot_home       = ((chainPath(to!string(_env["home"]), ".dr")).asNormalizedPath).array;
              /+ return paths +/
              if (_manifested.src.is_pod) {
                string _collection_root_a = ((chainPath(to!string(_manifested.pod.collection_root.to!string), ".dr")).asNormalizedPath).array;
                string _collection_root_b = ((chainPath(to!string(_manifested.pod.collection_root.to!string), "_dr")).asNormalizedPath).array;
                _possible_config_path_locations = [
                  _dot_pwd,
                  _underscore_pwd,
                  _collection_root_a,
                  _collection_root_b,
                  _dot_home,
                  "/etc/dr",
                ];
              } else {
                /+ config document (& or local site) on filesystem +/
                string _dr_doc_conf_pwd   = ((chainPath(to!string(_env["pwd"]), "dr_doc/conf")).asNormalizedPath).array;
                string _dr_doc_conf_pwd_a = ((chainPath(to!string(_env["pwd"]), "conf")).asNormalizedPath).array;
                string _dr_doc_conf_pwd_b = ((chainPath(to!string(_env["pwd"]), "../conf")).asNormalizedPath).array;
                string _dr_doc_conf_pwd_c = ((chainPath(to!string(_env["pwd"]), "../../conf")).asNormalizedPath).array;
                string _dr_doc_conf_pwd_d = ((chainPath(to!string(_env["pwd"]), "../../../conf")).asNormalizedPath).array;
                _possible_config_path_locations = [
                  _dr_doc_conf_pwd,
                  _dr_doc_conf_pwd_a,
                  _dr_doc_conf_pwd_b,
                  _dr_doc_conf_pwd_c,
                  _dr_doc_conf_pwd_d,
                  _dot_pwd,
                  _underscore_pwd,
                  _dot_home,
                  "/etc/dr"
                ];
              }
            } else {
              _possible_config_path_locations = [
                _cli_config_path_set
              ];
            }
            /+ FIX clean up conf paths ↑
            (compare pwd to doc path location, and build config path)
            +/
            return _possible_config_path_locations;
          }
        }
        return _ConfFilePaths();
      }
    }
    return ConfFilePaths();
  }
}
#+END_SRC

** _pod_ (including generated bundled source)                             :pod:
*** notes

tree pod
pod
└── dr_doc
    ├── conf
    │   └── dr_document_make
    ├── media
    │   ├── audio
    │   ├── image
    │   ├── text
    │   │   └── en
    │   │      ...
    │   │       ├── [conf]
    │   │       └── [image]
    │   └── video
    └── pod.manifest

_replace:_

doc
├── en
│   └── the_wealth_of_networks.yochai_benkler.sst
└── _dr
    └── dr_document_make

filelist for processing [things to ponder]

- the plan is to have a src manifest of related .sst or .ssm files that
  can be pointed to for processing
- multilingual documents [ponder persistence for multilingual documents]
  - in the case of multilingual documents, there will be multiple
    source documents on list with different language codes, and to build
    this list, persistence is required
- inserted documents (from master .ssm or .sst) must track document root
  so inserts can be located, else not found
- consider a commandline -o --output path specifier
- steps
  - auto create filelist
    - for single source file
    - for multilinugual source files
  - process document by pointing at filelist (rather than file)
    - if necessary manually create filelist (for multilinugual source)
    - keep document root for document inserts (.ssi)

*** manual source

#+NAME: template_paths_src_3
#+BEGIN_SRC d
template spinePathsSRC() {
  mixin spineRgxIn;
  static auto rgx = RgxI();
  @safe auto spinePathsSRC(D,Fn)(
    D   _pwd,
    Fn  _fn_src_and_path,
  ) {
    struct drSrcPaths {
      @safe auto pwd() {
        return _pwd;
      }
      @safe string language() {
        // use command line info as well?
        string _k;
        if (auto m = _fn_src_and_path.match(rgx.language_code_and_filename)) {
          _k = m.captures[1];
        } else { /+ unknown until doc_meta read, (could provide & use command line info?) +/
          _k = "xx"; // original default was "en" but is not known
        }
        return _k;
      }
      @safe string doc_root() {
        return "dr_doc";
      }
      @safe auto media_root() {
        return ((doc_root.chainPath("media")).asNormalizedPath).array;
      }
      @safe auto conf_root() {
        return ((doc_root.chainPath("conf")).asNormalizedPath).array;
      }
      @safe auto text_root() {
        return ((media_root.chainPath("text")).asNormalizedPath).array;
      }
      @safe auto image_root() {
        return ((media_root.chainPath("image")).asNormalizedPath).array;
      }
      @safe auto doc_src_fn_with_path_for_text_root_and_lng() {
        return ((text_root.chainPath(language)).asNormalizedPath).array;
      }
      @safe auto doc_src_fn() {
        return ((_fn_src_and_path.baseName).asNormalizedPath).array;
      }
      @safe auto doc_src_with_path() {
        return ((pwd.chainPath(_fn_src_and_path)).asNormalizedPath).array;
      }
    }
    return drSrcPaths();
  }
}
#+END_SRC

*** pods internal, zipped, unzipped

#+NAME: template_paths_pods
#+BEGIN_SRC d
template spinePathsPods() {
  mixin spineRgxIn;
  static auto rgx = RgxI();
  string _suffix = ".zip";
  auto spinePathsPods(M)(M doc_matters) {
    string _base_dir_pod = (doc_matters.output_path.length > 0)
    ? doc_matters.output_path ~ "/pod"
    : "/pod";
    string _base_dir_doc = "dr_doc";
    struct _PodPaths {
      string base_filename_(string fn_src) {
        return  fn_src.baseName.stripExtension;
      }
      string internal_base() {
        return "pod";
      }
      string pod_dir_() {
        return _base_dir_pod;
      }
      string dr_doc_dir_() {
        return _base_dir_doc;
      }
      string pod_filename_(string fn_src) {
        return _base_dir_pod.chainPath(base_filename_(fn_src) ~ _suffix).array;
      }
      string base_filesystem_(string fn_src) {
        string pth = _base_dir_pod.chainPath(base_filename_(fn_src)).array;
        assert(pth == _base_dir_pod ~ "/"  ~ base_filename_(fn_src),
          pth ~ " == "  ~ _base_dir_pod ~ "/" ~ base_filename_(fn_src) ~ "?");
        return pth;
      }
      string output_pod_manifest_file(string fn_src) {
        return base_filesystem_(fn_src).chainPath("pod.manifest").array;
      }
      string base_pod_(string fn_src) {
        return _base_dir_pod.chainPath(base_filename_(fn_src)).array; // change this
      }
      auto base_filename(string fn_src) {
        auto pth_1_ = base_filename_(fn_src);
        auto pth_2_ = base_filename_(fn_src);
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            return pth_2_;
          }
        }
        return _pods();
      }
      auto pod_filename(string fn_src) {
        auto pth_1_ = pod_filename_(fn_src);
        auto pth_2_ = pod_filename_(fn_src);
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            return pth_2_;
          }
        }
        return _pods();
      }
      auto base(string fn_src) {
        auto pth_1_ = "";
        auto pth_2_ = base_filesystem_(fn_src);
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            return pth_2_;
          }
        }
        return _pods();
      }
      auto pod_root(string fn_src) {
        auto pth_1_ = "pod";
        auto pth_2_ = ((base(fn_src).filesystem_open_zpod.chainPath("")).asNormalizedPath).array; // "dr_doc"
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            return pth_2_;
          }
        }
        return _pods();
      }
      auto conf_root(string fn_src) {
        auto pod_root_ = pod_root(fn_src);
        auto pth_1_ = "conf";
        auto pth_2_ = ((pod_root(fn_src).filesystem_open_zpod.chainPath("conf")).asNormalizedPath).array;
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            // assert(pod_root_.filesystem_open_zpod.chainPath(zpod).array == pth_2_);
            return pth_2_;
          }
        }
        return _pods();
      }
      auto css(string fn_src) {
        auto pod_root_ = pod_root(fn_src);
        auto pth_1_ = ((conf_root(fn_src).zpod.chainPath("css")).asNormalizedPath).array;
        auto pth_2_ = ((conf_root(fn_src).filesystem_open_zpod.chainPath("css")).asNormalizedPath).array;
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            // assert(pod_root_.filesystem_open_zpod.chainPath(zpod).array == pth_2_);
            return pth_2_;
          }
        }
        return _pods();
      }
      auto pod_manifest(string fn_src) {
        auto pod_root_ = pod_root(fn_src);
        auto pth_1_ = ((pod_root(fn_src).zpod.chainPath("pod.manifest")).asNormalizedPath).array;
        auto pth_2_ = ((pod_root(fn_src).filesystem_open_zpod.chainPath("pod.manifest")).asNormalizedPath).array;
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            // assert(pod_root_.filesystem_open_zpod.chainPath(zpod).array == pth_2_);
            return pth_2_;
          }
        }
        return _pods();
      }
      auto media_root(string fn_src) {
        auto pod_root_ = pod_root(fn_src);
        auto pth_1_ = ((pod_root(fn_src).zpod.chainPath("media")).asNormalizedPath).array;
        auto pth_2_ = ((pod_root(fn_src).filesystem_open_zpod.chainPath("media")).asNormalizedPath).array;
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            // assert(pod_root_.filesystem_open_zpod.chainPath(zpod).array == pth_2_);
            return pth_2_;
          }
        }
        return _pods();
      }
      auto text_root(string fn_src) {
        auto pod_root_ = pod_root(fn_src);
        auto pth_1_ = ((media_root(fn_src).zpod.chainPath("text")).asNormalizedPath).array;
        auto pth_2_ = ((media_root(fn_src).filesystem_open_zpod.chainPath("text")).asNormalizedPath).array;
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            // assert(pod_root_.filesystem_open_zpod.chainPath(zpod).array == pth_2_);
            return pth_2_;
          }
        }
        return _pods();
      }
      auto doc(string fn_src) {
        auto pod_root_ = pod_root(fn_src);
        auto pth_1_ = text_root(fn_src).zpod;
        auto pth_2_ = text_root(fn_src).filesystem_open_zpod;
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            // assert(pod_root_.filesystem_open_zpod.chainPath(zpod).array == pth_2_);
            return pth_2_;
          }
        }
        return _pods();
      }
      auto doc_lng(string fn_src, string lng) {
        auto pod_root_ = pod_root(fn_src);
        auto pth_1_ = ((text_root(fn_src).zpod.chainPath(lng)).asNormalizedPath).array;
        auto pth_2_ = ((text_root(fn_src).filesystem_open_zpod.chainPath(lng)).asNormalizedPath).array;
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            // assert(pod_root_.filesystem_open_zpod.chainPath(zpod).array == pth_2_);
            return pth_2_;
          }
        }
        return _pods();
      }
      auto image_root(string fn_src) {
        auto pod_root_ = pod_root(fn_src);
        auto pth_1_ = ((media_root(fn_src).zpod.chainPath("image")).asNormalizedPath).array;
        auto pth_2_ = ((media_root(fn_src).filesystem_open_zpod.chainPath("image")).asNormalizedPath).array;
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            // assert(pod_root_.filesystem_open_zpod.chainPath(zpod).array == pth_2_);
            return pth_2_;
          }
        }
        return _pods();
      }
      auto fn_pod_filelist(string fn_src) {
        auto pod_root_ = pod_root(fn_src);
        auto _manifested = PodManifest!()(doc_matters.opt.action, fn_src).pod_manifest_filename;
        auto pth_1_ = _manifested;
        auto pth_2_ = ((pod_root(fn_src).filesystem_open_zpod.chainPath(_manifested)).asNormalizedPath).array;
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            // assert(pod_root_.filesystem_open_zpod.chainPath(zpod).array == pth_2_);
            return pth_2_;
          }
        }
        return _pods();
      }
      auto fn_doc(string fn_src, string lng) {
        auto pod_root_ = pod_root(fn_src);
        auto pth_1_ = ((doc_lng(fn_src, lng).zpod.chainPath(fn_src.baseName)).asNormalizedPath).array;
        auto pth_2_ = ((doc_lng(fn_src, lng).filesystem_open_zpod.chainPath(fn_src.baseName)).asNormalizedPath).array;
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            // assert(pod_root_.filesystem_open_zpod.chainPath(zpod).array == pth_2_);
            return pth_2_;
          }
        }
        return _pods();
      }
      auto fn_doc_insert(string fn_src, string fn_insert, string lng) {
        auto pod_root_ = pod_root(fn_src);
        auto pth_1_ = ((doc_lng(fn_src, lng).zpod.chainPath(fn_insert.baseName)).asNormalizedPath).array;
        auto pth_2_ = ((doc_lng(fn_src, lng).filesystem_open_zpod.chainPath(fn_insert.baseName)).asNormalizedPath).array;
        struct _pods {
          auto zpod() {
            return pth_1_;
          }
          auto filesystem_open_zpod() {
            // assert(pod_root_.filesystem_open_zpod.chainPath(zpod).array == pth_2_);
            return pth_2_;
          }
        }
        return _pods();
      }
    }
    return _PodPaths();
  }
}
#+END_SRC

* 1. output paths
** _module template_                                      :module:paths_output:

#+BEGIN_SRC d :tangle "../src/doc_reform/io_out/paths_output.d"
/++
  default settings
+/
module doc_reform.io_out.paths_output;
import
  std.array,
  std.path,
  std.regex,
  std.stdio;
import doc_reform.meta.rgx;
<<template_paths_out_0>>
<<template_paths_out_1>>
<<template_paths_url>>
<<template_paths_html_0>>
<<template_paths_html_1>>
<<template_paths_html_2>>
<<template_paths_epub>>
<<template_paths_odf>>
<<template_paths_latex>>
<<template_paths_sqlite_0>>
<<template_paths_sqlite_1>>
<<template_paths_sqlite_2>>
#+END_SRC

** shared out path, base directory                                      :out:
- output paths for content, minimize chance of filename/directory collisions
  in particular pods can contain markup that is published under a name that may
  be used elsewere
  - if the pod directory has a different name from the file append the pod
    directory name to the output file name: pod_directory.markup_filename e.g.
    dr-manual.sisu_markup
  - if pod and file have same name, keep name (makes no sense to repeat pod
    name) NO dr_markup.sisu_markup should be sisu_markup

#+NAME: template_paths_out_0
#+BEGIN_SRC d
template spineOutPaths() {
  auto spineOutPaths()(
    string output_pth_root,
    string lng = "",
  ) {
    struct _PathsStruct {
      string output_root() {
        return (output_pth_root.length > 0)
        ? output_pth_root : "";
      }
      string output_base() {
        return ((output_root.chainPath(lng)).asNormalizedPath).array;
      }
      string internal_base() {
        return lng.asNormalizedPath.array;
      }
    }
    return _PathsStruct();
  }
}
template spineOutPathSQLite() {
  auto spineOutPathSQLite(Po)(
    Po  output_pth_root,
  ) {
    struct _PathsStruct {
      string output_root() {
        return (output_pth_root.length > 0)
        ? output_pth_root : "";
      }
      string output_base() {
        return ((output_root).asNormalizedPath).array;
      }
    }
    return _PathsStruct();
  }
}
template spineOutPathSQLiteCGI() {
  auto spineOutPathSQLiteCGI(Po)(
    Po  output_pth_root,
  ) {
    struct _PathsStruct {
      string output_root() {
        return (output_pth_root.length > 0)
        ? output_pth_root : "";
      }
      string output_base() {
        return ((output_root).asNormalizedPath).array;
      }
    }
    return _PathsStruct();
  }
}
#+END_SRC

** set

#+NAME: template_paths_out_1
#+BEGIN_SRC d
template spineOutPathsFnPd() {
  /+ TODO stuff to work out here +/
  auto spineOutPathsFnPd(Fn,Pn)(
    Fn  fn_src_pth,
    Pn  pod_name_with_path
  ) {
    struct _PathsStruct {
      string base_filename() {
        return fn_src_pth.baseName.stripExtension;
      }
      string base_pod_and_filename() { // TODO
        /+
          - if pod,
            - pod_name
            - file_name
            - if pod_name == file_name
              - file_name
            - else if pod_name != file_name
              - pod_name.file_name
        +/
        string _fn_src = fn_src_pth.baseName.stripExtension;
        string _output_base_name;
        if (!(pod_name_with_path.empty)) {
          if (pod_name_with_path == _fn_src) {
            _output_base_name = _fn_src;
          } else {
            _output_base_name = pod_name_with_path ~ "." ~ _fn_src;
          }
        } else {
          _output_base_name = _fn_src;
        }
        return _output_base_name;
      }
    }
    return _PathsStruct();
  }
}
#+END_SRC

** _html_                                                                :html:
*** relative

#+NAME: template_paths_html_0
#+BEGIN_SRC d
template spineDocRootTreeHTML() {
  mixin spineRgxIn;
  static auto rgx = RgxI();
  auto spineDocRootTreeHTML()(string lng) {
    auto lng_pth = spineOutPaths!()("", lng);
    string base_dir = "html";
    string suffix = ".html";
    struct _PathsStruct {
      string base_filename(string fn_src) {
        return fn_src.baseName.stripExtension;
      }
      string base_filename_scroll(string fn_src) {
        return base_filename(fn_src);
      }
      string base_filename_seg(string fn_src) {
        return base_filename(fn_src);
      }
      string doc_root() {
        return ((lng_pth.output_root).asNormalizedPath).array;
      }
      string base() {
        return (((lng).chainPath(base_dir)).asNormalizedPath).array;
      }
      string image() {
        return (("image").asNormalizedPath).array;
      }
      string css() {
        return (("css").asNormalizedPath).array;
      }
      string fn_seg_css() {
        return ((css.chainPath("html_seg.css")).asNormalizedPath).array;
      }
      string fn_scroll_css() {
        return ((css.chainPath("html_scroll.css")).asNormalizedPath).array;
      }
      string seg(string fn_src) {
        return ((base.chainPath(base_filename_seg(fn_src))).asNormalizedPath).array;
      }
      string fn_metadata(string fn_src) {
        return ((base.chainPath("metadata." ~ base_filename_scroll(fn_src) ~ suffix)).asNormalizedPath).array;
      }
      string fn_scroll(string fn_src) {
        return ((base.chainPath(base_filename_scroll(fn_src) ~ suffix)).asNormalizedPath).array;
      }
      string fn_seg(string fn_src, string seg_filename) {
        return ((seg(fn_src).chainPath(seg_filename ~ suffix)).asNormalizedPath).array;
      }
      string tail_seg(string fn_src) {
        return lng ~ "/html/" ~ base_filename_seg(fn_src);
      }
      string tail_fn_scroll(string fn_src) {
        return lng ~ "/html/" ~ base_filename_scroll(fn_src) ~ suffix;
      }
      string tail_fn_seg(string fn_src, string seg_filename) {
        return lng ~ "/html/" ~ seg(fn_src) ~ "/" ~ seg_filename ~ suffix;
      }
    }
    return _PathsStruct();
  }
}
#+END_SRC

*** absolute disk path

#+NAME: template_paths_html_1
#+BEGIN_SRC d
template spinePathsHTML() {
  mixin spineRgxIn;
  static auto rgx = RgxI();
  auto spinePathsHTML()(
    string output_path_root,
    string lng,
  ) {
    auto doc_tree = spineDocRootTreeHTML!()(lng);
    string base_dir = "html";
    string suffix = ".html";
    struct _PathsStruct {
      string doc_root() {
        return ((output_path_root.chainPath(doc_tree.doc_root)).asNormalizedPath).array;
      }
      string harvest(string fn_harvest) {
        return doc_root ~ "/" ~ fn_harvest;
      }
      string internal_base() {
        return ((doc_tree.base).asNormalizedPath).array;
      }
      string base() {
        return ((output_path_root.chainPath(doc_tree.base)).asNormalizedPath).array;
      }
      string image() {
        return ((output_path_root.chainPath(doc_tree.image)).asNormalizedPath).array;
      }
      string css() {
        return ((output_path_root.chainPath(doc_tree.css)).asNormalizedPath).array;
      }
      string fn_seg_css() {
        return ((output_path_root.chainPath(doc_tree.fn_seg_css)).asNormalizedPath).array;
      }
      string fn_scroll_css() {
        return ((output_path_root.chainPath(doc_tree.fn_scroll_css)).asNormalizedPath).array;
      }
      string seg(string fn_src) {
        return ((output_path_root.chainPath(doc_tree.seg(fn_src))).asNormalizedPath).array;
      }
      string fn_metadata(string fn_src) {
        return ((output_path_root.chainPath(doc_tree.fn_metadata(fn_src))).asNormalizedPath).array;
      }
      string fn_scroll(string fn_src) {
        return ((output_path_root.chainPath(doc_tree.fn_scroll(fn_src))).asNormalizedPath).array;
      }
      string fn_seg(string fn_src, string seg_filename) {
        return ((output_path_root.chainPath(doc_tree.fn_seg(fn_src, seg_filename))).asNormalizedPath).array;
      }
      string tail_seg(string fn_src) {
        return doc_tree.tail_seg(fn_src);
      }
      string tail_fn_scroll(string fn_src) {
        return doc_tree.tail_fn_scroll(fn_src);
      }
      string tail_fn_seg(string fn_src, string seg_filename) {
        return doc_tree.tail_fn_seg(fn_src, seg_filename);
      }
    }
    return _PathsStruct();
  }
}
#+END_SRC

*** urls

#+NAME: template_paths_html_2
#+BEGIN_SRC d
template spineUrlsHTML() {
  import std.format;
  mixin spineRgxIn;
  static auto rgx = RgxI();
  auto spineUrlsHTML()(
    string url_doc_root,
    string lng,
  ) {
    auto doc_tree = spineDocRootTreeHTML!()(lng);
    string base_dir = "html";
    string suffix = ".html";
    struct _PathsStruct {
      string doc_root() {
        return url_doc_root ~ ((doc_tree.doc_root).asNormalizedPath).array;
      }
      string harvest(string fn_harvest) {
        return format(q"┃%s/%s┃",
          doc_root,
          fn_harvest,
        );
      }
      string base() {
        return format(q"┃%s/%s┃",
          url_doc_root,
          ((doc_tree.base).asNormalizedPath).array,
        );
      }
      string image() {
        return format(q"┃%s/%s┃",
          url_doc_root,
          ((doc_tree.image).asNormalizedPath).array,
        );
      }
      string css() {
        return format(q"┃%s/%s┃",
          url_doc_root,
          ((doc_tree.css).asNormalizedPath).array,
        );
      }
      string fn_seg_css() {
        return format(q"┃%s/%s┃",
          url_doc_root,
          ((doc_tree.fn_seg_css).asNormalizedPath).array,
        );
      }
      string fn_scroll_css() {
        return format(q"┃%s/%s┃",
          url_doc_root,
          ((doc_tree.fn_scroll_css).asNormalizedPath).array,
        );
      }
      string seg(string fn_src) {
        return format(q"┃%s/%s┃",
          url_doc_root,
          ((doc_tree.seg(fn_src)).asNormalizedPath).array,
        );
      }
      string fn_metadata(string fn_src) {
        return format(q"┃%s/%s┃",
          url_doc_root,
          ((doc_tree.fn_metadata(fn_src)).asNormalizedPath).array,
        );
      }
      string fn_scroll(string fn_src) {
        return format(q"┃%s/%s┃",
          url_doc_root,
          ((doc_tree.fn_scroll(fn_src)).asNormalizedPath).array,
        );
      }
      string fn_seg(string fn_src, string seg_filename) {
        return format(q"┃%s/%s┃",
          url_doc_root,
          ((doc_tree.fn_seg(fn_src, seg_filename)).asNormalizedPath).array,
        );
      }
      string fn_scroll_obj_num(string fn_src, string obj_num) {
        return format(q"┃%s/%s#%s┃",
          url_doc_root,
          ((doc_tree.fn_scroll(fn_src)).asNormalizedPath).array,
          obj_num,
        );
      }
      string fn_seg_obj_num(string fn_src, string seg_filename, string obj_num) {
        return format(q"┃%s/%s#%s┃",
          url_doc_root,
          ((doc_tree.fn_seg(fn_src, seg_filename)).asNormalizedPath).array,
          obj_num,
        );
      }
      string tail_seg(string fn_src) {
        return doc_tree.tail_seg(fn_src);
      }
      string tail_fn_scroll(string fn_src) {
        return doc_tree.tail_fn_scroll(fn_src);
      }
      string tail_fn_seg(string fn_src, string seg_filename) {
        return doc_tree.tail_fn_seg(fn_src, seg_filename);
      }
    }
    return _PathsStruct();
  }
}
#+END_SRC

** _epub_                                                                :epub:

#+NAME: template_paths_epub
#+BEGIN_SRC d
template spinePathsEPUB() {
  mixin spineRgxIn;
  static auto rgx = RgxI();
  auto spinePathsEPUB()(
    string output_pth_root,
    string lng,
  ) {
    auto out_pth = spineOutPaths!()(output_pth_root, lng);
    string base_dir = "epub";
    struct _PathsStruct {
      string internal_base() {
        return (((out_pth.internal_base).chainPath(base_dir)).asNormalizedPath).array;
      }
      string base() {
        return (((out_pth.output_base).chainPath(base_dir)).asNormalizedPath).array;
      }
      string base_filename(string fn_src) {
        return fn_src.baseName.stripExtension;
      }
      string base_filename_epub(string fn_src) {
        return base_filename(fn_src) ~ "." ~ lng;
      }
      string doc_meta_inf() {
        return (("META-INF").asNormalizedPath).array;
      }
      string doc_oebps() {
        return (("OEBPS").asNormalizedPath).array;
      }
      string doc_oebps_css() {
        return ((doc_oebps.chainPath("css")).asNormalizedPath).array;
      }
      string doc_oebps_image() {
        return ((doc_oebps.chainPath("image")).asNormalizedPath).array;
      }
      string epub_file(string fn_src) {
        return ((base.chainPath(base_filename_epub(fn_src) ~ ".epub")).asNormalizedPath).array;
      }
      string dirtop() {
        return "".chainPath("").array;
      }
      string fn_mimetypes() {
        return ((dirtop.chainPath("mimetypes")).asNormalizedPath).array;
      }
      string fn_dmi_container_xml() {
        return ((doc_meta_inf.chainPath("container.xml")).asNormalizedPath).array;
      }
      string fn_oebps_toc_nav_xhtml() {
        return ((doc_oebps.chainPath("toc_nav.xhtml")).asNormalizedPath).array;
      }
      string fn_oebps_toc_ncx() {
        return ((doc_oebps.chainPath("toc.ncx")).asNormalizedPath).array;
      }
      string fn_oebps_content_opf() {
        return ((doc_oebps.chainPath("content.opf")).asNormalizedPath).array;
      }
      string fn_oebps_content_xhtml(string seg_filename) {
        return ((doc_oebps.chainPath(seg_filename ~ ".xhtml")).asNormalizedPath).array;
      }
      string fn_oebps_css() {
        return ((doc_oebps_css.chainPath("epub.css")).asNormalizedPath).array;
      }
      /+ debug +/
      string dbg_docdir(string fn_src) {
        return base.chainPath(base_filename(fn_src)).array;
      }
      string dbg_docdir_oebps(string fn_src) {
        return dbg_docdir(fn_src).chainPath("OEBPS").array;
      }
      string dbg_doc_meta_inf(string fn_src) {
        return dbg_docdir(fn_src).chainPath("META-INF").array;
      }
      string dbg_doc_oebps(string fn_src) {
        return dbg_docdir(fn_src).chainPath("OEBPS").array;
      }
      string dbg_doc_oebps_css(string fn_src) {
        return dbg_doc_oebps(fn_src).chainPath("css").array;
      }
      string dbg_doc_oebps_image(string fn_src) {
        return dbg_doc_oebps(fn_src).chainPath("image").array;
      }
      string dbg_fn_mimetypes(string fn_src) {
        return dbg_docdir(fn_src).chainPath("mimetypes").array;
      }
      string dbg_fn_dmi_container_xml(string fn_src) {
        return dbg_doc_meta_inf(fn_src).chainPath("container.xml").array;
      }
      string dbg_fn_oebps_toc_nav_xhtml(string fn_src) {
        return dbg_docdir_oebps(fn_src).chainPath("toc_nav.xhtml").array;
      }
      string dbg_fn_oebps_toc_ncx(string fn_src) {
        return dbg_docdir_oebps(fn_src).chainPath("toc.ncx").array;
      }
      string dbg_fn_oebps_content_opf(string fn_src) {
        return dbg_docdir_oebps(fn_src).chainPath("content.opf").array;
      }
      string dbg_fn_oebps_content_xhtml(string fn_src, string seg_filename) {
        return dbg_docdir_oebps(fn_src).chainPath(seg_filename ~ ".xhtml").array;
      }
      string dbg_fn_oebps_css(string fn_src) {
        return dbg_doc_oebps_css(fn_src).chainPath("epub.css").array;
      }
    }
    return _PathsStruct();
  }
}
#+END_SRC

** _odt_                                                                  :odt:

#+NAME: template_paths_odf
#+BEGIN_SRC d
template spinePathsODT() {
  mixin spineRgxIn;
  static auto rgx = RgxI();
  auto spinePathsODT(M)(
    M  doc_matters,
  ) {
    auto out_pth = spineOutPaths!()( doc_matters.output_path, doc_matters.src.language);
    string base_dir = "odf";
    struct _PathsStruct {
      string base_pth() { // dir will contain odt document file (also debug file tree)
        return (((out_pth.output_base).chainPath(base_dir)).asNormalizedPath).array;
      }
      string odt_file() {
        return ((base_pth.chainPath(doc_matters.src.doc_uid_out ~ ".odt")).asNormalizedPath).array;
      }
      string dirtop(string type) {
        return (type == "zip")
        ? "".chainPath("").array
        : ((base_pth.chainPath(doc_matters.src.doc_uid_out)).asNormalizedPath).array;
      }
      string mimetype(string type="fs") {
        assert(type == "zip" || "fs");
        return ((dirtop(type).chainPath("mimetype")).asNormalizedPath).array;
      }
      string manifest_rdf(string type="fs") {
        assert(type == "zip" || "fs");
        return ((dirtop(type).chainPath("manifest.rdf")).asNormalizedPath).array;
      }
      string settings_xml(string type="fs") {
        assert(type == "zip" || "fs");
        return ((dirtop(type).chainPath("settings.xml")).asNormalizedPath).array;
      }
      string styles_xml(string type="fs") {
        assert(type == "zip" || "fs");
        return ((dirtop(type).chainPath("styles.xml")).asNormalizedPath).array;
      }
      string image_dir(string type="fs") {
        assert(type == "zip" || "fs");
        return ((dirtop(type).chainPath("Pictures")).asNormalizedPath).array;
      }
      string image(string image_fn_src, string type="fs") {
        assert(type == "zip" || "fs");
        return ((image_dir(type).chainPath(image_fn_src)).asNormalizedPath).array;
      }
      string content_xml(string type="fs") {
        assert(type == "zip" || "fs");
        return ((dirtop(type).chainPath("content.xml")).asNormalizedPath).array;
      }
      string meta_inf_dir(string type="fs") {
        assert(type == "zip" || "fs");
        return ((dirtop(type).chainPath("META-INF")).asNormalizedPath).array;
      }
      string manifest_xml(string type="fs") {
        assert(type == "zip" || "fs");
        return ((meta_inf_dir(type).chainPath("manifest.xml")).asNormalizedPath).array;
      }
      string meta_xml(string type="fs") {
        assert(type == "zip" || "fs");
        return ((dirtop(type).chainPath("meta.xml")).asNormalizedPath).array;
      }
    }
    return _PathsStruct();
  }
}
#+END_SRC

** _latex_                                                              :latex:

#+NAME: template_paths_latex
#+BEGIN_SRC d
template spinePathsLaTeX() {
  mixin spineRgxIn;
  static auto rgx = RgxI();
  auto spinePathsLaTeX(M)(
    M  doc_matters,
  ) {
    struct _PathsStruct {
      string base_filename(string fn_src) {
        return fn_src.baseName.stripExtension;
      }
      string base() {
        auto out_pth = spineOutPaths!()(doc_matters.output_path, doc_matters.src.language);
        string base_dir = "latex";
        return (((out_pth.output_root).chainPath(base_dir)).asNormalizedPath).array;
      }
      string latex_path_stuff() {
        return ((base.chainPath(base_filename(doc_matters.src.filename))).asNormalizedPath).array;
      }
      string latex_file_with_path(string paper_size_orientation) {
        return ((base.chainPath(base_filename(doc_matters.src.filename)
             ~ "." ~ doc_matters.src.language
             ~ "."  ~ paper_size_orientation
             ~ ".tex")
          ).asNormalizedPath).array;
      }
      string images() {
        string image_dir = "image";
        return (((base).chainPath(image_dir)).asNormalizedPath).array;
      }
    }
    return _PathsStruct();
  }
}
#+END_SRC

** _sqlite_                                                            :sqlite:
*** discrete

#+NAME: template_paths_sqlite_0
#+BEGIN_SRC d
template spinePathsSQLiteDiscrete() {
  mixin spineRgxIn;
  static auto rgx = RgxI();
  auto spinePathsSQLiteDiscrete()(
    string output_pth_root,
    string lng,
  ) {
    struct _PathsStruct {
      string base_filename(string fn_src) {
        return fn_src.baseName.stripExtension;
      }
      string base() {
        auto out_pth = spineOutPaths!()(output_pth_root, lng);
        string base_dir = "sqlite";
        return (((out_pth.output_base).chainPath(base_dir)).asNormalizedPath).array;
      }
      string seg(string fn_src) {
        return ((base.chainPath(base_filename(fn_src))).asNormalizedPath).array;
      }
      string sqlite_file(string fn_src) {
        return ((base.chainPath(base_filename(fn_src) ~ ".sql.db")).asNormalizedPath).array;
      }
    }
    return _PathsStruct();
  }
}
#+END_SRC

*** TODO collection

#+NAME: template_paths_sqlite_1
#+BEGIN_SRC d
template spinePathsSQLite() {
  mixin spineRgxIn;
  static auto rgx = RgxI();
  auto spinePathsSQLite()(
    string db_name,
    string output_pth_root,
  ) {
    struct _PathsStruct {
      string base_filename(string fn_src) {
        return fn_src.baseName.stripExtension;
      }
      string base() {
        auto out_pth = spineOutPathSQLite!()(output_pth_root); // decide whether to have separate files for each language
        string base_dir = "sqlite";
        return (((out_pth.output_root).chainPath(base_dir)).asNormalizedPath).array;
      }
      string sqlite_file() {
        return (base.chainPath(db_name).asNormalizedPath).array;
      }
    }
    return _PathsStruct();
  }
}
#+END_SRC

*** TODO cgi search form

#+NAME: template_paths_sqlite_2
#+BEGIN_SRC d
template spinePathsSQLiteCGI() {
  mixin spineRgxIn;
  static auto rgx = RgxI();
  auto spinePathsSQLiteCGI()(
    string cgi_search_form_d,
    string cgi_search_form,
    string output_pth_root,
  ) {
    struct _PathsStruct {
      string base_filename(string fn_src) {
        return fn_src.baseName.stripExtension;
      }
      string base() {
        auto out_pth = spineOutPathSQLiteCGI!()(output_pth_root);
        string base_dir = "cgi";
        return (((out_pth.output_root).chainPath(base_dir)).asNormalizedPath).array;
      }
      string src() {
        return ((base.chainPath("src")).asNormalizedPath).array;
      }
      string cgi_bin() {
        return ((base.chainPath("cgi-bin")).asNormalizedPath).array;
      }
      string dub_sdl_path_out() {
        return ((base.chainPath("dub.sdl")).asNormalizedPath).array;
      }
      string search_form_dub_path() {
        return ("src/".chainPath(base_filename(cgi_search_form_d)).asNormalizedPath).array;
      }
      string search_form_path_out() {
        return (src.chainPath(cgi_search_form_d).asNormalizedPath).array;
      }
      string cgi_d_path_out() {
        return (src.chainPath("cgi.d").asNormalizedPath).array;
      }
    }
    return _PathsStruct();
  }
}
#+END_SRC

* __END__