diff --git a/lib/dtutils.lua b/lib/dtutils.lua new file mode 100644 index 0000000..db2be0a --- /dev/null +++ b/lib/dtutils.lua @@ -0,0 +1,457 @@ +--[[ + + dtutils.lua - common darktable lua functions + + Copyright (C) 2016 Bill Ferguson . + Copyright (C) 2016 Tobias Jakobs + + 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 . +]] + +local dtutils = {} + +dtutils.libdoc = { + Sections = {"Name", "Synopsis", "Description", "See_Also", "License"}, + Name = [[dtutils - A Darktable lua utilities library]], + Synopsis = [[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.]], + See_Also = [[dtutils.debug(3), dtutils.extensionts(3), dtutils.file(3), dtutils.processor(3)]], + 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 .]], + functions = {} +} + +local dt = require "darktable" + +local log = require "lib/libLog" + +dt.configuration.check_version(...,{3,0,0}) + +--[[ + NAME + split - split a string on a specified separator + + SYNOPSIS + 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. + + RETURN VALUE + result - a table of strings on success, or an empty table on error + + EXAMPLE + split("/a/long/path/name/to/a/file.txt", "/") would return a table like + {"a", "long", "path", "name", "to", "a", "file.txt"} + +]] + +dtutils.libdoc.functions[#dtutils.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value", "Example"}, + Name = [[split - split a string on a specified separator]], + Synopsis = [[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.]], + Return_Value = [[result - a table of strings on success, or an empty table on error]], + Example = [[split("/a/long/path/name/to/a/file.txt", "/") would return a table like + {"a", "long", "path", "name", "to", "a", "file.txt"}]], +} + +-- Thanks to http://lua-users.org/wiki/SplitJoin +function dtutils.split(str, pat) + local t = {} -- NOTE: use {n = 0} in Lua-5.0 + 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 + +--[[ + NAME + join - join a table of strings with a specified separator + + SYNOPSIS + 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 - the joined string on success, or an empty string on failure + + EXAMPLE + join({"a", "long", "path", "name", "to", "a", "file.txt"}, " ") would return the string + "a long path name to a file.txt" + +]] + +dtutils.libdoc.functions[#dtutils.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value", "Example"}, + Name = [[join - join a table of strings with a specified separator]], + Synopsis = [[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 - the joined string on success, or an empty string on failure]], + Example = [[join({"a", "long", "path", "name", "to", "a", "file.txt"}, " ") would return the string + "a long path name to a file.txt"]], +} + +-- Thanks to http://lua-users.org/wiki/SplitJoin +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[#dtutils.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value", "Example"}, + 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]], +} + +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 lib +end + +--[[ + NAME + push - push a value on a stack + + SYNOPSIS + local du = require "lib/dtutils" + + du.push(stack, value) + stack - table - a table being used as the stack + value - any type - the value to be put on the stack + + DESCRIPTION + Push a value on a stack + + RETURN VALUE + none + +]] + +dtutils.libdoc.functions[#dtutils.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[push - push a value on a stack]], + Synopsis = [[local du = require "lib/dtutils" + + du.push(stack, value) + stack - table - a table being used as the stack + value - any type - the value to be put on the stack]], + Description = [[Push a value on a stack]], + Return_Value = [[none]], +} + +dtutils.push = table.insert + +--[[ + NAME + pop - pop a value from a stack + + SYNOPSIS + local du = require "lib/dtutils" + + local result = du.pop(stack) + stack - table - a table being used as a stack + + DESCRIPTION + Remove the last value pushed on the stack and return it + + RETURN VALUE + result = nil if stack isn't a table or the stack is empty, otherwise the value removed from the stack + +]] + +dtutils.libdoc.functions[#dtutils.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[pop - pop a value from a stack]], + Synopsis = [[local du = require "lib/dtutils" + + local result = du.pop(stack) + stack - table - a table being used as a stack]], + Description = [[Remove the last value pushed on the stack and return it]], + Return_Value = [[result = nil if stack isn't a table or the stack is empty, otherwise the value removed from the stack]], +} + +dtutils.pop = table.remove + +--[[ + NAME + update_combobox_choices - change the list of choices in a combobox + + SYNOPSIS + local du = require "lib/dtutils" + + du.update_combobox_choices(combobox_widget, choice_table) + combobox_widget - lua_combobox - a combobox widget + choice_table - table - a table of strings for the combobox choices + + DESCRIPTION + Set the combobox choices to the supplied list. Remove any extra choices from the end + + RETURN VALUE + none + +]] + +dtutils.libdoc.functions[#dtutils.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[update_combobox_choices - change the list of choices in a combobox]], + Synopsis = [[local du = require "lib/dtutils" + + du.update_combobox_choices(combobox_widget, choice_table) + combobox_widget - lua_combobox - a combobox widget + choice_table - table - a table of strings for the combobox choices]], + Description = [[Set the combobox choices to the supplied list. Remove any extra choices from the end]], + Return_Value = [[none]], +} + +function dtutils.update_combobox_choices(combobox, choice_table) + local items = #combobox + local choices = #choice_table + for i, name in ipairs(choice_table) do + log.msg(log.debug, "Setting choice " .. i .. " to " .. name) + combobox[i] = name + end + if choices < items then + for j = items, choices + 1, -1 do + log.msg(log.debug, "Removing choice " .. j) + combobox[j] = nil + end + end + combobox.value = 1 +end + +--[[ + NAME + spairs - an iterator that provides sorted pairs from a table + + SYNOPSIS + 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 + + DESCIRPTION + 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. + + 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 + + REFERENCE + Code copied from http://stackoverflow.com/questions/15706270/sort-a-table-in-lua + +]] + +dtutils.libdoc.functions[#dtutils.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Example", "Reference"}, + Name = [[spairs - an iterator that provides sorted pairs from a table]], + Synopsis = [[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.]], + 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]], + Reference = [[Code copied from http://stackoverflow.com/questions/15706270/sort-a-table-in-lua]], +} + +-- 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 + +--[[ + + NAME + urlencode - encode a string in a websage manner + + SYNOPOIS + local du = require "lib/dtutils" + + local result = du.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 + + REFERENCE + https://forums.coronalabs.com/topic/43048-remove-special-characters-from-string/ + +]] + +dtutils.libdoc.functions[#dtutils.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value", "Reference"}, + Name = [[urlencode - encode a string in a websage manner]], + Synopsis = [[local du = require "lib/dtutils" + + local result = du.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]], + Reference = [[https://forums.coronalabs.com/topic/43048-remove-special-characters-from-string/]], +} + +function dtutils.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 diff --git a/lib/dtutils/de_DE/LC_MESSAGES/dtutils.processor.po b/lib/dtutils/de_DE/LC_MESSAGES/dtutils.processor.po new file mode 100644 index 0000000..ba35c0d --- /dev/null +++ b/lib/dtutils/de_DE/LC_MESSAGES/dtutils.processor.po @@ -0,0 +1,23 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-10-01 00:36-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: de_DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: dtutils.processor.lua:42 +#, lua-format +msgid "Export Image %i/%i" +msgstr "Exportiere Bild %i/%i" diff --git a/lib/dtutils/debug.lua b/lib/dtutils/debug.lua new file mode 100644 index 0000000..767d86f --- /dev/null +++ b/lib/dtutils/debug.lua @@ -0,0 +1,219 @@ +--[[ +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 log = require "lib/libLog" +local M = {} -- The actual content of the module + +M.libdoc = { + Sections = {"Name", "Synopsis", "Description", "License"}, + Name = [[dtutils.debug - debugging helpers used in developing darktable lua scripts]], + Synopsis = [[local dd = require "lib/dtutils.debug"]], + Description = [[dtutils.debug provides an interface to the darktable debugging routines.]], + 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 .]], + functions = {} +} + +--[[ + NAME + tracepoint - print out a tracepoint and dump the arguments + + SYNOPSIS + 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 + +]] + +M.libdoc.functions[#M.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[tracepoint - print out a tracepoint and dump the arguments]], + Synopsis = [[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]], +} + +function M.tracepoint(name,...) + log.always(4, "***** "..name.." ****") + params = {...} + print(dt.debug.dump(params,"parameters")) + return ...; +end + + + +--[[ + NAME + new_tracepoint - create a function returning a tracepoint + + SYNOPSIS + 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 + + EXAMPLE + register_event(event, dd.new_tracepoint("hit callback")) + + will print the following each time the callback is called + + **** hit callback **** + + +]] + +M.libdoc.functions[#M.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value", "Example"}, + Name = [[new_tracepoint - create a function returning a tracepoint]], + Synopsis = [[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]], + Example = [[register_event(event, dd.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 + + +--[[ + NAME + dprint - pass a variable to dt.debug.dump and print the results to stdout + + SYNOPSIS + 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 + +]] + +M.libdoc.functions[#M.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description"}, + Name = [[dprint - pass a variable to dt.debug.dump and print the results to stdout]], + Synopsis = [[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]], +} + +function M.dprint(...) + log.always(4, dt.debug.dump(...)) +end + +--[[ + NAME + terse_dump - set dt.debug.known to shorten all image dumps to a single line + + SYNOPSIS + 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 +]] + +M.libdoc.functions[#M.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description"}, + Name = [[terse_dump - set dt.debug.known to shorten all image dumps to a single line]], + Synopsis = [[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]], +} + +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/extensions.lua b/lib/dtutils/extensions.lua new file mode 100644 index 0000000..ca37152 --- /dev/null +++ b/lib/dtutils/extensions.lua @@ -0,0 +1,185 @@ +--[[ + + NAME + dtutils/extensions.lua - a library of extenstions to the lua libraries + + SYNOPSIS + require "lib/dtutils.extensions" + + DESCRIPTION + This library contains extensions to the lua libraries. + +]] + +local dtutils_extensions = {} +dtutils_extensions.libdoc = { + Sections = {"Name", "Synopsis", "Description", "License"}, + Name = "dtutils.extensions - a library of extenstions to the lua libraries", + Synopsis = [[require "lib/dtutils.extensions"]], + Description = [[This library contains extensions to the lua libraries.]], + 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 .]], + functions = {} +} + +--[[ + + NAME + string.strip_accents - strip accents from characters + + SYNOPSIS + require "lib/dtutils.extensions" + + result = string.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 + +]] + +dtutils_extensions.libdoc.functions[#dtutils_extensions.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[string.strip_accents - strip accents from characters]], + Synopsis = [[require "lib/dtutils.extensions" + + result = string.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]], +} + +-- Strip accents from a string +-- Copied from https://forums.coronalabs.com/topic/43048-remove-special-characters-from-string/ +function 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 + +--[[ + + NAME + string.escape_xml_characters - escape characters for xml documents + + SYNOPSIS + require "lib/dtutils.extensions" + + result = string.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 ">". + + RETURNN VALUE + result - string - the string containing escapes for the xml characters + +]] + +dtutils_extensions.libdoc.functions[#dtutils_extensions.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = "string.escape_xml_characters - escape characters for xml documents", + Synopsis = [[ require "lib/dtutils.extensions" + + result = string.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]], +} + +-- Escape XML characters +-- Keep & first, otherwise it will double escape other characters +-- https://stackoverflow.com/questions/1091945/what-characters-do-i-need-to-escape-in-xml-documents +function 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 + +return dtutils_extensions diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua new file mode 100644 index 0000000..ebeba68 --- /dev/null +++ b/lib/dtutils/file.lua @@ -0,0 +1,523 @@ +--[[ + + dtutils/file.lua - common darktable lua file functions + + Copyright (C) 2016 Bill Ferguson . + Copyright (C) 2016 Tobias Jakobs + + 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 . +]] + +local dtutils_file = {} +dtutils_file.libdoc = { + Sections = {"Name", "Synopsis", "Description", "License"}, + Name = [[dtutils.file - common darktable lua file functions]], + Synopsis = [[local df = require "lib/dtutils.file"]], + Description = [[dtutils.file provides common file manipulation functions used in + constructing Darktable lua scripts]], + 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 .]], + functions = {} +} + +local dt = require "darktable" + +local log = require "lib/libLog" + +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 + +--[[ + NAME + check_if_bin_exists - check if an executable is in the path + + SYNOPSIS + 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 + +]] + +dtutils_file.libdoc.functions[#dtutils_file.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[check_if_bin_exists - check if an executable is in the path]], + Synopsis = [[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]], +} + +function dtutils_file.check_if_bin_exists(bin) + local result = os.execute("which " .. bin) + if not result then + result = false + end + return result +end + +--[[ + NAME + split_filepath - split a filepath into parts + + SYNOPSIS + 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 + +]] + +dtutils_file.libdoc.functions[#dtutils_file.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[split_filepath - split a filepath into parts]], + Synopsis = [[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]], +} + +function dtutils_file.split_filepath(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 + +--[[ + NAME + get_path - get the path from a file path + + SYNOPSIS + 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 + +]] + +dtutils_file.libdoc.functions[#dtutils_file.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[get_path - get the path from a file path]], + Synopsis = [[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]], +} + +function dtutils_file.get_path(str) + local parts = dtutils_file.split_filepath(str) + return parts["path"] +end + +--[[ + NAME + get_filename - get the filename and extension from a file path + + SYNOPSIS + 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 + +]] + +dtutils_file.libdoc.functions[#dtutils_file.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[get_filename - get the filename and extension from a file path]], + Synopsis = [[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]], +} + +function dtutils_file.get_filename(str) + local parts = dtutils_file.split_filepath(str) + return parts["filename"] +end + +--[[ + NAME + get_basename - get the filename without the path or extension + + SYNOPSIS + 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 + +]] + +dtutils_file.libdoc.functions[#dtutils_file.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[get_basename - get the filename without the path or extension]], + Synopsis = [[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]], +} + +function dtutils_file.get_basename(str) + local parts = dtutils_file.split_filepath(str) + return parts["basename"] +end + +--[[ + NAME + get_filetype - get the filetype from a filename + + SYNOPSIS + 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 + +]] + +dtutils_file.libdoc.functions[#dtutils_file.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[get_filetype - get the filetype from a filename]], + Synopsis = [[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]], +} + +function dtutils_file.get_filetype(str) + local parts = dtutils_file.split_filepath(str) + return parts["filetype"] +end + +--[[ + NAME + check_if_file_exists - check if a file or path exist + + SYNOPSIS + 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 + +]] + +-- Thanks Tobias Jakobs for the idea +dtutils_file.libdoc.functions[#dtutils_file.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[check_if_file_exists - check if a file or path exist]], + Synopsis = [[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]], +} + +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 + +--[[ + NAME + chop_filetype - remove a filetype from a filename + + SYNOPSIS + 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 + +]] + +dtutils_file.libdoc.functions[#dtutils_file.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[chop_filetype - remove a filetype from a filename]], + Synopsis = [[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]], +} + +function dtutils_file.chop_filetype(path) + local length = dtutils_file.get_filetype(path):len() + 2 + return string.sub(path, 1, -length) +end + +--[[ + NAME + file_copy - copy a file to another name/location + + SYNOPSIS + 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 + +]] + +dtutils_file.libdoc.functions[#dtutils_file.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[file_copy - copy a file to another name/location]], + Synopsis = [[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]], +} + +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 + +--[[ + NAME + file_move - move a file from one directory to another + + SYNOPSIS + 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 + +]] + +dtutils_file.libdoc.functions[#dtutils_file.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[file_move - move a file from one directory to another]], + Synopsis = [[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]], +} + +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 + +--[[ + NAME + filename_increment - add a two digit increment to a filename + + SYNOPSIS + 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 + +]] + +dtutils_file.libdoc.functions[#dtutils_file.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[filename_increment - add a two digit increment to a filename]], + Synopsis = [[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]], +} + +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 + +return dtutils_file diff --git a/lib/dtutils/processor.lua b/lib/dtutils/processor.lua new file mode 100644 index 0000000..b79e0b9 --- /dev/null +++ b/lib/dtutils/processor.lua @@ -0,0 +1,266 @@ +--[[ + This file is part of darktable, + 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 . +]] + +local dtutils_processor = {} + +dtutils_processor.libdoc = { + Sections = {"Name", "Synopsis", "Description", "License"}, + Name = [[dtutils.processor - Darktable lua functions for building processor scripts]], + Synopsis = [[local dp = require "lib/dtutils.processor"]], + Description = [[dtutils.processor provides common functions used for building scripts + that send images out to an external process, process them, and return the result (i.e. processors).]], + 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 .]], + functions = {} +} + +local dt = require "darktable" +local dtutils = require "lib/dtutils" +local df = require "lib/dtutils.file" +local log = require "lib/libLog" + +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 +-- This should be $HOME/.config/darktable/lua/locale/ +gettext.bindtextdomain("dtutils",dt.configuration.config_dir.."/lua/locale/") + +local function _(msgid) + return gettext.dgettext("dtutils.processor", msgid) +end + +--[[ + NAME + exporter_status - show the status while exporting images + + SYNOPSIS + local dp = require "lib/dtutils.processor" + + local result = dp.exporter_status(storage, image, format, filename, number, total, high_quality, extra_data) + storage - dt_imageio_module_storage_t - the storage that the status is being shown for + image - dt_lua_image_t - the current image + format - dt_imageio_module_format_t - the format of the export image + filename - string - the filename being exported to + number - integer - the number of this image in the sequence + total - integer - the total number of images being exported + high_quality - boolean + extra_data - extra data from the storage + + DESCRIPTION + exporter_status runs prior to each image being exported and prints out a status that lets the user + know how many images, out of the total, have been exported + + RETURN VALUE + none + +]] + +dtutils_processor.libdoc.functions[#dtutils_processor.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[exporter_status - show the status while exporting images]], + Synopsis = [[local dp = require "lib/dtutils.processor" + + local result = dp.exporter_status(storage, image, format, filename, number, total, high_quality, extra_data) + storage - dt_imageio_module_storage_t - the storage that the status is being shown for + image - dt_lua_image_t - the current image + format - dt_imageio_module_format_t - the format of the export image + filename - string - the filename being exported to + number - integer - the number of this image in the sequence + total - integer - the total number of images being exported + high_quality - boolean + extra_data - extra data from the storage]], + Description = [[exporter_status runs prior to each image being exported and prints out a status that lets the user + know how many images, out of the total, have been exported]], + Return_Value = [[none]], +} + +function dtutils_processor.exporter_status(storage, image, format, filename, + number, total, high_quality, extra_data) + dt.print(string.format(_("Export Image %i/%i"), number, total)) +end + +--[[ + NAME + extract_image_list - assemble the exported image filenames from an image_table into a string + + SYNOPSIS + local dp = require "lib/dtutils.processor" + + result = dp.extract_image_list(image_table) + image_table - table - a table of images such as supplied by the exporter or by libPlugin.build_image_table + + DESCRIPTION + extract_image_list concatenates the exported image names into a space separated string suitable for + passing as an argument to a processor. Each filename is bracketed with single quotes to protect against + spaces and special characters in the filepath + + RETURN VALUE + result - string - the assembled image list on success, or an empty image list on error + +]] + +dtutils_processor.libdoc.functions[#dtutils_processor.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[extract_image_list - assemble the exported image filenames from an image_table into a string]], + Synopsis = [[local dp = require "lib/dtutils.processor" + + result = dp.extract_image_list(image_table) + image_table - table - a table of images such as supplied by the exporter or by libPlugin.build_image_table]], + Description = [[extract_image_list concatenates the exported image names into a space separated string suitable for + passing as an argument to a processor. Each filename is bracketed with single quotes to protect against + spaces and special characters in the filepath]], + Return_Value = [[result - string - the assembled image list on success, or an empty image list on error]], +} + +function dtutils_processor.extract_image_list(image_table) + local img_list = "" + for _,exp_img in pairs(image_table) do + img_list = img_list .. " " .. "'" .. exp_img .. "'" + end + return img_list +end + +--[[ + NAME + extract_collection_path - extract the collection path from an image table + + SYNOPSIS + local dp = require "lib/dtutils.processor" + + local result = dp.extract_collection_path(image_table) + image_table - table - a table of images such as supplied by the exporter or by libPlugin.build_image_table + + DESCRIPTION + extract_collection_path looks at the first image in the image_table and returns the path + + RETURN VALUE + result - string - the collection path on success, or nil if there was an error + + LIMITATIONS + The collection path is determined from the first image. If the table consists of images from different + collections, then only the first collection is used. + +]] + +dtutils_processor.libdoc.functions[#dtutils_processor.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value", "Limitations"}, + Name = [[extract_collection_path - extract the collection path from an image table]], + Synopsis = [[local dp = require "lib/dtutils.processor" + + local result = dp.extract_collection_path(image_table) + image_table - table - a table of images such as supplied by the exporter or by libPlugin.build_image_table]], + Description = [[extract_collection_path looks at the first image in the image_table and returns the path]], + Return_Value = [[result - string - the collection path on success, or nil if there was an error]], + Limitations = [[The collection path is determined from the first image. If the table consists of images from different + collections, then only the first collection is used.]], +} + +function dtutils_processor.extract_collection_path(image_table) + collection_path = nil + for i,_ in pairs(image_table) do + collection_path = i.path + break + end + return collection_path +end + +--[[ + NAME + make_output_filename- make an output filename from an image list + + SYNOPSIS + local dp = require "lib/dtutils.processor" + + local result = dtutils.make_output_filename(img_list) + img_list - string - a space separated list of filenames + + DESCRIPTION + make_output_filename takes a string of filenames, breaks them apart, then + combines them in a way that makes some sense. This is useful in routines + that do panoramas, hdr, or focus stacks. The returned filename is a representation + of the files used to construct the image. If there are 3 or fewer images, then the file + basenames are concatenated with a separator. If there is more than 3 images, the first and + last file basenames are concatenated with a separator. + + RETURN VALUE + result - string - the constructed filename on success, nil on error + +]] + +dtutils_processor.libdoc.functions[#dtutils_processor.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[make_output_filename- make an output filename from an image list]], + Synopsis = [[local dp = require "lib/dtutils.processor" + + local result = dtutils.make_output_filename(img_list) + img_list - string - a space separated list of filenames]], + Description = [[make_output_filename takes a string of filenames, breaks them apart, then + combines them in a way that makes some sense. This is useful in routines + that do panoramas, hdr, or focus stacks. The returned filename is a representation + of the files used to construct the image. If there are 3 or fewer images, then the file + basenames are concatenated with a separator. If there is more than 3 images, the first and + last file basenames are concatenated with a separator.]], + Return_Value = [[result - string - the constructed filename on success, nil on error]], +} + +function dtutils_processor.make_output_filename(img_list) + local images = {} + local cnt = 1 + local max_distinct_names = 3 + local name_separator = "-" + local outputFileName = nil + log.msg(log.debug, "img_list is ", img_list) + + local result = dtutils.split(img_list, " ") + table.sort(result) + for _,img in pairs(result) do + images[cnt] = df.get_basename(img) + cnt = cnt + 1 + end + + cnt = cnt - 1 + + if cnt > 1 then + if cnt > max_distinct_names then + -- take the first and last + outputFileName = images[1] .. name_separator .. images[cnt] + else + -- join them + outputFileName = dtutils.join(images, name_separator) + end + else + -- return the single name + outputFileName = images[cnt] + end + + return outputFileName +end + +return dtutils_processor diff --git a/lib/libLog.lua b/lib/libLog.lua new file mode 100644 index 0000000..052a9da --- /dev/null +++ b/lib/libLog.lua @@ -0,0 +1,378 @@ +--[[ + + libLog.lua - darktable lua logging library + + Copyright (C) 2016 Bill Ferguson . + + 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 . +]] + +local libLog = {} +libLog.libdoc = { + Sections = {"Name", "Synopsis", "Description", "License", "Copyright"}, + Name = "libLog - darktable lua logging library", + Synopsis = [[local log = require "lib/libLog"]], + Description = [[libLog provides a multi-level logging solution for use with + the darktable lua scripts.]], + 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" + +-- set the default log levels + +libLog.debug = {"DEBUG:", false} +libLog.info = {"INFO:", false} +libLog.warn = {"WARN:", false} +libLog.error = {"ERROR:", true} +libLog.success = {"SUCCESS:", true} + +libLog.libdoc.functions[#libLog.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = "caller - get the name of the calling routine", + Synopsis = [[local log = require "lib/libLog" + +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 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]], +} + +--[[ + NAME + caller - get the name of the calling routine + + SYNOPSIS + local log = require "lib/libLog" + + local 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 of the calling routine and returns it + + RETURN VALUE + result - string - the name of the calling function and line number or 'callback: ' if the attempt to get the + caller returns nil + + +]] + +function libLog.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 + +libLog.libdoc.functions[#libLog.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Limitations"}, + Name = "msg - print a log message", + Synopsis = [[local log = require "lib/libLog" + + log.msg(level, ...) + level - table - the type of message, one of: + libLog.debug - debugging messages + libLog.info - informational messages + libLog.warn - warning messages + libLog.error - error messages + libLog.success - success messages + ... - 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. + debug messages print straight to the console, info and warn messages to dt.print_error() and error + and success to dt.print()]], + Limitations = [[If you use libLog.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. + + libLog.msg(libLog.debug, "libPlugin.format_combobox:", "value is " .. self.value) + + which would result in + + DEBUG: callback: libPlugin.format_combobox: value is JPEG]], +} + +--[[ + NAME + msg - print a log message + + SYNOPSIS + local log = require "lib/libLog" + + log.msg(level, ...) + level - table - the type of message, one of: + libLog.debug - debugging messages + libLog.info - informational messages + libLog.warn - warning messages + libLog.error - error messages + libLog.success - success messages + ... - 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. + debug messages print straight to the console, info and warn messages to dt.print_error() and error + and success to dt.print() + + RETURN VALUE + none + + LIMITATIONS + If you use libLog.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. + + libLog.msg(libLog.debug, "libPlugin.format_combobox:", "value is " .. self.value) + + which would result in + + DEBUG: callback: libPlugin.format_combobox: value is JPEG + +]] + +function libLog.msg(level, ...) + local message = {...} + if level[2] == true then + libLog.print(level[1], libLog.caller(3), unpack(message)) + end +end + +libLog.libdoc.functions[#libLog.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value", "Reference"}, + Name = "print - print the supplied arguments", + Synopsis = [[local log = require "lib/libLog" + + log.print(...) + ... - arguments to be printed that are converted with tostring()]], + Description = [[print loops through the arguments converting each to a string then writing + them to stdout. Spaces are put between each argument on output.]], + Return_Value = "none", + Reference = "http://stackoverflow.com/questions/7148678/lua-print-on-the-same-line", +} + +--[[ + NAME + libLog.print - print the supplied arguments + + SYNOPSIS + local log = require "lib/libLog" + + log.print(...) + ... - arguments to be printed that are converted with tostring() + + DESCRIPTION + print loops through the arguments converting each to a string then writing + them to stdout. Spaces are put between each argument on output. + + RETURN VALUE + none + + REFERENCE + http://stackoverflow.com/questions/7148678/lua-print-on-the-same-line + +]] + +function libLog.print(...) + local write = io.write + local n = select("#",...) + for i = 1,n do + local v = tostring(select(i,...)) + write(v) + if i~=n then + write(' ') + end + end + write('\n') +end + +libLog.libdoc.functions[#libLog.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[set_level - set the logging level]], + Synopsis = [[local log = require "lib/libLog" + + log.set_level(level) + level - string - a string specifying the level, one of: "debug", "info", "warn", "error", "success"]], + Description = [[set_level sets the logging level to the level specified. Every level above the specified level + is also enabled, therefore specifying "debug" would turn on all levels and specifying "success" + would turn all levels off except libLog.success.]], + Return_Value = "none", +} + +--[[ + NAME + set_level - set the logging level + + SYNOPSIS + local log = require "lib/libLog" + + log.set_level(level) + level - string - a string specifying the level, one of: "debug", "info", "warn", "error", "success" + + DESCRIPTION + set_level sets the logging level to the level specified. Every level above the specified level + is also enabled, therefore specifying "debug" would turn on all levels and specifying "success" + would turn all levels off except libLog.success. + + RETURN VALUE + none + +]] + +function libLog.set_level(level) + level = level:lower() + if string.match(level, "debug") then + -- turn everything on + libLog.debug[2] = true + libLog.info[2] = true + libLog.warn[2] = true + libLog.error[2] = true + libLog.success[2] = true + elseif string.match(level, "info") then + -- turn off debug and everything else on + libLog.debug[2] = false + libLog.info[2] = true + libLog.warn[2] = true + libLog.error[2] = true + libLog.success[2] = true + elseif string.match(level, "warn") then + -- turn off debug and info and everything else on + libLog.debug[2] = false + libLog.info[2] = false + libLog.warn[2] = true + libLog.error[2] = true + libLog.success[2] = true + elseif string.match(level, "error") or string.match("reset") then + -- everything off except error and success + libLog.debug[2] = false + libLog.info[2] = false + libLog.warn[2] = false + libLog.error[2] = true + libLog.success[2] = true + elseif string.match(level, "success") then + -- everything off except success + libLog.debug[2] = false + libLog.info[2] = false + libLog.warn[2] = false + libLog.error[2] = false + libLog.success[2] = true + else + -- leave everything unchanged and return + return + end +end + +libLog.libdoc.functions[#libLog.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description", "Return_Value"}, + Name = [[get_level - get the current logging level]], + Synopsis = [[local log = require "lib/libLog" + + local result = libLog.get_level()]], + Description = "get_level returns a string representing the current log level.", + Return_Value = [[result - string - one of "debug", "info", "warn", "error", or "success"]], +} + +--[[ + NAME + get_level - get the current logging level + + SYNOPSIS + local log = require "lib/libLog" + + local result = log.get_level() + + DESCRIPTION + get_level returns a string representing the current log level. + + RETURN VALUE + result - string - one of "debug", "info", "warn", "error", or "success" + +]] + +function libLog.get_level() + local result = nil + if libLog.debug[2] == true then + result = "debug" + elseif libLog.info[2] == true then + result = "info" + elseif libLog.warn[2] == true then + result = "warn" + elseif libLog.error[2] == true then + result = "error" + elseif libLog.success[2] == true then + result = "success" + end + return result +end + +libLog.libdoc.functions[#libLog.libdoc.functions + 1] = { + Sections = {"Name", "Synopsis", "Description"}, + Name = [[always - always write out a log message]], + Synopsis = [[local log = require "lib/libLog" + + log.always(level, ...) + level - number - the number of stack levels to go back to identify the caller + ... - string(s) - the message to print]], + Description = [[always is meant specifically for the dtutils.debug library, but may be used for other + purposes. always is independent of the log level setting so that it will always work. + The number of stack levels to look back for the caller may be specified. It is normally + 3 when called from a routine, but is 4 when called from the debugging routines since the + debugging routines add another stack level.]], +} + +--[[ + NAME + always - always write out a log message + + SYNOPSIS + local log = require "lib/libLog" + + log.always(level, ...) + level - number - the number of stack levels to go back to identify the caller + ... - string(s) - the message to print + + DESCRIPTION + always is meant specifically for the dtutils.debug library, but may be used for other + purposes. always is independent of the log level setting so that it will always work. + The number of stack levels to look back for the caller may be specified. It is normally + 3 when called from a routine, but is 4 when called from the debugging routines since the + debugging routines add another stack level. +]] + +function libLog.always(level, ...) + local message = {...} + libLog.print(libLog.caller(level), unpack(message)) +end + +return libLog 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..4dbed6c --- /dev/null +++ b/tools/get_lib_manpages.lua @@ -0,0 +1,63 @@ +--[[ + 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 libname = nil + +dt.configuration.check_version(...,{3,0,0}) + +local function output_man(d) + local parts = du.split(d[d.Sections[1]], " - ") + if not libname then + libname = parts[1] + end + local fname = "/tmp/" .. parts[1] .. ".3" + local mf = io.open(fname, "w") + if mf then + mf:write(".TH " .. string.upper(parts[1]) .. " 3 \"\" \"\" \"Darktable " .. libname .. " functions\"\n") + for _,section in pairs(d.Sections) do + if d[section] then + mf:write(".SH " .. string.upper(string.gsub(section, "_", " ")) .. "\n") + mf:write(d[section] .. "\n") + end + end + mf:close() + os.execute("groff -man " .. fname .. " | ps2pdf - " .. fname .. ".pdf") + 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..706b025 --- /dev/null +++ b/tools/get_libdoc.lua @@ -0,0 +1,46 @@ +--[[ + 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 function output_doc(d) + for _,section in pairs(d.Sections) do + if d[section] 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 +