summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers@digital-scurf.org>2016-06-25 15:44:10 +0100
committerDaniel Silverstone <dsilvers@digital-scurf.org>2016-06-25 15:44:10 +0100
commitbc1ca6add8210e432ae991375f59ae694e9b938e (patch)
treecc40fa3dd4fed6d083c15ce5621e49d0afbdace0
parent4910b68f03bf329eefa21b27277d54d3a0646e8a (diff)
downloadtongue-bc1ca6add8210e432ae991375f59ae694e9b938e.tar.bz2
Some basic tongue tests and very early language pack support
-rw-r--r--Makefile2
-rw-r--r--lib/tongue.lua3
-rw-r--r--lib/tongue/langpack.lua224
-rw-r--r--test/test-tongue.langpack.lua69
-rw-r--r--test/test-tongue.lua6
5 files changed, 302 insertions, 2 deletions
diff --git a/Makefile b/Makefile
index de7da60..7f562e6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
all: test doc
-MODULES := tongue
+MODULES := tongue tongue.langpack
LUA_VER := 5.1
PREFIX ?= /usr/local
diff --git a/lib/tongue.lua b/lib/tongue.lua
index 9f71be7..a22bff0 100644
--- a/lib/tongue.lua
+++ b/lib/tongue.lua
@@ -15,9 +15,12 @@ local _ABI = 1
local VERSION = "Tongue Version " .. tostring(_VERSION)
+local langpack = require "tongue.langpack"
+
return {
_VERSION = _VERSION,
VERSION = VERSION,
_ABI = _ABI,
ABI = ABI,
+ langpack = langpack,
}
diff --git a/lib/tongue/langpack.lua b/lib/tongue/langpack.lua
new file mode 100644
index 0000000..b77a128
--- /dev/null
+++ b/lib/tongue/langpack.lua
@@ -0,0 +1,224 @@
+-- lib/tongue/langpack.lua
+--
+-- Lua I18N library 'Tongue' - Language Packs
+--
+-- Copyright 2016 Daniel Silverstone <dsilvers@digital-scurf.org>
+--
+-- For licence terms, see COPYING
+--
+
+--- Language packs comprise zero or more sources of translations for a given
+-- "language".
+--
+-- Packs may be added to at any time. A pack can have a parent pack which will
+-- be used if the pack in question lacks a translation token. Tongue defines a
+-- single default pack (available as tongue.pack.fallback) which always returns
+-- a string comprising the token and its arguments flattened as strings. This
+-- is the only part of Tongue which assumes an ability to flatten token
+-- arguments as strings, all other parts of tongue preserve token arguments
+-- unchanged which allows the passing of complex arguments such as repository
+-- objects.
+--
+-- @module tongue.langpack
+
+local fallback, langpack = nil, {}
+
+--- Fallback language pack.
+--
+-- The fallback language pack provides a mechanism to ensure that language
+-- packs can always expand a translation to some extent.
+--
+-- @field fallback
+
+--- Try and convert a string to a function.
+--
+-- This takes a tongue expansion string and tries to convert it to a function
+-- which can be used to generate the expansion later.
+--
+-- @tparam string expn The expansion string
+-- @treturn[1] function The resulting function
+-- @treturn[2] nil To indicate error
+-- @treturn[2] string Error message
+local function try_convert(expn)
+ return nil, "No converter written"
+end
+
+--- Generate a function to localise when a conversion failed.
+--
+-- This generates a function to report a failed expansion and why
+--
+-- @tparam string token The token which failed to expand.
+-- @tparam string expn The content of that token.
+-- @tparam string msg The error message.
+-- @treturn function a function which can report that failure
+local function report_conversion(token, expn, msg)
+ return function(args)
+ local nargs = {
+ _pack = args.pack,
+ token = token,
+ expn = expn,
+ msg = msg,
+ args = args
+ }
+ return args._pack:expand("__TONGUE.FAILEDEXPAND", nargs)
+ end
+end
+
+
+--- Tongue Language Pack.
+--
+-- A Tongue language pack comprises zero or more translations associated with
+-- the language (and sub-language) chosen at construction time. Language packs
+-- may have a parent and can be augmented at any time with further
+-- translations.
+--
+-- @type langpack
+
+--- Add a token expansion to a pack.
+--
+-- This adds the expansion of a token to a tongue language pack. On addition,
+-- if the expansion is a string then it will *NOT* be validated unless the
+-- strict argument is set.
+--
+-- Token names are automatically uppercased in the ASCII charset.
+--
+-- @tparam string token The token to be expanded
+-- @tparam string|function expansion The expansion string (or function)
+-- @tparam bool strict Whether to treat a bad expansion string as an error.
+-- @treturn bool Whether or not the expansion was successfully added
+-- @function add_token
+
+function langpack:add_token(token, expansion, strict)
+ if strict and type(expansion) == "string" then
+ expansion = try_convert(expansion)
+ if not expansion then
+ -- We don't give a reason, we just fail to expand/add
+ return false
+ end
+ end
+ self.entries[token:upper()] = expansion
+end
+
+--- Expand a token and arguments into a message.
+--
+-- This expands the given token and arguments into a full message. This will
+-- always succeed unless something has gone crazywrong with the internals of
+-- tongue or the language packs.
+--
+-- Passed-in tokens are always uppercased before expansion.
+--
+-- @tparam string token The token to be expanded
+-- @tparam table args Arguments to the token expansion
+-- @treturn string The expanded result
+-- @function expand
+function langpack:expand(token, args)
+ local pack = self
+ if not args._pack then args._pack = pack end
+ local ok, expn = pcall(function()
+ if not pack.entries[token] then
+ -- expand upward toward the parent
+ return pack.parent:expand(token, args)
+ end
+ if type(pack.entries[token]) == "string" then
+ -- Attempt to convert the string form into a function
+ local fn, msg = try_convert(pack.entries[token])
+ if not fn then
+ -- Failed to convert, so generate an error report expn. instead
+ fn = report_conversion(token, pack.entries[token], msg)
+ end
+ pack.entries[token] = fn
+ end
+ return pack.entries[token](args)
+ end)
+ if ok then
+ return expn
+ end
+ -- Failed to expand for whatever reason, we need to report this:
+ return self:expand("__TONGUE.INTERNALERROR",
+ { token=token, args=args, err=expn })
+end
+
+
+-- set up the language pack metatable
+local langpack_mt = {
+ __index = langpack
+}
+
+--- Create a language pack.
+--
+-- This creates a tongue language pack. Once the pack is created it can be
+-- populated in various ways by language loaders. The passed in language
+-- is used by other parts of Tongue (e.g. the message resolver) to manage
+-- language packs.
+--
+-- @tparam string language The language name (e.g. 'en')
+-- @tparam ?string sublang The sub-language name (or nil if unwanted) e.g. "GB"
+-- @tparam ?langpack parent The parent langauge pack (if nil, Tongue will use the fallback)
+-- @treturn langpack The newly created language pack
+-- @function create
+local function createpack(language, sublang, parent)
+ local retpack = {}
+ retpack.language = language
+ retpack.sublang = sublang
+ retpack.parent = parent or fallback
+ retpack.entries = {}
+ retpack.lang = sublang and language .. "_" .. sublang or language
+ return setmetatable(retpack, langpack_mt)
+end
+
+-- Finally, populate the fallback pack with some cleverness
+fallback = createpack()
+
+local function flatten(t)
+ if t._VISITED then return "???" end
+ t._VISITED=t
+ local ret = {}
+ for k, v in pairs(t) do
+ if type(k) == "string" and string.sub(k, 1, 1) ~= "_" then
+ if type(v) == "table" then
+ v = flatten(v)
+ elseif type(v) == "string" then
+ v = ("%q"):format(v)
+ else
+ v = tostring(v)
+ end
+ ret[#ret+1] = k .. "=" .. v
+ end
+ end
+ t._VISITED=nil
+ return "{" .. table.concat(ret,",") .. "}"
+end
+
+local function fallback_entries_index(t, name)
+ local f = function(args)
+ return "!!!" .. name .. "!!!" .. flatten(args)
+ end
+ t[name] = f
+ return f
+end
+
+setmetatable(fallback.entries, {__index=fallback_entries_index})
+
+fallback.entries["__TONGUE.INTERNALERROR"] = function(args)
+ -- args.token
+ -- args.err
+ -- args.args
+ return table.concat({
+ "!!!Internal error while processing", tostring(args.token),
+ "-", tostring(args.err), "-", flatten(args.args)}, " ")
+end
+
+fallback.entries["__TONGUE.FAILEDEXPAND"] = function(args)
+ -- args.token
+ -- args.expn
+ -- args.msg
+ -- args.args
+ return table.concat({
+ "!!!Failed to expand", tostring(args.token), "-", tostring(args.expn),
+ "-", tostring(args.err), "-", flatten(args.args)}, " ")
+end
+
+return {
+ create = createpack,
+ fallback = fallback
+}
diff --git a/test/test-tongue.langpack.lua b/test/test-tongue.langpack.lua
new file mode 100644
index 0000000..102dd6d
--- /dev/null
+++ b/test/test-tongue.langpack.lua
@@ -0,0 +1,69 @@
+-- test/test-tongue.langpack.lua
+--
+-- Lua I18N library 'Tongue' -- Tests for the language pack module
+--
+-- Copyright 2016 Daniel Silverstone <dsilvers@digital-scurf.org>
+--
+-- For Licence terms, see COPYING
+--
+
+-- Step one, start coverage
+
+pcall(require, 'luacov')
+
+local tongue = require 'tongue'
+
+local testnames = {}
+
+local real_assert = assert
+local total_asserts = 0
+local function assert(...)
+ local retval = real_assert(...)
+ total_asserts = total_asserts + 1
+ return retval
+end
+
+local function add_test(suite, name, value)
+ rawset(suite, name, value)
+ testnames[#testnames+1] = name
+end
+
+local suite = setmetatable({}, {__newindex = add_test})
+
+function suite.fallback_present()
+ assert(tongue.langpack.fallback, "Fallback language pack not present")
+end
+
+function suite.create_present()
+ assert(tongue.langpack.create, "Language pack creation function not present")
+end
+
+function suite.tryfallback()
+ local expn = tongue.langpack.fallback:expand("TEST", {foo="bar"})
+ assert(string.sub(expn, 1, 10) == "!!!TEST!!!", expn)
+ assert(string.find(expn, 'foo="bar"'), expn)
+end
+
+function suite.tryparentfallback()
+ local lang = tongue.langpack.create("en")
+ assert(lang, "Could not create language pack")
+ local expn = lang:expand("TEST", {foo="bar"})
+ assert(string.sub(expn, 1, 10) == "!!!TEST!!!", expn)
+ assert(string.find(expn, 'foo="bar"'), expn)
+end
+
+local count_ok = 0
+for _, testname in ipairs(testnames) do
+-- print("Run: " .. testname)
+ local ok, err = xpcall(suite[testname], debug.traceback)
+ if not ok then
+ print(err)
+ print()
+ else
+ count_ok = count_ok + 1
+ end
+end
+
+print(tostring(count_ok) .. "/" .. tostring(#testnames) .. " [" .. tostring(total_asserts) .. "] OK")
+
+os.exit(count_ok == #testnames and 0 or 1)
diff --git a/test/test-tongue.lua b/test/test-tongue.lua
index 1f499d1..fc77e66 100644
--- a/test/test-tongue.lua
+++ b/test/test-tongue.lua
@@ -2,7 +2,7 @@
--
-- Lua I18N library 'Tongue' -- Tests for the core Tongue module
--
--- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org>
+-- Copyright 2016 Daniel Silverstone <dsilvers@digital-scurf.org>
--
-- For Licence terms, see COPYING
--
@@ -43,6 +43,10 @@ function suite.version_string_present()
assert(tongue.VERSION, "Version string is missing")
end
+function suite.langpack_module_present()
+ assert(tongue.langpack, "Language pack module not present")
+end
+
local count_ok = 0
for _, testname in ipairs(testnames) do
-- print("Run: " .. testname)