* Modules for FH Library

For users to report plugin bugs and request plugin enhancements; and for authors to test new/new versions of plugins, and to discuss plugin development (in the Programming Technicalities sub-forum). If you want advice on choosing or using a plugin, please ask in General Usage or an appropriate sub-forum.
Post Reply
User avatar
Jane
Site Admin
Posts: 8442
Joined: 01 Nov 2002 15:00
Family Historian: V7
Location: Somerset, England
Contact:

Modules for FH Library

Post by Jane » 10 Oct 2013 16:03

In Modules for Family Historian (10814) Mike asked:
Any further thoughts on setfenv() approach discussed above?
Sounds sensible. Do you want to finish off one of the modules and I'll try and experiment with it. It might be worth using the penlight idea with the folder so we can have

fhh.stringx
fhh.iterators
etc. with the files being stringx.lua and iterators.lua

Won't be for a while as I am running FH courses on Monday and Tuesday.
Jane
My Family History : My Photography "Knowledge is knowing that a tomato is a fruit. Wisdom is not putting it in a fruit salad."

User avatar
tatewise
Megastar
Posts: 27087
Joined: 25 May 2010 11:00
Family Historian: V7
Location: Torbay, Devon, UK
Contact:

Re: Modules for FH Library

Post by tatewise » 15 Oct 2013 17:59

There are several components to this posting.

(1) Function loadrequire()
I have had to amend this to cope with certain require(module) errors.
Example Scenario:-
A loaded module "ABC" uses require("XYZ") but module "XYZ" no longer exists.
When loadrequire("ABC") invokes require(module) it returns false.
So a download and install is attempted, which does not resolve the problem.
Thus, the associated require(module) error message needs to be examined to determine the correct action.
So I have replaced the original line (which erroneously declares the global res):

Code: Select all

res = pcall(requiref,extended)
with the lines [Edited 19-Oct]:

Code: Select all

    local res, err = pcall(requiref,extended)	-- was: res = pcall(requiref,extended)
    if not(res) then
        if err:match("module '"..extended:gsub("(%W)","%%%1").."' not found") then
            ... { existing code to download extended/module goes here } ...
        else
            fhMessageBox('Error from require("'..module..'") command:\n'..err)
            return false
        end
This only tries a download if the error message is of the form:
...alico Pie\Family Historian\Plugins\ABC.lua:22: module 'ABC.lua' not found: ...
It should cope with all other error messages, including when module 'XYZ.lua' not found is NOT the required module.

(2) FH Module Naming
I have decided to put the FH Modules in the \Plugins\fh\ sub-folder.
This is short, similar to the FH Icon, and to the fh prefix on LUA API functions, but otherwise rarely occurs in code.
I have added version suffixes such as _v1 and _v2 to module filenames to identify their version.
e.g. \fh\stringx_v1.lua
I assume version control will be exercised on each individual module rather than on the fh library as a whole.
Any version suffix would be OK, providing the resulting name is also a valid LUA variable name, so that the Penlight loading and lazy table techniques can be used.

(3) FH Module Library Files
The following module files are available from my SkyDrive fh folder.
Tick the modules you want, then right-click to Download a zip file of them all.
Alternatively, simply right-click and Download the files individually.
encoder_v1.lua
encoder_v2.lua
general_v1.lua
iterate_v1.lua
iup_gui_v1.lua
loadreq_v1.lua
progbar_v1.lua
stringx_v1.lua
tablex_v1.lua

These can be loaded and called in the conventional ways:
[EDIT 19-Oct] This has all been updated - see (5) [EDIT UPDATE 19-Oct] later.

Code: Select all

  if not loadrequire("fh.encoder_v1") then return end
  require "fh.general_v1"
  require("fh.stringx_v1").import()
  require("fh.tablex_v1")
  tablex.import()

  strText = encoder.StrCP1252_XML(strText)
  general.SaveStringToFile(strText,strFile)
  strText = strText:split()
  table.save(tblData,strFile)
But require "fh.encoder_v1" and require "fh.encoder_v2" cannot be used in same Plugin, because both utilise the same module name and table encoder. [EDIT - This is no longer a restriction.]
Note that fh.general_v1 uses:

Code: Select all

require "fh.loadreq_v1"								-- To access loadrequire() function
if not loadreq.loadrequire("pl","pl.init") then return end
local pl = require("pl.import_into")()			-- To access Penlight packages
(4) [EDIT UPDATE 18-Oct]
All modules updated Friday 18 Oct ~ 19:00 pm with relatively minor changes.

You may be interested in the way I developed these modules:-
They all start as .fh_lua modules, with names such as +fh+encoder_v1 and +fh+stringx_v1.
This allows them to be edited directly from the Plugins window, and the +fh+... lists them at the top.
They are tested using require "+fh+encoder_v1" etc, and have exactly the same content as a module.

I have a Plugin whose purpose is to assess & copy each +fh+{module_ver}.fh_lua to its fh\{module_ver}.lua.
The assessments are confidence checks that firstly the .lua and then the .fh_lua modules work consistently.
If all the assessments pass, then any updated/new +fh+{module_ver}.fh_lua is copied to its fh\{module_ver}.lua.

I am looking at how Penlight loads the whole pl library, yet supports individual require "pl.{module}" commands, and the lazy table technique.
I presume something like loadrequire("fh","fh.init") would load all versions of all modules, yet support individual require "fh.{module_ver}" commands for any single version of each module.
What is not clear, is how a new version of one module gets incorporated into the fh library and downloaded.
Alternatively, we could simply use loadrequire("fh.{module_ver}") for each module version.

(5) [EDIT UPDATE 19-Oct]
All modules updated Saturday 19 Oct ~ 19:00 pm with additional files:
library_v1.lua
library_v2.lua
modules_v1.lua
modules_v2.lua
stringx_v2.lua

These implement a Penlight style of loading as follows.
For library version 1 set of modules, all at version 1:

Code: Select all

	if not loadrequire("fh.library_v1") then return end
	local fh = require("fh.modules_v1")()
For library version 2 set of modules, all at version 1 plus some at version 2:

Code: Select all

	if not loadrequire("fh.library_v2") then return end
	local fh = require("fh.modules_v2")()
"fh.library_v1/2" combines the Penlight "pl","pl.init" function into one file.
"fh.modules_v1/2" performs the Penlight "pl.import_into" & "pl.utils" function in one file.

The idea is that "fh.library_v1" downloads all the fh\{module}.lua files for V1, and calls "fh.modules_v1", which lazy loads all the required modules.
Later on "fh.library_v2" downloads all the fh\{module}.lua files for V1 & V2, and calls "fh.modules_v2", which lazy loads all the required V1 & V2 modules.
For the moment "fh.modules_v1/2" does NOT use the List, Map, Set, MultiMap classes from pl.import_into as I could not see where they got used, and everything seems to work OK without them.
loadrequire would be more efficient if it only downloaded files NOT already existing in the Plugins folder.
e.g. Just before Get file down and install it insert:

Code: Select all

 if lfs.attributes(storein..filename,"mode") == "file" then return true end
All the library modules now start with the following style of code:

Code: Select all

local fh = {}								-- Local environment table
package.seeall(fh)						 -- Enable all globals
module(...,package.seeall)			  -- Create matching module name
setfenv(1,fh)								-- All public names are added to local fh table
This no longer pollutes the global space with a table name ( e.g. encoder = fh ).
This also allows multiple versions of the same module to be used at the same time.
e.g.
A library module could use local encoder = require "fh.encoder_v1"
while main Plugin can use local encoder = require "fh.encoder_v2"
both can then use encoder.StrUTF8_Encode() but with different results, because this function has been updated from V1 to V2.

Also, I have realised that the lazy table technique creates a Global table for each module, with the same name as the module file.
So, \fh\encoder_v1.lua would utilise Global table encoder_v1,
and \fh\encoder_v2.lua would utilise Global table encoder_v2, and so on.
This offers another way that items from different versions can be used at the same time.
It also virtually guarantees no name clashes with libraries such as Penlight, because the version suffixes are quite unusual.
Mike Tate ~ researching the Tate and Scott family history ~ tatewise ancestry

User avatar
tatewise
Megastar
Posts: 27087
Joined: 25 May 2010 11:00
Family Historian: V7
Location: Torbay, Devon, UK
Contact:

Re: Modules for FH Library (LUA Libraries)

Post by tatewise » 22 Oct 2013 21:42

Special consideration must be given to FH Modules intended to import onto LUA Library methods.
e.g. fh.stringx_v1 and fh.stringx_v2 intended for the LUA string methods.

If more than one such FH Module is imported to string, then the results may be unpredictable, and depend on the order in which they are imported.
The solution is to impose some simple conventions:
  • FH Modules wishing to use fh.stringx_v1 or fh.stringx_v2 must NOT use import to string because the effect is global. They must use the stringx_v1.function() mode of access rather than string methods.
  • Alternatively, fh.stringx_v1 and fh.stringx_v2 must be totally compatible, where fh.stringx_v2 only adds extra unrelated functions, and leaves all other functions unchanged. Then multiple import to string can do no harm.
Similar considerations apply to LUA table, math, etc.
Mike Tate ~ researching the Tate and Scott family history ~ tatewise ancestry

User avatar
tatewise
Megastar
Posts: 27087
Joined: 25 May 2010 11:00
Family Historian: V7
Location: Torbay, Devon, UK
Contact:

Re: Modules for FH Library (Summary Usage)

Post by tatewise » 22 Oct 2013 22:05

In order to use the FH Libraries the Plugin initially needs the following:

Code: Select all

	if not fhLoadRequire("fh.library_v2") then return end
	local fh = require("fh.modules_v2")()
See also loadrequire() Features (10858).
If the fh. mode of access is not needed, then local fh = is not needed; just the require( ) ().
This will lazy load all the V1 and V2 modules included in library V2.

Thereafter, any V1 or V2 module function can be accessed several ways:

Code: Select all

	general_v1.FlgFileExists("C:\ProgramData")    -- global general_v1 table
	fh.general_v1.FlgFileExists("C:\ProgramData") -- upvalue fh table with v1 module

	encoder_v1.StrUTF8_Encode("¡ - ÿ")        -- global encoder_v1 table
	local encoder = require "fh.encoder_v1"     -- local encoder table assigned by require
	encoder.StrUTF8_Encode(¡ - ÿ")

	fh.encoder_v2.StrUTF8_Encode("¡ - ÿ")   -- upvalue fh table with v2 module
	local encoder = encoder_v2                -- local encoder table = global encoder_v2
	encoder.StrUTF8_Encode("¡ - ÿ")
Furthermore, no required module needs to invoke fhLoadRequire("fh.library_v2") nor require("fh.modules_v2")() or the V1 equivalents, because all the V1 & V2 modules are lazy loaded already.
These modules can use the access methods shown above to call any other modules (except the fh table, which may not exist, and is hidden by its own local fh table).
Mike Tate ~ researching the Tate and Scott family history ~ tatewise ancestry

Post Reply