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 +