Skip to content

Commit

Permalink
Support for requiring external libraries through -L option
Browse files Browse the repository at this point in the history
For example you could install raylib somewhere in your computer
and `require` would work by running with `-L /path/to/nelua-raylib`

For persistent changes you can either create `.neluacfg.lua`
in your project directory or `$HOME/config/neluacfg.lua` in your home
with the following contents:

return {
  lib_path = {'/path/to/nelua-raylib'}
}
  • Loading branch information
edubart committed Apr 2, 2020
1 parent 52cd5ab commit 9683c40
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 63 deletions.
4 changes: 2 additions & 2 deletions nelua/builtins.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ function builtins.require(context, node)

-- load it and parse
local unitpath = argnode.attr.value
local filepath = fs.findmodulefile(unitpath, config.path)
local filepath, err = fs.findmodulefile(unitpath, config.path)
if not filepath then
if canloadatruntime then
-- maybe it would succeed at runtime
attr.runtime_require = true
return
else
node:raisef("in require: module '%s' not found", unitpath)
node:raisef("in require: module '%s' not found:\n%s", unitpath, err)
end
end

Expand Down
169 changes: 112 additions & 57 deletions nelua/configer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ local argparse = require 'argparse'
local tabler = require 'nelua.utils.tabler'
local metamagic = require 'nelua.utils.metamagic'
local except = require 'nelua.utils.except'
local sstream = require 'nelua.utils.sstream'
local fs = require 'nelua.utils.fs'
local cdefs = require 'nelua.cdefs'
local compat = require 'pl.compat'
local pretty = require 'pl.pretty'
local console = require 'nelua.utils.console'

local configer = {}
local config = {}
Expand All @@ -16,65 +19,102 @@ local defconfig = {
standard = 'default',
cpu_bits = 64
}
metamagic.setmetaindex(config, defconfig)

local function create_parser(argv)
local function convert_define(param)
if param:match('^%a[_%w]*$') then
param = param .. ' = true'
end
local f, err = compat.load(param, '@define', "t")
if err then
return nil, string.format("failed parsing define '%s':\n %s", param, err)
end
return param
end

local function convert_add_path(param)
if not fs.isdir(param) and not param:match('%?') then
return nil, string.format("path '%s' is not a valid directory", param)
end
return param
end

local function merge_configs(conf, pconf)
for k,v in pairs(pconf) do
if conf[k] == nil then
conf[k] = v
elseif type(conf[k]) == 'table' and type(v) == 'table' and #v > 0 then
tabler.insertvalues(conf[k], 1, v)
end
end

if conf.lib_path then
local ss = sstream()
for _,libpath in ipairs(conf.lib_path) do
if libpath:find('?') then
ss:add(libpath)
else
ss:add(libpath .. '/?.nelua;')
ss:add(libpath .. '/?/init.nelua;')
end
end
ss:add(conf.path)
conf.path = ss:tostring()
end
end

local function action_print_config(options) --luacov:disable
merge_configs(options, defconfig)
console.info(pretty.write(options))
os.exit(0)
end --luacov:enable

local function create_parser(args)
local argparser = argparse("nelua", "Nelua 0.1")
local d = defconfig
argparser:flag('-c --compile', "Compile the generated code only")
argparser:flag('-b --compile-binary', "Compile the generated code and binaries only")
argparser:flag('-c --compile', "Compile the generated code only", defconfig.compile)
argparser:flag('-b --compile-binary', "Compile the generated code and binaries only", defconfig.compile_binary)
--argparser:option('-o --output', "Output file when compiling")
argparser:flag('-e --eval', 'Evaluate string code from input', d.eval)
argparser:flag('-l --lint', 'Only check syntax errors', d.lint)
argparser:flag('-q --quiet', "Don't print any information while compiling", d.quiet)
argparser:flag('-a --analyze', 'Analyze the code only', d.analyze)
argparser:flag('-r --release', 'Release mode build', d.release)
argparser:flag('-t --timing', 'Debug compile timing information', d.timing)
argparser:flag('--no-cache', "Don't use any cached compilation", d.no_cache)
argparser:flag('-e --eval', 'Evaluate string code from input', defconfig.eval)
argparser:flag('-l --lint', 'Only check syntax errors', defconfig.lint)
argparser:flag('-q --quiet', "Don't print any information while compiling", defconfig.quiet)
argparser:flag('-a --analyze', 'Analyze the code only', defconfig.analyze)
argparser:flag('-r --release', 'Release mode build', defconfig.release)
argparser:flag('-t --timing', 'Debug compile timing information', defconfig.timing)
argparser:flag('--no-cache', "Don't use any cached compilation", defconfig.no_cache)
argparser:option('-D --define', 'Define values in the preprocessor')
:count("*"):convert(convert_define, defconfig.define)
argparser:option('-g --generator', "Code generator to use (lua/c)", defconfig.generator)
argparser:option('-d --standard', "Source standard (default/luacompat)", defconfig.standard)
argparser:option('-p --path', "Set module search path", defconfig.path)
argparser:option('-L --lib-path', "Add module search path", defconfig.lib_path)
:count("*"):convert(convert_add_path)
argparser:option('--cc', "C compiler to use", defconfig.cc)
argparser:option('--cpu-bits', "Target CPU architecture bit size (64/32)", defconfig.cpu_bits)
argparser:option('--cflags', "Additional C flags to use on compilation", defconfig.cflags)
argparser:option('--cache-dir', "Compilation cache directory", defconfig.cache_dir)
argparser:option('--lua', "Lua interpreter to use when runnning", defconfig.lua)
argparser:option('--lua-version', "Target lua version for lua generator", defconfig.lua_version)
argparser:option('--lua-options', "Lua options to use when running", defconfig.lua_options)
argparser:flag('--print-ast', 'Print the AST only')
argparser:flag('--print-analyzed-ast', 'Print the analyzed AST only')
argparser:flag('--print-code', 'Print the generated code only')
argparser:option('-D --define', 'Define values in the preprocessor'):count("*"):convert(function(param)
if param:match('^%a[_%w]*$') then
param = param .. ' = true'
end
local f, err = compat.load(param, '@define', "t")
if err then
return nil, string.format("failed parsing define '%s':\n %s", param, err)
end
return param
end)
argparser:option('-g --generator', "Code generator to use (lua/c)", d.generator)
argparser:option('-d --standard', "Source standard (default/luacompat)", d.standard)
argparser:option('--cc', "C compiler to use", d.cc)
argparser:option('--cpu-bits', "Target CPU architecture bit size (64/32)", d.cpu_bits)
argparser:option('--cflags', "Additional C flags to use on compilation", d.cflags)
argparser:option('--cache-dir', "Compilation cache directory", d.cache_dir)
argparser:option('--path', "Nelua modules search path", d.path)
argparser:option('--lua', "Lua interpreter to use when runnning", d.lua)
argparser:option('--lua-version', "Target lua version for lua generator", d.lua_version)
argparser:option('--lua-options', "Lua options to use when running", d.lua_options)
argparser:argument("input", "Input source file"):action(function(options, _, v)
argparser:flag('--print-config', "Print config variables only"):action(action_print_config)
argparser:argument("input", "Input source file")
:action(function(options, _, v)
-- hacky way to stop handling options
local index = tabler.ifind(argv, v) + 1
local found_stop_index = tabler.ifind(argv, '--')
if not found_stop_index or found_stop_index > index-1 then
table.insert(argv, index, '--')
if v then
local index = tabler.ifind(args, v) + 1
local found_stop_index = tabler.ifind(args, '--')
if not found_stop_index or found_stop_index > index-1 then
table.insert(args, index, '--')
end
options.input = v
end
options.input = v
end)
argparser:argument("runargs"):args("*")
return argparser
end

local function get_path(data_path)
local libdir = fs.join(data_path, 'lib')
return
fs.join(libdir,'?.nelua')..';'..
fs.join(libdir,'?','init.nelua')..';'..
fs.join('.','?.nelua')..';'..
fs.join('.','?','init.nelua')
end

local function get_cc()
local envcc = os.getenv('CC')
if envcc and fs.findbinfile(envcc) then return envcc end
Expand All @@ -88,13 +128,24 @@ local function get_cc()
return cc
end

local function get_search_path(datapath)
local path = os.getenv('NELUA_PATH')
if path then return path end
local libdir = fs.join(datapath, 'lib')
path = fs.join(libdir,'?.nelua')..';'..
fs.join(libdir,'?','init.nelua')..';'..
fs.join('.','?.nelua')..';'..
fs.join('.','?','init.nelua')
return path
end

function configer.parse(args)
defconfig.data_path = fs.getdatapath(args[0])
defconfig.path = get_search_path(defconfig.data_path)
local argparser = create_parser(tabler.copy(args))
local ok, options = argparser:pparse(args)
except.assertraise(ok, options)
config.data_path = fs.getdatapath(args[0])
config.path = get_path(config.data_path)
metamagic.setmetaindex(options, defconfig)
merge_configs(options, defconfig)
metamagic.setmetaindex(config, options, true)
return config
end
Expand All @@ -103,12 +154,8 @@ function configer.get()
return config
end

local function init_default_configs()
defconfig.data_path = fs.getdatapath()
defconfig.path = get_path(defconfig.data_path)
defconfig.cc = get_cc()
defconfig.cflags = os.getenv('CFLAGS') or ''
metamagic.setmetaindex(config, defconfig)
function configer.get_default()
return defconfig
end

local function load_configs(configfile)
Expand All @@ -117,8 +164,16 @@ local function load_configs(configfile)
tabler.update(defconfig, homeconfig)
end

local function init_default_configs()
defconfig.data_path = fs.getdatapath()
defconfig.path = get_search_path(defconfig.data_path)
defconfig.cc = get_cc()
defconfig.cflags = os.getenv('CFLAGS') or ''

load_configs(fs.getuserconfpath('neluacfg.lua'))
load_configs('.neluacfg.lua')
end

init_default_configs()
load_configs(fs.getuserconfpath('neluacfg.lua'))
load_configs('.neluacfg.lua')

return configer
9 changes: 8 additions & 1 deletion nelua/utils/fs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ local plfile = require 'pl.file'
local plpath = require 'pl.path'
local except = require 'nelua.utils.except'
local stringer = require 'nelua.utils.stringer'

local fs = {}

fs.join = plpath.join
Expand All @@ -15,6 +14,11 @@ fs.getfiletime = plfile.modified_time
fs.isfile = plpath.isfile
fs.gettmpname = plpath.tmpname
fs.deletefile = plfile.delete
fs.exists = plpath.exists

function fs.isdir(path)
return fs.exists(path) and not fs.isfile(path)
end

function fs.ensurefilepath(file)
local outdir = plpath.dirname(file)
Expand Down Expand Up @@ -63,12 +67,15 @@ end
function fs.findmodulefile(name, path)
name = name:gsub('%.', plpath.sep)
local paths = stringer.split(path, ';')
local triedpaths = {}
for _,trypath in ipairs(paths) do
trypath = trypath:gsub('%?', name)
if plpath.isfile(trypath) then
return fs.abspath(trypath)
end
table.insert(triedpaths, trypath)
end
return nil, "\tno file '" .. table.concat(triedpaths, "'\n\tno file '") .. "'"
end

local path_pattern = string.format('[^%s]+', plpath.dirsep)
Expand Down
9 changes: 6 additions & 3 deletions nelua/utils/tabler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ function tabler.ifind(t, val, idx)
end

-- insert values
function tabler.insertvalues(t, st)
local tlen = #t
function tabler.insertvalues(t, pos, st)
if not st then
st = pos
pos = #t + 1
end
for i=1,#st do
t[tlen + i] = st[i]
t[pos + i - 1] = st[i]
end
return t
end
Expand Down
28 changes: 28 additions & 0 deletions spec/08-runner_spec.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'busted.runner'()

local assert = require 'spec.tools.assert'
local configer = require 'nelua.configer'

describe("Nelua runner should", function()

Expand Down Expand Up @@ -73,4 +74,31 @@ it("define option", function()
assert.run_error('-D1 examples/helloworld.nelua', "failed parsing define '1'")
end)

it("configure module search paths", function()
assert.run({'-L', './examples', '--eval',[[
require 'helloworld'
]]}, 'hello world')
assert.run_error({'--eval',[[
require 'helloworld'
]]}, "module 'helloworld' not found")

assert.run_error({'-L', './examples/invalid', '--analyze', '--eval',[[--nothing]]}, 'is not a valid directory')
assert.run({'-L', './examples/?.lua', '--analyze', '--eval',[[
## assert(config.path:find('examples'))
]]})

local defconfig = configer.get_default()
local oldlibpath = defconfig.lib_path
defconfig.lib_path = {'/tests'}
assert.run({'-L', './examples', '--analyze', '--eval',[[
## assert(config.path:find('examples'))
## assert(config.path:find('tests'))
]]})
defconfig.lib_path = oldlibpath

assert.run({'--path', './examples', '--analyze', '--eval',[[
## assert(config.path == './examples')
]]})
end)

end)

0 comments on commit 9683c40

Please sign in to comment.