diff --git a/lib/dtutils.lua b/lib/dtutils.lua
new file mode 100644
index 0000000..5b5986a
--- /dev/null
+++ b/lib/dtutils.lua
@@ -0,0 +1,218 @@
+local dtutils = {}
+
+dtutils.libdoc = {
+ Name = [[dtutils]],
+ Synopsis = [[A Darktable lua utilities library]],
+ Usage = [[local du = require "lib/dtutils"]],
+ Description = [[dtutils provides a common library of functions used to build
+ lua scripts. There are also sublibraries that provide more functions.]],
+ Return_Value = [[du - library - the library of functions]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[dtutils.debug(3), dtutils.file(3), dtutils.log(3), dtutils.string(3)]],
+ Reference = [[]],
+ License = [[This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .]],
+ Copyright = [[Copyright (C) 2016 Bill Ferguson .
+ Copyright (C) 2016 Tobias Jakobs]],
+ functions = {}
+}
+
+local dt = require "darktable"
+
+local log = require "lib/dtutils.log"
+
+dt.configuration.check_version(...,{3,0,0})
+
+dtutils.libdoc.functions["split"] = {
+ Name = [[split]],
+ Synopsis = [[split a string on a specified separator]],
+ Usage = [[local du = require "lib/dtutils"
+
+ local result = du.split(str, pat)
+ str - string - the string to split
+ pat - string - the pattern to split on]],
+ Description = [[split separates a string into a table of strings. The strings are separated at each
+ occurrence of the supplied pattern. The pattern may be any pattern as described in the lua docs.
+ Each match of the pattern is consumed and not returned.]],
+ Return_Value = [[result - a table of strings on success, or an empty table on error]],
+ Limitations = [[]],
+ Example = [[split("/a/long/path/name/to/a/file.txt", "/") would return a table like
+ {a, "long", "path", "name", "to", a, "file.txt"}]],
+ See_Also = [[]],
+ Reference = [[http://lua-users.org/wiki/SplitJoin]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils.split(str, pat)
+ local t = {}
+ local fpat = "(.-)" .. pat
+ local last_end = 1
+ local s, e, cap = str:find(fpat, 1)
+ while s do
+ if s ~= 1 or cap ~= "" then
+ table.insert(t,cap)
+ end
+ last_end = e+1
+ s, e, cap = str:find(fpat, last_end)
+ end
+ if last_end <= #str then
+ cap = str:sub(last_end)
+ table.insert(t, cap)
+ end
+ return t
+end
+
+dtutils.libdoc.functions["join"] = {
+ Name = [[join]],
+ Synopsis = [[join a table of strings with a specified separator]],
+ Usage = [[local du = require "lib/dtutils"
+
+ local result = du.join(tabl, pat)
+ tabl - a table of strings
+ pat - a separator]],
+ Description = [[join assembles a table of strings into a string with the specified pattern
+ in between each string]],
+ Return_Value = [[result - string - the joined string on success, or an empty string on failure]],
+ Limitations = [[]],
+ Example = [[join({a, "long", "path", "name", "to", a, "file.txt"}, " ") would return the string
+ "a long path name to a file.txt"]],
+ See_Also = [[]],
+ Reference = [[http://lua-users.org/wiki/SplitJoin]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils.join(tabl, pat)
+ returnstr = ""
+ for i,str in pairs(tabl) do
+ returnstr = returnstr .. str .. pat
+ end
+ return string.sub(returnstr, 1, -(pat:len() + 1))
+end
+
+--[[
+ NAME
+ prequire - a protected lua require
+
+ SYNOPSIS
+ local du = require "lib/dtutils"
+
+ local result = du.prequire(req_name)
+ req_name - the filename of the lua code to load without the ".lua" filetype
+
+ DESCRIPTION
+ prequire is a protected require that can survive an error in the code being loaded without
+ bringing down the calling routine.
+
+ RETURN VALUE
+ result - the code or true on success, otherwise an error message
+
+ EXAMPLE
+ prequire("lib/dtutils.file") which would load lib/dtutils/file.lua
+
+]]
+
+dtutils.libdoc.functions["prequire"] = {
+ Name = [[prequire]],
+ Synopsis = [[a protected lua require]],
+ Usage = [[local du = require "lib/dtutils"
+
+ local status, lib = du.prequire(req_name)
+ req_name - the filename of the lua code to load without the ".lua" filetype]],
+ Description = [[prequire is a protected require that can survive an error in the code being loaded without
+ bringing down the calling routine.]],
+ Return_Value = [[status - boolean - true on success
+ lib - if status is true, then the code, otherwise an error message]],
+ Limitations = [[]],
+ Example = [[local status, lib = prequire("lib/dtutils.file") which would load lib/dtutils/file.lua which
+ would return a status of true and the reference to the library in lib.]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils.prequire(req_name)
+ local status, lib = pcall(require, req_name)
+ if status then
+ log.msg(log.info, "Loaded " .. req_name)
+ else
+ log.msg(log.info, "Error loading " .. req_name)
+ end
+ return status, lib
+end
+
+dtutils.libdoc.functions["spairs"] = {
+ Name = [[spairs]],
+ Synopsis = [[an iterator that provides sorted pairs from a table]],
+ Usage = [[local du = require "lib/dtutils"
+
+ for key, value in du.spairs(t, order) do
+ t - table - table of key, value pairs
+ order - function - an optional function to sort the pairs
+ if none is supplied, table.sort() is used]],
+ Description = [[spairs is an iterator that returns key, value pairs from a table in sorted
+ order. The sorting order is the result of table.sort() if no function is
+ supplied, otherwise sorting is done as specified in the function.]],
+ Return_Value = [[]],
+ Limitations = [[]],
+ Example = [[HighScore = { Robin = 8, Jon = 10, Max = 11 }
+
+ -- basic usage, just sort by the keys
+ for k,v in spairs(HighScore) do
+ print(k,v)
+ end
+ --> Jon 10
+ --> Max 11
+ --> Robin 8
+
+ -- this uses an custom sorting function ordering by score descending
+ for k,v in spairs(HighScore, function(t,a,b) return t[b] < t[a] end) do
+ print(k,v)
+ end
+ --> Max 11
+ --> Jon 10
+ --> Robin 8]],
+ See_Also = [[]],
+ Reference = [[Code copied from http://stackoverflow.com/questions/15706270/sort-a-table-in-lua]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+-- Sort a table
+function dtutils.spairs(_table, order) -- Code copied from http://stackoverflow.com/questions/15706270/sort-a-table-in-lua
+ -- collect the keys
+ local keys = {}
+ for _key in pairs(_table) do keys[#keys + 1] = _key end
+
+ -- if order function given, sort by it by passing the table and keys a, b,
+ -- otherwise just sort the keys
+ if order then
+ table.sort(keys, function(a,b) return order(_table, a, b) end)
+ else
+ table.sort(keys)
+ end
+
+ -- return the iterator function
+ local i = 0
+ return function()
+ i = i + 1
+ if keys[i] then
+ return keys[i], _table[keys[i]]
+ end
+ end
+end
+
+return dtutils
diff --git a/lib/dtutils/debug.lua b/lib/dtutils/debug.lua
new file mode 100644
index 0000000..277aa8f
--- /dev/null
+++ b/lib/dtutils/debug.lua
@@ -0,0 +1,161 @@
+--[[
+This file is part of darktable,
+Copyright (c) 2014 Jérémy Rosen
+Copyright (c) 2016 Bill Ferguson
+
+darktable is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+darktable is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with darktable. If not, see .
+]]
+--[[
+DEBUG HELPERS
+A collection of helper functions to help debugging lua scripts.
+
+require it as
+
+dhelpers = require "lib/dtutils.debug"
+
+Each function is documented in its own header
+
+
+]]
+
+
+local dt = require "darktable"
+local io = require "io"
+local table = require "table"
+require "darktable.debug"
+local log = require "lib/dtutils.log"
+local M = {} -- The actual content of the module
+
+M.libdoc = {
+ Name = [[dtutils.debug]],
+ Synopsis = [[debugging helpers used in developing darktable lua scripts]],
+ Usage = [[local dd = require "lib/dtutils.debug"]],
+ Description = [[dtutils.debug provides an interface to the darktable debugging routines.]],
+ Return_Value = [[dd - library - the darktable lua debugging helpers]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = log.libdoc.License,
+ Copyright = [[Copyright (c) 2014 Jérémy Rosen
+Copyright (c) 2016 Bill Ferguson
+]],
+ functions = {}
+}
+
+M.libdoc.functions["tracepoint"] = {
+ Name = [[tracepoint]],
+ Synopsis = [[print out a tracepoint and dump the arguments]],
+ Usage = [[local dd = require "lib/dtutils.debug"
+
+ local result = tracepoint(name, ...)
+ name - string - the name of the tracepoint to print out
+ ... - arguments - variables to dump the contents of]],
+ Description = [[tracepoint prints its name and dumps its parameters using
+ dt.debug]],
+ Return_Value = [[result - ... - the supplied argument list]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function M.tracepoint(name,...)
+ log.msg(log.always, 4, "***** "..name.." ****")
+ params = {...}
+ log.msg(log.always, 0, dt.debug.dump(params,"parameters"))
+ return ...;
+end
+
+M.libdoc.functions["new_tracepoint"] = {
+ Name = [[new_tracepoint]],
+ Synopsis = [[create a function returning a tracepoint]],
+ Usage = [[local dd = require "lib/dtutils.debug"
+
+ local result = new_tracepoint(name, ...)
+ name - string - the name of the tracepoint to print out
+ ... - arguments - variables to dump the contents of]],
+ Description = [[A function that returns a tracepoint function with the given name
+ This is mainly used to debug callbacks.]],
+ Return_Value = [[result - function - a function that returns the result of a tracepoint]],
+ Limitations = [[]],
+ Example = [[register_event(event, dd.new_tracepoint("hit callback"))
+
+ will print the following each time the callback is called
+
+ **** hit callback ****
+ ]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function M.new_tracepoint(name)
+ return function(...) return M.tracepoint(name,...) end
+end
+
+
+M.libdoc.functions["dprint"] = {
+ Name = [[dprint]],
+ Synopsis = [[pass a variable to dt.debug.dump and print the results to stdout]],
+ Usage = [[local dd = require "lib/dtutils.debug"
+
+ dd.dprint(var)
+ var - variable - any variable that you want to see the contents of]],
+ Description = [[Wrapper around debug.dump, will directly print to stdout,
+ same calling convention]],
+ Return_Value = [[]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function M.dprint(...)
+ log.msg(log.always, 4, dt.debug.dump(...))
+end
+
+M.libdoc.functions["terse_dump"] = {
+ Name = [[terse_dump]],
+ Synopsis = [[set dt.debug.known to shorten all image dumps to a single line]],
+ Usage = [[local dd = require "lib/dtutils.debug"
+
+ dd.terse_dump()]],
+ Description = [[terse_dump sets dt.debug.known to shorten all images to a single line.
+ If you don't need to debug the content of images, this will avoid them flooding your logs]],
+ Return_Value = [[]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function M.terse_dump()
+ for _,v in ipairs(dt.database) do
+ dt.debug.known[v] = tostring(v)
+ end
+end
+
+
+
+return M
+-- vim: shiftwidth=2 expandtab tabstop=2 cindent
+-- kate: tab-indents: off; indent-width 2; replace-tabs on; remove-trailing-space on;
diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua
new file mode 100644
index 0000000..fd20e46
--- /dev/null
+++ b/lib/dtutils/file.lua
@@ -0,0 +1,394 @@
+local dtutils_file = {}
+local dt = require "darktable"
+
+local log = require "lib/dtutils.log"
+
+dtutils_file.libdoc = {
+ Name = [[dtutils.file]],
+ Synopsis = [[common darktable lua file functions]],
+ Usage = [[local df = require "lib/dtutils.file"]],
+ Description = [[[dtutils.file provides common file manipulation functions used in
+ constructing Darktable lua scripts]],
+ Return_Value = [[df - library - the file functions]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = log.libdoc.License,
+ Copyright = [[Copyright (C) 2016 Bill Ferguson .]],
+ Copyright = [[Copyright (C) 2016 Bill Ferguson .
+ Copyright (C) 2016 Tobias Jakobs]],
+ functions = {}
+}
+
+local gettext = dt.gettext
+
+dt.configuration.check_version(...,{3,0,0})
+
+-- Tell gettext where to find the .mo file translating messages for a particular domain
+gettext.bindtextdomain("dtutils.file",dt.configuration.config_dir.."/lua/locale/")
+
+local function _(msgid)
+ return gettext.dgettext("dtutils.file", msgid)
+end
+
+dtutils_file.libdoc.functions["check_if_bin_exists"] = {
+ Name = [[check_if_bin_exists]],
+ Synopsis = [[check if an executable is in the path]],
+ Usage = [[local df = require "lib/dtutils.file"
+
+ local result = df.check_if_bin_exists(bin)
+ bin - string - the binary to check for]],
+ Description = [[check_if_bin_exists checks to see if the specified binary executable is
+ in the path.]],
+ Return_Value = [[result - boolean - true if the executable was found, false if not]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_file.check_if_bin_exists(bin)
+ local result = os.execute("which " .. bin)
+ if not result then
+ result = false
+ end
+ return result
+end
+
+dtutils_file.libdoc.functions["split_filepath"] = {
+ Name = [[split_filepath]],
+ Synopsis = [[split a filepath into parts]],
+ Usage = [[local df = require "lib/dtutils.file"
+
+ local result = df.split_filepath(filepath)
+ filepath - string - path and filename]],
+ Description = [[split_filepath splits a filepath into the path, filename, basename and filetype and puts
+ that in a table]],
+ Return_Value = [[result - table - a table containing the path, filename, basename, and filetype]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_file.split_filepath(str)
+ -- strip out single quotes from quoted pathnames
+ str = string.gsub(str, "'", "")
+ local result = {}
+ -- Thank you Tobias Jakobs for the awesome regular expression, which I tweaked a little
+ result["path"], result["filename"], result["basename"], result["filetype"] = string.match(str, "(.-)(([^\\/]-)%.?([^%.\\/]*))$")
+ return result
+end
+
+dtutils_file.libdoc.functions["get_path"] = {
+ Name = [[get_path]],
+ Synopsis = [[get the path from a file path]],
+ Usage = [[local df = require "lib/dtutils.file"
+
+ local result = df.get_path(filepath)
+ filepath - string - path and filename]],
+ Description = [[get_path strips the filename and filetype from a path and returns the path]],
+ Return_Value = [[result - string - the path]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_file.get_path(str)
+ local parts = dtutils_file.split_filepath(str)
+ return parts["path"]
+end
+
+dtutils_file.libdoc.functions["get_filename"] = {
+ Name = [[get_filename]],
+ Synopsis = [[get the filename and extension from a file path]],
+ Usage = [[local df = require "lib/dtutils.file"
+
+ local result = df.get_filename(filepath)
+ filepath - string - path and filename]],
+ Description = [[get_filename strips the path from a filepath and returns the filename]],
+ Return_Value = [[result - string - the file name and type]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_file.get_filename(str)
+ local parts = dtutils_file.split_filepath(str)
+ return parts["filename"]
+end
+
+dtutils_file.libdoc.functions["get_basename"] = {
+ Name = [[get_basename]],
+ Synopsis = [[get the filename without the path or extension]],
+ Usage = [[local df = require "lib/dtutils.file"
+
+ local result = df.get_basename(filepath)
+ filepath - string - path and filename]],
+ Description = [[get_basename returns the name of the file without the path or filetype
+]],
+ Return_Value = [[result - string - the basename of the file]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_file.get_basename(str)
+ local parts = dtutils_file.split_filepath(str)
+ return parts["basename"]
+end
+
+dtutils_file.libdoc.functions["get_filetype"] = {
+ Name = [[get_filetype]],
+ Synopsis = [[get the filetype from a filename]],
+ Usage = [[local df = require "lib/dtutils.file"
+
+ local result = df.get_filetype(filepath)
+ filepath - string - path and filename]],
+ Description = [[get_filetype returns the filetype from the supplied filepath]],
+ Return_Value = [[result - string - the filetype]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_file.get_filetype(str)
+ local parts = dtutils_file.split_filepath(str)
+ return parts["filetype"]
+end
+
+
+-- Thanks Tobias Jakobs for the idea
+dtutils_file.libdoc.functions["check_if_file_exists"] = {
+ Name = [[check_if_file_exists]],
+ Synopsis = [[check if a file or path exist]],
+ Usage = [[local df = require "lib/dtutils.file"
+
+ local result = df.check_if_file_exists(filepath)
+ filepath - string - a file or path to check]],
+ Description = [[check_if_file_exists checks to see if a file or path exists]],
+ Return_Value = [[result - boolean - true if the file or path exists, false if it doesn't]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_file.check_if_file_exists(filepath)
+ local result = os.execute("test -e " .. filepath)
+ if not result then
+ result = false
+ end
+ return result
+end
+
+dtutils_file.libdoc.functions["chop_filetype"] = {
+ Name = [[chop_filetype]],
+ Synopsis = [[remove a filetype from a filename]],
+ Usage = [[local df = require "lib/dtutils.file"
+
+ local result = df.chop_filetype(path)
+ path - string - a filename with or without a path]],
+ Description = [[chop_filetype removes the filetype from the filename]],
+ Return_Value = [[result - string - the path and filename without the filetype]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_file.chop_filetype(path)
+ local length = dtutils_file.get_filetype(path):len() + 2
+ return string.sub(path, 1, -length)
+end
+
+dtutils_file.libdoc.functions["file_copy"] = {
+ Name = [[file_copy]],
+ Synopsis = [[copy a file to another name/location]],
+ Usage = [[local df = require "lib/dtutils.file"
+
+ local result = df.file_copy(fromFile, toFile)
+ fromFile - string - name of file to copy from
+ toFile - string - name of file to copy to]],
+ Description = [[copy a file using a succession of methods from operating system
+ to a pure lua solution]],
+ Return_Value = [[result - boolean - nil on error, true on success]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_file.file_copy(fromFile, toFile)
+ local result = nil
+ -- if cp exists, use it
+ if dtutils_file.check_if_bin_exists("cp") then
+ result = os.execute("cp '" .. fromFile .. "' '" .. toFile .. "'")
+ end
+ -- if cp was not present, or if cp failed, then a pure lua solution
+ if not result then
+ local fileIn, err = io.open(fromFile, 'rb')
+ if fileIn then
+ local fileOut, errr = io.open(toFile, 'w')
+ if fileOut then
+ local content = fileIn:read(4096)
+ while content do
+ fileOut:write(content)
+ content = fileIn:read(4096)
+ end
+ result = true
+ fileIn:close()
+ fileOut:close()
+ else
+ log.msg(log.error, errr)
+ end
+ else
+ log.msg(log.error, err)
+ end
+ end
+ return result
+end
+
+dtutils_file.libdoc.functions["file_move"] = {
+ Name = [[file_move]],
+ Synopsis = [[move a file from one directory to another]],
+ Usage = [[local df = require "lib/dtutils.file"
+
+ local result = df.file_move(fromFile, toFile)
+ fromFile - string - name of the original file
+ toFile - string - the new file location and name]],
+ Description = [[Move a file from one place to another. Try a succession of methods from
+ builtin to operating system to a pure lua solution.]],
+ Return_Value = [[result - boolean - nil on error, some value on success]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_file.file_move(fromFile, toFile)
+ local success = os.rename(fromFile, toFile)
+ if not success then
+ -- an error occurred, so let's try using the operating system function
+ if dtutils_file.check_if_bin_exists("mv") then
+ success = os.execute("mv '" .. fromFile .. "' '" .. toFile .. "'")
+ end
+ -- if the mv didn't exist or succeed, then...
+ if not success then
+ -- pure lua solution
+ success = dtutils_file.file_copy(fromFile, toFile)
+ if success then
+ os.remove(fromFile)
+ else
+ log.msg(log.error, "Unable to move " .. fromFile .. " to " .. toFile .. ". Leaving " .. fromFile .. " in place.")
+ end
+ end
+ end
+ return success -- nil on error, some value if success
+end
+
+dtutils_file.libdoc.functions["filename_increment"] = {
+ Name = [[filename_increment]],
+ Synopsis = [[add a two digit increment to a filename]],
+ Usage = [[local df = require "lib/dtutils.file"
+
+ local result = df.filename_increment(filepath)
+ filepath - string - filename to increment]],
+ Description = [[filename_increment solves the problem of filename confllict by adding an
+ increment to the filename. If the supplied filename has no increment then
+ "01" is added to the basename. If the filename already has an increment, then
+ 1 is added to it and the filename returned.]],
+ Return_Value = [[result - string - the incremented filename]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_file.filename_increment(filepath)
+
+ -- break up the filepath into parts
+ local path = dtutils_file.get_path(filepath)
+ local basename = dtutils_file.get_basename(filepath)
+ local filetype = dtutils_file.get_filetype(filepath)
+
+ -- check to see if we've incremented before
+ local increment = string.match(basename, "_(%d-)$")
+
+ if increment then
+ -- we do 2 digit increments so make sure we didn't grab part of the filename
+ if string.len(increment) > 2 then
+ -- we got the filename so set the increment to 01
+ increment = "01"
+ else
+ increment = string.format("%02d", tonumber(increment) + 1)
+ basename = string.gsub(basename, "_(%d-)$", "")
+ end
+ else
+ increment = "01"
+ end
+ local incremented_filepath = path .. basename .. "_" .. increment .. "." .. filetype
+
+ return incremented_filepath
+end
+
+
+dtutils_file.libdoc.functions["create_unique_filename"] = {
+ Name = [[create_unique_filename]],
+ Synopsis = [[create a unique filename from the supplied argment]],
+ Usage = [[local df = require "lib/dtutils.file"
+
+ local result = df.create_unique_filename(filepath)
+ filepath - string - the path and filename requested]],
+ Description = [[create_unique_filename takes a requested filepath and checks to see if
+ it exists. If if doesn't then it's returned intact. If it already exists, then a two
+ digit increment is added to the filename and it is tested again. The increment keeps
+ increasing until either a unique filename is found or there have been 100 attempts.]],
+ Return_Value = [[result - string - the incremented filename]],
+ Limitations = [[create_unique_filename will only attempt 100 increments.]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_file.create_unique_filename(filepath)
+ while dtutils_file.check_if_file_exists(filepath) do
+ filepath = dtutils_file.filename_increment(filepath)
+ -- limit to 99 more exports of the original export
+ if string.match(dtfileutils.get_basename(filepath), "_(d-)$") == "99" then
+ break
+ end
+ end
+ return filepath
+end
+
+return dtutils_file
diff --git a/lib/dtutils/log.lua b/lib/dtutils/log.lua
new file mode 100644
index 0000000..dcdc509
--- /dev/null
+++ b/lib/dtutils/log.lua
@@ -0,0 +1,291 @@
+
+local dtutils_log = {}
+
+dtutils_log.libdoc = {
+ Name = [[dtutils.log]],
+ Synopsis = [[darktable lua logging library]],
+ Usage = [[local log = require "lib/dtutils.log"]],
+ Description = [[log provides a multi-level logging solution for use with
+ the darktable lua scripts. With this library you can leave log messages
+ scattered through out your code and only turn them on as necessary.]],
+ Return_Value = [[log - library - the darktable lua logging functions]],
+ Limitations = [[]],
+ Example = [[local log = require "lib/dtutils.log"
+
+ local cur_level = log.log_level()
+ log.log_level(log.warn)
+
+ print out warning, error and success messages as code is running
+
+ log.log_level(debug)
+
+ print out debugging messages too because this isnt working
+
+ log.log_level(info)
+
+ I want to make sure this is working ok
+
+ log.log_level(cur_level)
+
+ reset the logging level back to normal]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .]],
+ Copyright = [[Copyright (C) 2016 Bill Ferguson .]],
+ functions = {}
+}
+
+local dt = require "darktable"
+local dt_print_error = dt.print_error
+-- work around until dt.print_log()
+local dt_print_log = dt.print_error
+local dt_print = dt.print
+
+-- set the default log levels
+
+dtutils_log.debug = {
+ label = "DEBUG:",
+ enabled = false,
+ engine = dt_print_log,
+ level = 1,
+}
+dtutils_log.info = {
+ label = "INFO:",
+ enable = false,
+ engine = dt_print_log,
+ level = 2,
+}
+dtutils_log.warn = {
+ label = "WARN:",
+ enabled = false,
+ engine = dt_print_log,
+ level = 3,
+}
+dtutils_log.error = {
+ label = "ERROR:",
+ enabled = true,
+ engine = dt_print_error,
+ level = 4,
+}
+dtutils_log.success = {
+ label = "SUCCESS:",
+ enabled = true,
+ engine = dt_print_log,
+ level = 5,
+}
+dtutils_log.screen = {
+ label = "",
+ enabled = true,
+ engine = dt_print,
+ level = 9,
+}
+dtutils_log.always = {
+ label = "",
+ enabled = true,
+ engine = dt_print_log,
+ level = 9,
+}
+dtutils_log.critical = {
+ label = "CRITICAL:",
+ enabled = true,
+ engine = print,
+ level = 9,
+}
+
+dtutils_log.libdoc.functions["caller"] = {
+ Name = [[caller]],
+ Synopsis = [[get the name and line number of the calling routine]],
+ Usage = [[local log = require "lib/dtutils.log"
+
+result = log.caller(level)
+ level - number - the number of stack levels to go down to retrieve the caller routine information]],
+ Description = [[caller gets the name and line number of the calling routine and returns it]],
+ Return_Value = [[result - string - the name and line number of the calling function or 'callback: ' if the attempt to get the
+ caller returns nil]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_log.caller(level)
+ local name = debug.getinfo(level).name
+ local lineno = nil
+ local source = nil
+ if name then
+ lineno = debug.getinfo(level).currentline
+ -- returns the path to the file prefixed with @
+ source = debug.getinfo(level).source
+ -- we just need the filename, so grab it from the string
+ -- Thanks, Tobias Jakobs :-)
+ source = string.match(source, "@.-([^\\/]-%.?[^%.\\/]*)$")
+ return name .. ": " .. source .. ": " .. lineno .. ":"
+ else
+ return "callback:"
+ end
+end
+
+dtutils_log.libdoc.functions["msg"] = {
+ Name = [[msg]],
+ Synopsis = [[print a log message]],
+ Usage = [[local log = require "lib/log"
+
+ log.msg(level, ...)
+ level - table - the type of message, one of:
+ log.debug - debugging messages
+ log.info - informational messages
+ log.warn - warning messages
+ log.error - error messages
+ log.success - success messages
+ log.always - an internal message for debugging
+ log.screen - output 1 line of text to the screen
+ log.critical - print a critical message to the console
+
+ ... - string(s) - the message to print, which could be a comma separated set of strings]],
+ Description = [[msg checks the level to see if it is enabled, then prints the level type and message if it is.
+ Messages are output using the engine configured in each log level.]],
+ Return_Value = [[]],
+ Limitations = [[If you use log.msg in a callback, the name of the calling routine can't be determined. A solution
+ is to include some means of reference such as the name of the callback as an argument, i.e.
+
+ log.msg(log.debug, "libPlugin.format_combobox:", "value is " .. self.value)
+
+ which would result in
+
+ DEBUG: callback: libPlugin.format_combobox: value is JPEG]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_log.msg(level, ...)
+ if level.enabled then
+ local args = {...}
+ local call_level = 3
+ if level == dtutils_log.always then
+ call_level = args[1]
+ table.remove(args, 1)
+ end
+ local log_msg = level.label
+ if level.engine ~= dt_screen and call_level ~= 0 then
+ log_msg = log_msg .. dtutils_log.caller(call_level) .. " "
+ elseif log_msg:len() > 2 then
+ log_msg = log_msg .. " "
+ end
+ for i = 1,#args do
+ log_msg = log_msg .. tostring(args[i]) .. " "
+ end
+ level.engine(log_msg)
+ end
+end
+
+dtutils_log.libdoc.functions["log_level"] = {
+ Name = [[log_level]],
+ Synopsis = [[get or set the log level]],
+ Usage = [[local log = require "lib/log"
+
+ local result = log.log_level(...)
+ ... - arguments - if none is supplied, then the current log level is returned as one of:
+ log.debug, log.info, log.warn, log.error, log.success. If one of log.debug, log.info, log.warn,
+ log.error, or log.success is supplied as the argument then the log level is set to that value. All
+ log levels greater than or equal in value will be enabled. Any levels of lesser value will be disabled.]],
+ Description = [[log_level gets and sets the logging level. When called with no arguments the current log level
+ is returned as one of log.debug, log.info, log.warn, log.error, or log.success. When called with one of log.debug,
+ log.info, log.warn, log.error or log.success then the log level is set. When setting the log level all levels
+ equal or greater are enabled and any of lesser value are disabled. See the example.]],
+ Return_Value = [[result - the log level, one of log.debug, log.info, log.warn, log.error or log.success]],
+ Limitations = [[]],
+ Example = [[Assume that the current log level is log.error. Calling log.log_level() will return log.error.
+ Calling log.log_level(log.info) will leave log.debug disabled, and enable log.info, log.warn, log.error and
+ log.success. log.info will be returned as the log_level.]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_log.log_level(...)
+ local levels = {"debug", "info", "warn", "error", "success"}
+ local args = {...}
+ local log_level = nil
+ if #args > 0 then
+ log_level = args[1]
+ if log_level == dtutils_log.critical or
+ log_level == dtutils_log.screen or
+ log_level == dtutils_log.always then
+ -- these aren't valid for setting levels
+ return nil
+ else
+ for _,v in ipairs(levels) do
+ if dtutils_log[v].level >= log_level.level then
+ dtutils_log[v].enabled = true
+ else
+ dtutils_log[v].enabled = false
+ end
+ end
+ end
+ else
+ for _,v in ipairs(levels) do
+ if dtutils_log[v].enabled == true then
+ log_level = dtutils_log[v]
+ end
+ end
+ end
+ return log_level
+end
+
+dtutils_log.libdoc.functions["engine"] = {
+ Name = [[engine]],
+ Synopsis = [[get and set the output engine]],
+ Usage = [[local log = require "lib/dtutils.log"
+
+result = log.engine(level, ...)
+ level - table - the log level to get or set the engine for, one of log.debug, log.info, log.warn, log.error
+ log.success, log.always, log.screen, log.critical
+ ... - function - the output function, one of dt.print, dt.print_error, dt.print_log, print
+ if not function is included, the current engine is returned for the specified log level]],
+ Description = [[engine returns the output engine for the specified log level if a second argument is not
+ supplied. If a function is supplied as the second argment, then the output engine for the specified log level
+ is set to that.]],
+ Return_Value = [[result - function - the current output engine]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_log.print_engine(level, ...)
+ local engines = {"dt_print", "dt_print_error", "dt_print_log", "print"}
+ local args = {...}
+ local cur_engine = ""
+ if #args == 0 then
+ cur_engine = level.engine
+ else
+ for _,v in ipairs(engines) do
+ if args[1] == v then
+ level.engine = args[1]
+ cur_engine = level.engine
+ end
+ end
+ end
+ return cur_engine
+end
+
+return dtutils_log
diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua
new file mode 100644
index 0000000..db04423
--- /dev/null
+++ b/lib/dtutils/string.lua
@@ -0,0 +1,173 @@
+local dtutils_string = {}
+dtutils_string.libdoc = {
+ Name = [[dtutils.string]],
+ Synopsis = [[a library of string utilities for use in darktable lua scripts]],
+ Usage = [[local ds = require "lib/dtutils.string"]],
+ Description = [[This library contains string manipulation routines to aid in building
+ darktable lua scripts.]],
+ Return_Value = [[du - library - the darktable lua string library]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[]],
+ License = [[This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .]],
+ Copyright = [[Copyright (c) 2016 Bill Ferguson ]],
+ functions = {}
+}
+
+dtutils_string.libdoc.functions["strip_accents"] = {
+ Name = [[strip_accents]],
+ Synopsis = [[strip accents from characters]],
+ Usage = [[local ds = require "lib/dtutils.string"
+
+ local result = ds.strip_accents(str)
+ str - string - the string with characters that need accents removed]],
+ Description = [[strip_accents removes accents from accented characters returning the
+ unaccented character.]],
+ Return_Value = [[result - string - the string containing unaccented characters]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[Copied from https://forums.coronalabs.com/topic/43048-remove-special-characters-from-string/]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_string.strip_accents( str )
+ local tableAccents = {}
+ tableAccents["à"] = "a"
+ tableAccents["á"] = "a"
+ tableAccents["â"] = "a"
+ tableAccents["ã"] = "a"
+ tableAccents["ä"] = "a"
+ tableAccents["ç"] = "c"
+ tableAccents["è"] = "e"
+ tableAccents["é"] = "e"
+ tableAccents["ê"] = "e"
+ tableAccents["ë"] = "e"
+ tableAccents["ì"] = "i"
+ tableAccents["í"] = "i"
+ tableAccents["î"] = "i"
+ tableAccents["ï"] = "i"
+ tableAccents["ñ"] = "n"
+ tableAccents["ò"] = "o"
+ tableAccents["ó"] = "o"
+ tableAccents["ô"] = "o"
+ tableAccents["õ"] = "o"
+ tableAccents["ö"] = "o"
+ tableAccents["ù"] = "u"
+ tableAccents["ú"] = "u"
+ tableAccents["û"] = "u"
+ tableAccents["ü"] = "u"
+ tableAccents["ý"] = "y"
+ tableAccents["ÿ"] = "y"
+ tableAccents["À"] = "A"
+ tableAccents["Á"] = "A"
+ tableAccents["Â"] = "A"
+ tableAccents["Ã"] = "A"
+ tableAccents["Ä"] = "A"
+ tableAccents["Ç"] = "C"
+ tableAccents["È"] = "E"
+ tableAccents["É"] = "E"
+ tableAccents["Ê"] = "E"
+ tableAccents["Ë"] = "E"
+ tableAccents["Ì"] = "I"
+ tableAccents["Í"] = "I"
+ tableAccents["Î"] = "I"
+ tableAccents["Ï"] = "I"
+ tableAccents["Ñ"] = "N"
+ tableAccents["Ò"] = "O"
+ tableAccents["Ó"] = "O"
+ tableAccents["Ô"] = "O"
+ tableAccents["Õ"] = "O"
+ tableAccents["Ö"] = "O"
+ tableAccents["Ù"] = "U"
+ tableAccents["Ú"] = "U"
+ tableAccents["Û"] = "U"
+ tableAccents["Ü"] = "U"
+ tableAccents["Ý"] = "Y"
+
+ local normalizedString = ""
+
+ for strChar in string.gmatch(str, "([%z\1-\127\194-\244][\128-\191]*)") do
+ if tableAccents[strChar] ~= nil then
+ normalizedString = normalizedString..tableAccents[strChar]
+ else
+ normalizedString = normalizedString..strChar
+ end
+ end
+
+ return normalizedString
+
+end
+
+dtutils_string.libdoc.functions["escape_xml_characters"] = {
+ Name = [[escape_xml_characters]],
+ Synopsis = [[escape characters for xml documents]],
+ Usage = [[local ds = require "lib/dtutils.string"
+
+ local result = ds.escape_xml_characters(str)
+ str - string - the string that needs escaped]],
+ Description = [[escape_xml_characters provides the escape sequences for
+ "&", '"', "'", "<", and ">" with the corresponding "&",
+ """, "'", "<", and ">".]],
+ Return_Value = [[result - string - the string containing escapes for the xml characters]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[https://stackoverflow.com/questions/1091945/what-characters-do-i-need-to-escape-in-xml-documents]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+-- Keep & first, otherwise it will double escape other characters
+function dtutils_string.escape_xml_characters( str )
+
+ str = string.gsub(str,"&", "&")
+ str = string.gsub(str,"\"", """)
+ str = string.gsub(str,"'", "'")
+ str = string.gsub(str,"<", "<")
+ str = string.gsub(str,">", ">")
+
+ return str
+end
+
+dtutils_string.libdoc.functions["urlencode"] = {
+ Name = [[urlencode]],
+ Synopsis = [[encode a string in a websage manner]],
+ Usage = [[local ds = require "lib/dtutils.string"
+
+ local result = ds.urlencode(str)
+ str - string - the string that needs to be made websafe]],
+ Description = [[urlencode converts a string into a websafe version suitable for
+ use in a web browser.]],
+ Return_Value = [[result - string - a websafe string]],
+ Limitations = [[]],
+ Example = [[]],
+ See_Also = [[]],
+ Reference = [[https://forums.coronalabs.com/topic/43048-remove-special-characters-from-string/]],
+ License = [[]],
+ Copyright = [[]],
+}
+
+function dtutils_string.urlencode(str)
+ if (str) then
+ str = string.gsub (str, "\n", "\r\n")
+ str = string.gsub (str, "([^%w ])", function () return string.format ("%%%02X", string.byte()) end)
+ str = string.gsub (str, " ", "+")
+ end
+ return str
+end
+
+return dtutils_string
diff --git a/official/debug-helpers.lua b/official/debug-helpers.lua
deleted file mode 100644
index 568a678..0000000
--- a/official/debug-helpers.lua
+++ /dev/null
@@ -1,93 +0,0 @@
---[[
-This file is part of darktable,
-copyright (c) 2014 Jérémy Rosen
-
-darktable is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-darktable is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with darktable. If not, see .
-]]
---[[
-DEBUG HELPERS
-A collection of helper functions to help debugging lua scripts.
-
-require it as
-
-dhelpers = require "official/debug-helpers"
-
-Each function is documented in its own header
-
-
-]]
-
-
-local dt = require "darktable"
-local io = require "io"
-local table = require "table"
-require "darktable.debug"
-local M = {} -- The actual content of the module
-
---[[
-A function which prints its name and its parameters
-* name : the name of the tracepoint
-* ... : anything, will be dumped using dt.debug
-]]
-function M.tracepoint(name,...)
- print("***** "..name.." ****")
- params = {...}
- print(dt.debug.dump(params,"parameters"))
- return ...;
-end
-
-
-
---[[
-A function that returns a tracepoint function with the given name
-This is mainly used to debug callbacks,
-
-register_event(event, dhelpers.new_tracepoint("hit callback"))
-
-will print the following each time the callback is called
-
-**** hit callback ****
-
-
-]]
-function M.new_tracepoint(name)
- return function(...) return M.tracepoint(name,...) end
-end
-
-
---[[
-Wrapper around debug.dump, will directly print to stdout,
-same calling covention
-]]
-
-function M.dprint(...)
- print(dt.debug.dump(...))
-end
-
---[[
-sets dt.debug.known to shorten all images to a single line
-
-if you don't need to debug the content of images, this will avoid them flooding your logs
-]]
-function M.terse_dump()
- for _,v in ipairs(dt.database) do
- dt.debug.known[v] = tostring(v)
- end
-end
-
-
-
-return M
--- vim: shiftwidth=2 expandtab tabstop=2 cindent
--- kate: tab-indents: off; indent-width 2; replace-tabs on; remove-trailing-space on;
diff --git a/tools/get_lib_manpages.lua b/tools/get_lib_manpages.lua
new file mode 100644
index 0000000..23400aa
--- /dev/null
+++ b/tools/get_lib_manpages.lua
@@ -0,0 +1,76 @@
+--[[
+ get_lib_manpages.lua - retrieve the included library documentation and output it as man pages
+
+ Copyright (c) 2016, Bill Ferguson
+]]
+
+local dt = require "darktable"
+local du = require "lib/dtutils"
+local df = require "lib/dtutils.file"
+local log = require "lib/dtutils.log"
+local libname = nil
+
+dt.configuration.check_version(...,{3,0,0})
+
+local keys = {"Name", "Synopsis", "Usage", "Description", "Return_Value", "Limitations",
+ "Example", "See_Also", "Reference", "License", "Copyright"}
+
+local function output_man(d)
+ local name = d["Name"]
+ if not libname then
+ libname = name
+ end
+ local fname = "/tmp/" .. name .. ".3"
+ local mf = io.open(fname, "w")
+ if mf then
+ mf:write(".TH " .. string.upper(name) .. " 3 \"\" \"\" \"Darktable " .. libname .. " functions\"\n")
+ for _,section in ipairs(keys) do
+ if d[section]:len() > 0 then
+ mf:write(".SH " .. string.upper(string.gsub(section, "_", " ")) .. "\n")
+ mf:write(d[section] .. "\n")
+ end
+ end
+ mf:close()
+ if df.check_if_bin_exists("groff") then
+ if df.check_if_bin_exists("ps2pdf") then
+ os.execute("groff -man " .. fname .. " | ps2pdf - " .. fname .. ".pdf")
+ else
+ log.msg(log.error, "Missing ps2pdf. Can't generate pdf man pages.")
+ end
+ else
+ log.msg(log.error, "Missing groff. Can't generate pdf man pages.")
+ end
+ else
+ log.msg(log.error, "Can't open file " .. fname .. "for writing")
+ end
+end
+
+-- find the libraries
+
+local output = io.popen("cd "..dt.configuration.config_dir.."/lua/lib ;find . -name \\*.lua -print | sort")
+
+-- loop through the libraries
+
+for line in output:lines() do
+ line = string.gsub(line, "/", ".")
+ local lib_name = line:sub(3,-5)
+ if lib_name:len() > 2 then
+ lib_name = "lib/" .. lib_name
+ local lib = require(lib_name)
+
+ -- print the documentation for the library
+ if lib.libdoc then
+ local doc = lib.libdoc
+ if doc then
+ output_man(doc)
+ for _,fdoc in pairs(doc.functions) do
+
+ -- print the documentation for each of the functions
+ output_man(fdoc)
+ end
+ end
+ end
+ end
+ libname = nil
+end
+
diff --git a/tools/get_libdoc.lua b/tools/get_libdoc.lua
new file mode 100644
index 0000000..de6615d
--- /dev/null
+++ b/tools/get_libdoc.lua
@@ -0,0 +1,49 @@
+--[[
+ get_libdoc.lua - retrieve the included library documentation and output it
+
+ Copyright (c) 2016, Bill Ferguson
+]]
+
+local dt = require "darktable"
+
+dt.configuration.check_version(...,{3,0,0})
+
+local keys = {"Name", "Synopsis", "Usage", "Description", "Return_Value", "Limitations",
+ "Example", "See_Also", "Reference", "License", "Copyright"}
+
+local function output_doc(d)
+ for _,section in ipairs(keys) do
+ if d[section]:len() > 0 then
+ print(string.upper(string.gsub(section, "_", " ")))
+ print("\t" .. d[section] .. "\n")
+ end
+ end
+ print("\f")
+end
+
+-- find the libraries
+
+local output = io.popen("cd "..dt.configuration.config_dir.."/lua/lib ;find . -name \\*.lua -print | sort")
+
+-- loop through the libraries
+
+for line in output:lines() do
+ line = string.gsub(line, "/", ".")
+ local lib_name = line:sub(3,-5)
+ if lib_name:len() > 2 then
+ lib_name = "lib/" .. lib_name
+ local lib = require(lib_name)
+
+ -- print the documentation for the library
+ if lib.libdoc then
+ local doc = lib.libdoc
+ output_doc(doc)
+ for _,fdoc in pairs(doc.functions) do
+
+ -- print the documentation for each of the functions
+ output_doc(fdoc)
+ end
+ end
+ end
+end
+