Last active
September 12, 2015 14:56
-
-
Save HyroVitalyProtago/7802bd00e7d56d480379 to your computer and use it in GitHub Desktop.
Required is a little framework for Codea to manage local and gist dependencies
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
automatically created by Required (0.0.31) from HyroVitalyProtago | |
[email protected] | |
https://gist.github.com/7802bd00e7d56d480379 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return (function() | |
return { | |
get = function() | |
local ret, content = pcall(function() | |
return assert(loadstring(readProjectTab('Required:local')))() | |
end) | |
return (ret and type(content) == 'table' and content) or {} | |
end, | |
save = function(t) | |
return saveProjectTab('Required:local', 'return json.decode([[' .. json.encode(t, {indent=true}) .. ']])') | |
end | |
} | |
end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return (function(prnt, err, __preloads) | |
local Loader = required.import 'Loader' | |
local Env = required.import 'Env' | |
local Data = required.import 'Data' | |
local GistLoader = required.import 'GistLoader' | |
local Version = required.import 'Version' | |
local context = {} | |
-- todo don't just use required.import, use a cached base in function of projects, dependencies, ... | |
local function run(name, content) | |
if name:sub(1,1) == '#' then -- file for the name | |
elseif name == "package.json" then -- todo? | |
else | |
local moduleName = table.peek(context)..(name:match('(%w-).lua')) | |
local f = assert(loadstring(content)) | |
local env = setmetatable({}, {__index = function(env, o) | |
if o == "required" then | |
return { -- override required module | |
path = function() print("required.path disable") end, | |
import = function(m) | |
if m:find("%w-%.%w-%.%w+") then -- external module | |
err("not implemented yet") | |
else | |
return required.import(table.peek(context) .. m) | |
end | |
end | |
} | |
else | |
return _G[o] | |
end | |
end}) | |
f = Env.set(f, env) -- run f with another environnement | |
__preloads[moduleName] = { f, env } | |
end | |
end | |
-- todo download librairies for a faster setup phase | |
local gl = GistLoader(run) | |
return Loader(function(self, data, callback) | |
local typ, owner, project, version = data:match("(.-)@(.-):(.-):(.*)") | |
if not typ then -- lazy verification for owner, project, version | |
err("invalid dependency format\n\tformat: typ@owner:project:version\n\texample: gist@HyroVitalyProtago:Btn:*)") | |
end | |
prnt(string.format("required.fetch(%s, %s, %s, %s)", typ, owner, project, version)) | |
-- get current repository (stocked in local) | |
local rdata = Data.get() | |
if rdata.name == owner then -- project self owned | |
local prj = rdata.repo[owner].projects[project] or err("project not found in your repository...") | |
table.push(context, owner .. "." .. project .. ".") | |
gl:push({id=prj.id, sha=Version.getShaOfBetterVersion(version, prj.versions)}):load(callback) | |
else | |
err("Not implemented yet") | |
end | |
end) | |
end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return (function() | |
local function findenv(f) | |
local level = 1 | |
repeat | |
local name, value = debug.getupvalue(f, level) | |
if name == '_ENV' then | |
return level, value | |
end | |
level = level + 1 | |
until name == nil | |
return nil | |
end | |
local function getfenv(f) | |
return(select(2, findenv(f)) or _G) | |
end | |
local function setfenv(f, t) | |
local level = findenv(f) | |
if level then | |
debug.setupvalue(f, level, t) | |
end | |
return f | |
end | |
return { | |
get = getfenv, | |
set = setfenv | |
} | |
end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return (function(prnt, err) | |
local Data = required.import 'Data' | |
local Pkg = required.import 'Pkg' | |
local GistLoader = required.import 'GistLoader' | |
local Version = required.import 'Version' | |
local function saveTab(name, content) | |
if name:sub(1,1) == '#' then -- file for the name | |
elseif name == "package.json" then | |
Pkg.save(json.decode(content)) | |
else | |
saveProjectTab(name:match('(%w-).lua'), content) | |
end | |
end | |
local gl = GistLoader(saveTab) | |
local function fetch(dependency) -- typ@owner:project:version | |
local typ, owner, project, version = dependency:match("(.-)@(.-):(.-):(.*)") | |
if not typ then -- lazy verification for owner, project, version | |
err("invalid dependency format\n\tformat: typ@owner:project:version\n\texample: gist@HyroVitalyProtago:Btn:*)") | |
end | |
prnt(string.format("required.fetch(%s, %s, %s, %s)", typ, owner, project, version)) | |
-- get current repository (stocked in local) | |
local rdata = Data.get() | |
if rdata.name == owner then -- project self owned | |
local prj = rdata.repo[owner].projects[project] or err("project not found in your repository...") | |
gl:push({id=prj.id, sha=Version.getShaOfBetterVersion(version, prj.versions)}):load(function() | |
close() -- or restart? | |
end) | |
else | |
err("Not implemented yet") | |
end | |
end | |
return fetch | |
end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return (function() | |
local Gist = { | |
debug = true, | |
location = "https://api.github.com/", | |
accept = "application/vnd.github.v3+json", | |
token = readGlobalData("gist"), | |
list = {}, | |
comments = {} | |
} | |
local function request(url, callback, opts) | |
opts = opts or {} | |
opts.headers = opts.headers or {} | |
-- set default headers | |
opts.headers.Accept = opts.headers.Accept or Gist.accept | |
opts.headers.Authorization = opts.headers.Authorization or (Gist.token and "token " .. Gist.token) | |
-- json encode data | |
opts.data = opts.data and json.encode(opts.data) | |
-- set path absolute | |
url = Gist.location .. url | |
-- debug mode | |
if Gist.debug then print("[gist]", opts.method or "GET ", url) end | |
-- success and fail callbacks | |
local success = type(callback) == "table" and callback.success or callback | |
local fail = type(callback) == "table" and callback.fail or alert | |
http.request(url, function(data, status, headers) | |
success(json.decode(data), status, headers) | |
end, fail, opts) | |
end | |
local function method(name) | |
return function(t) | |
t = t or {} | |
t.method = name | |
return t | |
end | |
end | |
local post = method("POST") | |
local put = method("PUT") | |
local delete = method("DELETE") | |
local patch = method("PATCH") | |
local function required(tbl, ...) -- assert if a required field is missing | |
tbl = tbl or {} | |
for _,field in ipairs({...}) do | |
assert(tbl[field], "required field missing : " .. field) | |
end | |
return tbl | |
end | |
local function data(args, ...) | |
local tbl = {} | |
for _,v in ipairs({...}) do | |
tbl[v] = args[v] | |
end | |
return tbl | |
end | |
-- GITHUB TIMESTAMP (YYYY-MM-DDTHH:MM:SSZ) to os.time | |
function Gist.gtimestamp(githubTime) | |
githubTime = githubTime:sub(1, #githubTime-1) -- remove Z | |
githubTime = Utilities.explode("T", githubTime) | |
githubTime[1] = Utilities.explode("-", githubTime[1]) | |
githubTime[2] = Utilities.explode(":", githubTime[2]) | |
return os.time({ | |
year = tonumber(githubTime[1][1]), | |
month = tonumber(githubTime[1][2]), | |
day = tonumber(githubTime[1][3]), | |
hour = tonumber(githubTime[2][1]), | |
min = tonumber(githubTime[2][2]), | |
sec = tonumber(githubTime[2][3]) | |
}) | |
end | |
-- List gists | |
function Gist.user(callback) | |
request("user", callback) | |
end | |
-- List gists | |
function Gist.list.user(user, callback) | |
request("users/"..user.."/gists", callback) | |
end | |
function Gist.list.all(callback) -- return all public gists if called anonymously | |
request("gists", callback) | |
end | |
function Gist.list.allPublic(callback) | |
request("gists/public", callback) | |
end | |
function Gist.list.starred(callback) | |
request("gists/starred", callback) | |
end | |
-- Get a single gist | |
function Gist.get(id, callback) | |
request("gists/"..id, callback) | |
end | |
-- Get a single revision | |
function Gist.revision(id, sha, callback) | |
request("gists/"..id.."/"..sha, callback) | |
end | |
-- Create a gist | |
function Gist.create(arg, callback) | |
request("gists", callback, post({ data = data(required(arg, "public", "files"), "public", "files", "description") })) | |
end | |
-- Edit a gist | |
function Gist.edit(id, arg, callback) | |
request("gists/"..id, callback, patch({ data = data(required(arg, "files"), "files", "description") })) | |
end | |
-- Star a gist | |
function Gist.star(id, callback) | |
request("gists/"..id.."/star", callback, put()) | |
end | |
-- Unstar a gist | |
function Gist.unstar(id, callback) | |
request("gists/"..id.."/star", callback, delete()) | |
end | |
-- Check if a gist is starred | |
function Gist.checkStar(id, callback) | |
request("gists/"..id.."/star", callback) | |
end | |
-- Fork a gist | |
function Gist.fork(id, callback) | |
request("gists/"..id.."/forks", callback, post()) | |
end | |
-- Delete a gist | |
function Gist.delete(id, callback) | |
request("gists/"..id, callback, delete()) | |
end | |
--- GISTS COMMENTS --- | |
-- List comments on a gist | |
function Gist.comments.list(gist_id, callback) | |
request("gists/"..gist_id.."/comments", callback) | |
end | |
-- Get a single comment | |
function Gist.comments.get(gist_id, id, callback) | |
request("gists/"..gist_id.."/comments/"..id, callback) | |
end | |
-- Create a comment | |
function Gist.comments.create(gist_id, body, callback) | |
request("gists/"..gist_id.."/comments", callback, post({ data={ body=body }})) | |
end | |
-- Edit a comment | |
function Gist.comments.edit(gist_id, id, body, callback) | |
request("gists/"..gist_id.."/comments/"..id, callback, patch({ data={ body=body }})) | |
end | |
-- Delete a comment | |
function Gist.comments.delete(gist_id, id, callback) | |
request("gists/"..gist_id.."/comments/"..id, callback, delete()) | |
end | |
return Gist | |
end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return (function(prnt, err) | |
local Gist = required.import 'Gist' | |
local Loader = required.import 'Loader' | |
local GistLoader = class(Loader) -- iterator on gist files | |
-- todo replace name by infos | |
function GistLoader:init(gf) -- function called on each gist file (name, content) | |
self.gf = gf | |
Loader.init(self, function(self, data, callback) | |
local function success(gdata, status, headers) | |
for k,v in pairs(gdata.files) do | |
if not v.truncated then | |
self.gf(k, v.content) | |
else | |
self:push({k=k, raw_url=gdata.raw_url}) | |
end | |
end | |
callback() | |
end | |
if data.sha then | |
Gist.revision(data.id, data.sha, { success = success, fail = err }) | |
elseif data.id then | |
Gist.get(data.id, { success = success, fail = err }) | |
elseif data.raw_url then | |
http.request(data.raw_url, function(content) | |
self.gf(data.k, content) | |
callback() | |
end, err) | |
end | |
end) | |
end | |
return GistLoader | |
end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return (function() | |
--[[ Downloader | |
function setup() | |
http.request("https://api.github.com/gists/7802bd00e7d56d480379", function(data, status, headers) | |
http.request(json.decode(data).files["Installer.lua"].raw_url, function(data) | |
(assert(loadstring(data))()()).download() | |
end, alert) | |
end, alert) | |
end | |
--]] | |
return { | |
install = function() -- todo | |
-- 1. Generate a token with gist access | |
-- https://help.github.com/articles/creating-an-access-token-for-command-line-use/ | |
--> Response get username and show a message like (welcome <username>!) | |
-- 2. Required generate a repository as a gist for store access of public projects (fork the main repository) | |
-- 3. You can use this project to navigate in public repositories (and run projects!) | |
-- 4. When you create a project, don't forget to add Required as a dependency | |
-- 5. When you launch a project for the first time, you need to configure it (name, description) | |
-- - you can create manualy a package tab with informations in it | |
-- - you can use parameters facilities when the project is launched | |
-- 6. All tabs should be like this: | |
-- return (function() | |
-- local dependency = required.import 'dependency' | |
-- | |
-- local module = {} | |
-- ... | |
-- return module (optional) | |
-- end) | |
-- > This is useful because there is no problems with tab orders | |
-- ... | |
end, | |
download = function() | |
-- set up draw for waiting | |
font("HelveticaNeue-Light") | |
fontSize(128) | |
fill(50) | |
function draw() | |
background(200) | |
text("{ Loading }", WIDTH*.5, HEIGHT*.5) | |
end | |
-- hard coded loader | |
local stack = {} -- stack for truncated files (name, raw_url) | |
local function download_raw() | |
if not next(stack) then -- if there is no truncated file => done! | |
return close() -- restart ? | |
end | |
local ku = table.remove(stack, #stack) -- get the next truncated file | |
http.request(ku[2], function(data) | |
saveProjectTab(ku[1]:match('(%w-).lua'), data) -- save it | |
download_raw() -- download the next | |
end, alert) | |
end | |
local function success(data, status, headers) | |
-- 2. for each file in data.files, if no truncated, save it | |
data = json.decode(data) | |
for k,v in pairs(data.files) do | |
if k:sub(1,1) == '#' then -- file for the name | |
elseif k == 'package.json' then -- special case for package | |
saveProjectTab('package', 'return json.decode([[' .. v.content .. ']])') -- todo check for truncated | |
elseif not v.truncated then | |
saveProjectTab(k:match('(%w-).lua'), v.content) | |
else | |
stack[#stack+1] = {k, v.raw_url} | |
end | |
end | |
-- 3. download all truncated files | |
download_raw() | |
end | |
-- 1. Get Required project | |
http.request("https://api.github.com/gists/7802bd00e7d56d480379", success, alert) | |
end | |
} | |
end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return (function() | |
local Loader = class() | |
function Loader:init(f) | |
self.f = f -- function(data, callback) | |
self.data = {} | |
end | |
function Loader:load(callback) | |
if not table.any(self.data) then | |
return callback() | |
end | |
self:f(table.pop(self.data), function() | |
self:load(callback) | |
end) | |
end | |
function Loader:push(o) | |
table.push(self.data, o) | |
return self | |
end | |
function Loader:pushAll(tbl) | |
for _,o in ipairs(tbl) do | |
table.push(self.data, o) | |
end | |
return self | |
end | |
return Loader | |
end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return (function() | |
local Data = required.import 'Data' | |
local data = Data.get() | |
local name = "> " .. (data.name or "unauthenticated user") .. " <" | |
function setup() | |
-- displayMode(FULLSCREEN) | |
--[[ | |
required.repo(function(repo) | |
data = repo.projects | |
fontSize(32) | |
function draw() | |
background(200) | |
text(table.tostring(data), WIDTH*.5, HEIGHT*.5) | |
end | |
end) | |
--]] | |
-- todo | |
-- - projects manager (list, details, run, update, comment, star, ...) | |
-- - dowloaded dependencies manager (list, details, remove, update) | |
font("HelveticaNeue-UltraLight") | |
end | |
function draw() | |
background(200) | |
fontSize(128) | |
text("{ Required }", WIDTH*.5, HEIGHT*.55) | |
fontSize(64) | |
text(name, WIDTH*.5, HEIGHT*.4) | |
end | |
end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"author":"HyroVitalyProtago", | |
"name":"Required", | |
"ignore":["local"], | |
"description":"Required is a little framework for Codea to manage local and gist dependencies", | |
"id":"7802bd00e7d56d480379", | |
"codea":"2.3.1", | |
"version":"0.0.31" | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return (function(prnt, err) | |
-- todo licence [MIT, GPL, Apache, NoLicense], assets (, images & videos ?) | |
-- README.md, ... | |
-- get content of package tab in project | |
local function get(project) | |
local ret, content = pcall(function() | |
local data = readProjectTab((project and project .. ':' or '')..'package') | |
return assert(loadstring(data))() | |
end) | |
return (ret and type(content) == 'table' and content) -- todo assert(name, description, version, ...) | |
end | |
-- save updated package info | |
local keyorder = {"id", "name", "description", "author", "version", "codea", "ignore", "dependencies"} | |
local function save(t) | |
saveProjectTab('package', 'return json.decode([[' .. json.encode(t, {indent=true, keyorder=keyorder}) .. ']])') | |
end | |
-- export pkg for gist into files table | |
local function export(files) | |
local rpkg = get('Required') | |
local pkg = get() -- last saved package | |
files['# ' .. pkg.name .. ' ' .. pkg.version] = { | |
content = 'automatically created by Required (' .. rpkg.version .. ') from HyroVitalyProtago\n' | |
.. '[email protected]\n' | |
.. 'https://gist.github.com/' .. rpkg.id | |
} | |
files['package.json'] = { content=json.encode(pkg, {indent=true}) } | |
return files | |
end | |
return { | |
get = get, | |
save = save, | |
export = export | |
} | |
end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return (function(prnt, err) | |
local Data = required.import 'Data' | |
local Gist = required.import 'Gist' | |
-- todo transform content in trie structure | |
local function format(repo) | |
return repo | |
end | |
local function get(callback) | |
local repo_id = Data.get().repo[data.name].id | |
Gist.get(repo_id, { | |
success = function(data) | |
local jsn = data.files["repo.json"] | |
if not jsn.truncated then | |
callback(format(json.decode(jsn.content))) | |
else | |
http.request(data.raw_url, function(data) | |
callback(format(json.decode(data))) | |
end, err) | |
end | |
end, | |
fail = function() | |
err("can't get the repository...") | |
end | |
}) | |
end | |
return { | |
get = get | |
} | |
end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
required = {} | |
--[[ | |
interface required { | |
void path(string project) -- add the project to the path | |
table import(string project) -- import each file of a project, return a table with filename as keys | |
void fetch(string gist_id) -- download the gist in the current project | |
[todo] table list() -- return all downloaded project | |
void repo(callback(table)) -- return the repository | |
ui { | |
void create() -- create the gist and link-it (id in the package file) | |
void update() -- search new repositories and add-it in the repository | |
-- (available only if the project has been created) | |
void open() -- open the project in the in-app browser | |
void patch() -- push a new revision of the project | |
void minor() -- push a new revision of the project | |
void major() -- push a new revision of the project | |
[todo] void release() -- add the current version into repository (a published version shouldn't be removed) | |
} | |
} | |
--]] | |
-- local mime = require 'mime' | |
-- local b64 = { encode=mime.b64, decode=mime.unb64 } | |
local DBG = true | |
local app = {} -- load all functions before launch the project | |
local __setup -- setup of the running application | |
-- prepend [required] in print | |
local function prnt(...) | |
if DBG then print("[required]", ...) end | |
end | |
-- prepend [required] in errors | |
local function err(...) | |
error("[required] "..table.concat({...}, ' ')) | |
end | |
-- easier addition to package.path | |
function required.path(p) | |
package.path = package.path .. ";" .. os.getenv('HOME') .. '/Documents/' .. p .. '.codea/?.lua' | |
end | |
-- add Required to the path | |
required.path 'Required' | |
-- contains loaded modules | |
local __modules = {} | |
-- contains all preloaded modules for dependencies | |
local __preloads = {} | |
-- based on require for loading libraries | |
function required.import(module, ...) -- args can be used only for the first load (useful for partial modules) | |
if not __modules[module] then -- first load of | |
if module:find(".-%..-%..+") then -- external dependency | |
local res = __preloads[module][1]() | |
__modules[module] = res and res() or __preloads[module][2] -- module or environnement | |
else -- internal dependency | |
__modules[module] = (require(module))(...) or true -- run file only one time (even if it return nothing) | |
end | |
end | |
return __modules[module] | |
end | |
required.import 'Table' -- globals | |
local Gist = required.import 'Gist' | |
local Data = required.import 'Data' | |
local Env = required.import 'Env' | |
local Version = required.import 'Version' | |
local Dependencies = required.import('Dependencies', prnt, err, __preloads) | |
local Pkg = required.import('Pkg', prnt, err) | |
local Repo = required.import('Repo', prnt, err) | |
required.repo = Repo.get | |
required.fetch = required.import('Fetch', prnt, err) | |
-- create a new gist | |
local function create(pkg, files, callback) | |
Gist.create({ | |
public = true, | |
description = pkg.description, | |
files = Pkg.export(files) -- files with pkg informations | |
}, { | |
success = function(data, status, headers) | |
prnt('gist successfully created!') | |
prnt('id: ', data.id) | |
pkg.id = data.id | |
Pkg.save(pkg) | |
-- todo edit the package on gist for adding the id | |
callback(data, status, headers) | |
end, | |
fail = function(data, status, headers) | |
prnt('gist creation failed') | |
err(data, status, headers) | |
end | |
}) | |
end | |
-- edit a gist | |
local function edit(pkg, files, callback) | |
-- fetch gist for remove old files | |
Gist.get(pkg.id, { | |
success = function(data, status, headers) | |
for k,_ in pairs(data.files) do | |
if not files[k] then | |
files[k] = json.null | |
prnt('remove file: '..k) | |
end | |
end | |
-- todo check for differences, if nothing is different, alert "UP-TO-DATE" | |
Gist.edit(pkg.id, { | |
description = pkg.description, | |
files = Pkg.export(files) -- files with pkg informations | |
}, { | |
success = function(data, status, headers) | |
prnt('gist successfully edited!') | |
callback(data, status, headers) | |
end, | |
fail = function(data, status, headers) | |
prnt('gist edition failed') | |
err(data, status, headers) | |
end | |
}) | |
end, | |
fail = function(data, status, headers) | |
err(data, status, headers) | |
end | |
}) | |
end | |
-- set up parameters | |
local function parameters() | |
local pkg = Pkg.get() | |
if not pkg then return end -- disable parameters | |
local files = {} | |
local tabs = listProjectTabs() | |
for _,tab in pairs(tabs) do | |
if tab ~= 'package' and (not pkg.ignore or not table.mem(tab, pkg.ignore)) then -- skip package and ignored file | |
files[tab .. '.lua'] = { content=readProjectTab(tab) } | |
end | |
end | |
local function callback(data, status, headers) | |
parameter.action('Open', function() | |
openURL(data.html_url, true) | |
end) | |
end | |
if pkg.id then -- todo and differences? | |
parameter.action('Open', function() openURL('https://gist.github.com/'..pkg.id, true) end) | |
for i,v in ipairs({"Patch", "Minor", "Major"}) do | |
parameter.action(v, function() | |
parameter.clear() | |
pkg.version = Version[v:lower().."Up"](pkg.version) | |
Pkg.save(pkg) | |
edit(pkg, files, callback) | |
end) | |
end | |
else | |
parameter.action('Create', function() | |
parameter.clear() | |
create(pkg, files, callback) | |
end) | |
end | |
end | |
-- launch application | |
local function launch() | |
prnt("set up parameters") | |
parameters() | |
prnt("launch app") | |
setmetatable(_G, nil) -- remove protection on _G | |
for k,v in pairs(app) do _G[k] = v end -- load all globals declared | |
collectgarbage() -- free all unused memory allocated for required | |
if __setup then | |
__setup() | |
else | |
local main = required.import 'Main' | |
if main and type(main) == "table" and main.setup then -- if main is also a module | |
main.setup() | |
elseif setup then -- if main is a module, but use globals | |
setup() -- never reached statement ? | |
end | |
end | |
end | |
-- setup of required | |
local function setup() | |
displayMode(OVERLAY) | |
prnt("setup") | |
if not Data.get().first then -- first load of Required | |
-- todo display welcome, ... | |
Data.save({ | |
first = true, | |
-- todo ... | |
}) | |
end | |
local info = Pkg.get() | |
if not info then | |
-- display: package file required for version control | |
return launch() | |
end | |
-- prnt("save package information") | |
for _,k in pairs({'Author', 'Description'}) do | |
saveProjectInfo(k, info[k:lower()]) | |
end | |
-- todo check no redondances or conflicts (versions, ...) | |
Dependencies:pushAll(info.dependencies or {}):load(launch) | |
end | |
local function draw() | |
background(200) | |
font("HelveticaNeue-UltraLight") | |
fontSize(128) | |
fill(50) | |
text("{ Required }", WIDTH*.5, HEIGHT*.5) | |
end | |
-- protect the setup function of required | |
setmetatable(_G, { | |
__index = function(tbl, k) | |
if k == "setup" then | |
return setup | |
elseif k == "draw" then | |
return draw | |
end | |
end, | |
__newindex = function(tbl, k, v) | |
prnt('set global',k,v) | |
if k == "setup" then | |
__setup = v | |
else | |
app[k] = v | |
end | |
end | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return (function() | |
-- todo test all, remove unused/useless | |
-- todo real functionnal paradigm (return function if not all parameters) or change parameters orders (table in first) | |
-- table to string | |
function table.tostring(t, i) | |
local acc = "" | |
i = i or 0 | |
for k, v in pairs(t) do | |
local f = "\n" .. string.rep(" ", i) .. k .. ": " | |
if type(v) == "table" then | |
acc = acc .. f .. table.tostring(v, i+1) | |
else | |
acc = acc .. f .. tostring(v) | |
end | |
end | |
if not next(t) then | |
acc = acc .. "{}" | |
end | |
return acc | |
end | |
-- print entire table | |
function table.print(t) | |
print(table.tostring(t)) | |
end | |
-- deepcopy on table | |
function table.deepcopy(orig) | |
local orig_type, copy = type(orig) | |
if orig_type == 'table' then | |
copy = {} | |
for orig_key, orig_value in next, orig, nil do | |
copy[table.deepcopy(orig_key)] = table.deepcopy(orig_value) | |
end | |
setmetatable(copy, table.deepcopy(getmetatable(orig))) | |
else -- number, string, boolean, etc | |
copy = orig | |
end | |
return copy | |
end | |
-- return the real size of a table based on pairs | |
function table.count(t) | |
local i = 0 | |
for _ in pairs(t) do | |
i = i + 1 | |
end | |
return i | |
end | |
-- return the real size of a table based on ipairs | |
function table.length(t) | |
local i = 0 | |
for _ in pairs(t) do | |
i = i + 1 | |
end | |
return i | |
end | |
-- Stack utilities | |
function table.push(tbl, o) tbl[#tbl+1] = o end | |
function table.pop(tbl) return table.remove(tbl, #tbl) end | |
function table.peek(tbl) return tbl[#tbl] end | |
function table.any(tbl) | |
for _ in ipairs(tbl) do | |
return true | |
end | |
return false | |
end | |
-- Merge one or more arrays | |
-- If the input arrays have the same string keys, | |
-- then the later value for that key will overwrite the previous one. | |
-- If, however, the arrays contain numeric keys, | |
-- the later value will not overwrite the original value, but will be appended. | |
-- Values in the input array with numeric keys will be renumbered with | |
-- incrementing keys starting from zero in the result array. | |
function table.merge(...) | |
local acc = {} | |
for _,t in pairs({...}) do | |
for k,v in pairs(t) do | |
if type(k) == "number" then | |
acc[#acc+1] = v | |
else | |
acc[k] = v | |
end | |
end | |
end | |
return acc | |
end | |
-- Exchanges all keys with their associated values | |
function table.flip(tbl) | |
local acc = {} | |
for k, v in pairs(tbl) do | |
acc[v] = k | |
end | |
return acc | |
end | |
-- table with OCaml functionnal paradigm | |
-- val rev : 'a list -> 'a list | |
-- List reversal. | |
function table.rev(tbl) | |
local ret = {} | |
for i = 1,math.floor(#tbl*.5) do | |
ret[#tbl-i+1] = tbl[i] | |
end | |
return ret | |
end | |
-- val append : 'a list -> 'a list -> 'a list | |
-- Append the second list to the first. | |
function table.append(tbl1, tbl2) | |
local ret = table.deepcopy(tbl1) | |
for _,v in ipairs(tbl2) do | |
ret[#ret+1] = v | |
end | |
return ret | |
end | |
-- val rev_append : 'a list -> 'a list -> 'a list | |
-- equivalent to table.append(table.rev(tbl1), table.rev(tbl2)) | |
function table.rev_append(tbl1, tbl2) -- todo more efficient | |
return table.append(table.rev(tbl1), table.rev(tbl2)) | |
end | |
-- val flatten : 'a list list -> 'a list | |
-- Concatenate a list of lists. The elements of the argument are all concatenated together (in the same order) to give the result. | |
function table.flatten(tbls) | |
local ret = {} | |
for i = 1,#tbls do | |
table.append(ret, tbls[i]) | |
end | |
return ret | |
end | |
-- Iterators -- | |
-- val iter : ('a -> unit) -> 'a list -> unit | |
-- List.iter f [a1; ...; an] applies function f in turn to a1; ...; an. It is equivalent to begin f a1; f a2; ...; f an; () end. | |
function table.iter(f, tbl) | |
for _,v in pairs(tbl) do | |
f(v) | |
end | |
end | |
-- val iteri : (int -> 'a -> unit) -> 'a list -> unit | |
-- Same as List.iter, but the function is applied to the index of the element as first argument | |
-- (counting from 1), and the element itself as second argument. | |
function table.iteri(f, tbl) | |
for i,v in ipairs(tbl) do | |
f(i,v) | |
end | |
end | |
-- val map : ('a -> 'b) -> 'a list -> 'b list | |
-- List.map f [a1; ...; an] applies function f to a1, ..., an, and builds the list [f a1; ...; f an] with the results returned by f. | |
function table.map(f, tbl) | |
local ret = {} | |
for k,v in pairs(tbl) do | |
ret[k] = f(v) | |
end | |
return ret | |
end | |
-- val mapi : (int -> 'a -> 'b) -> 'a list -> 'b list | |
-- Same as List.map, but the function is applied to the index of the element as first argument (counting from 1), | |
-- and the element itself as second argument. | |
function table.mapi(f, tbl) | |
local ret = {} | |
for i,v in ipairs(tbl) do | |
ret[i] = f(i, v) | |
end | |
return ret | |
end | |
-- val rev_map : ('a -> 'b) -> 'a list -> 'b list | |
-- equivalent to table.rev(table.map(f, tbl)) | |
function table.rev_map(f, tbl) -- todo more efficient | |
return table.rev(table.map(f, tbl)) | |
end | |
-- val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a | |
-- List.fold_left f a [b1; ...; bn] is f (... (f (f a b1) b2) ...) bn. | |
function table.fold_left(f, pacc, tbl) | |
local acc = table.deepcopy(pacc) | |
for i,v in ipairs(tbl) do | |
acc = f(acc, v) | |
end | |
return acc | |
end | |
-- val fold_right : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b | |
-- List.fold_right f [a1; ...; an] b is f a1 (f a2 (... (f an b) ...)). | |
function table.fold_right(f, pacc, tbl) | |
local acc = table.deepcopy(pacc) | |
for i,v in ipairs(table.rev(tbl)) do | |
acc = f(acc, v) | |
end | |
return acc | |
end | |
-- Iterators on two lists -- | |
-- val iter2 : ('a -> 'b -> unit) -> 'a list -> 'b list -> unit | |
-- List.iter2 f [a1; ...; an] [b1; ...; bn] calls in turn f a1 b1; ...; f an bn. | |
-- Raise Invalid_argument if the two lists have different lengths. | |
function table.iter2(f, tbl1, tbl2) | |
assert(#tbl1 == #tbl2, "table.iter2: two lists must have same lengths") | |
for i = 1,#tbl1 do | |
f(tbl1[i], tbl2[i]) | |
end | |
end | |
-- val map2 : ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list | |
-- List.map2 f [a1; ...; an] [b1; ...; bn] is [f a1 b1; ...; f an bn]. | |
-- Raise Invalid_argument if the two lists have different lengths. | |
function table.map2(f, tbl1, tbl2) | |
assert(#tbl1 == #tbl2, "table.map2: two lists must have same lengths") | |
local ret = {} | |
for i = 1,#tbl1 do | |
ret[#ret + 1] = f(tbl1[i], tbl2[i]) | |
end | |
return ret | |
end | |
-- val rev_map2 : ('a -> 'b -> 'c) -> 'a list -> 'b list -> 'c list | |
-- equivalent to table.rev(table.map2(f, tbl1, tbl2)) | |
function table.rev_map2(f, tbl1, tbl2) -- todo more efficient | |
return table.rev(table.map2(f, tbl1, tbl2)) | |
end | |
-- val fold_left2 : ('a -> 'b -> 'c -> 'a) -> 'a -> 'b list -> 'c list -> 'a | |
-- List.fold_left2 f a [b1; ...; bn] [c1; ...; cn] is f (... (f (f a b1 c1) b2 c2) ...) bn cn. | |
-- Raise Invalid_argument if the two lists have different lengths. | |
function table.fold_left2(f, pacc, tbl1, tbl2) | |
assert(#tbl1 == #tbl2, "table.fold_left2: two lists must have same lengths") | |
local acc = table.deepcopy(pacc) | |
for i = 1,#tbl1 do | |
acc = f(acc, tbl1[i], tbl2[i]) | |
end | |
return acc | |
end | |
-- val fold_right2 : ('a -> 'b -> 'c -> 'c) -> 'a list -> 'b list -> 'c -> 'c | |
-- List.fold_right2 f [a1; ...; an] [b1; ...; bn] c is f a1 b1 (f a2 b2 (... (f an bn c) ...)). | |
-- Raise Invalid_argument if the two lists have different lengths. | |
function table.fold_right2(f, pacc, tbl1, tbl2) | |
assert(#tbl1 == #tbl2, "table.fold_right2: two lists must have same lengths") | |
local acc = table.deepcopy(pacc) | |
for i = #tbl1,1,-1 do | |
acc = f(acc, tbl1[i], tbl2[i]) | |
end | |
return acc | |
end | |
-- List scanning -- | |
-- val for_all : ('a -> bool) -> 'a list -> bool | |
-- for_all p [a1; ...; an] checks if all elements of the list satisfy the predicate p. | |
-- That is, it returns (p a1) && (p a2) && ... && (p an). | |
function table.for_all(f, tbl) | |
for i,v in pairs(tbl) do | |
if not f(v) then | |
return false | |
end | |
end | |
return true | |
end | |
-- val exists : ('a -> bool) -> 'a list -> bool | |
-- exists p [a1; ...; an] checks if at least one element of the list satisfies the predicate p. | |
-- That is, it returns (p a1) || (p a2) || ... || (p an). | |
function table.exists(f, tbl) | |
for i,v in pairs(tbl) do | |
if f(v) then | |
return true | |
end | |
end | |
return false | |
end | |
-- val for_all2 : ('a -> 'b -> bool) -> 'a list -> 'b list -> bool | |
-- Same as List.for_all, but for a two-argument predicate. Raise Invalid_argument if the two lists have different lengths. | |
-- val exists2 : ('a -> 'b -> bool) -> 'a list -> 'b list -> bool | |
-- Same as List.exists, but for a two-argument predicate. Raise Invalid_argument if the two lists have different lengths. | |
-- val mem : 'a -> 'a list -> bool | |
-- mem a l is true if and only if a is equal to an element of l. | |
function table.mem(v, tbl) | |
return table.exists(function(e) | |
return e == v | |
end, tbl) | |
end | |
-- val memq : 'a -> 'a list -> bool | |
-- Same as List.mem, but uses physical equality instead of structural equality to compare list elements. | |
-- List searching -- | |
-- val find : ('a -> bool) -> 'a list -> 'a | |
-- find p l returns the first element of the list l that satisfies the predicate p. | |
-- Raise Not_found if there is no value that satisfies p in the list l. | |
function table.find(f, tbl) | |
for _,v in pairs(tbl) do | |
if f(v) then | |
return v | |
end | |
end | |
return nil | |
end | |
-- val filter : ('a -> bool) -> 'a list -> 'a list | |
-- filter p l returns all the elements of the list l that satisfy the predicate p. | |
-- The order of the elements in the input list is preserved. | |
function table.filter(f, tbl) | |
local ret = {} | |
for _,v in pairs(tbl) do | |
if f(v) then | |
ret[#ret + 1] = v | |
end | |
end | |
return ret | |
end | |
-- val partition : ('a -> bool) -> 'a list -> 'a list * 'a list | |
-- partition p l returns a pair of lists (l1, l2), where l1 is the list of all the elements of l that satisfy the predicate p, | |
-- and l2 is the list of all the elements of l that do not satisfy p. The order of the elements in the input list is preserved. | |
function table.partition(f, tbl) | |
local ret1, ret2 = {}, {} | |
for _,v in ipairs(tbl) do | |
if f(v) then | |
ret1[#ret1 + 1] = v | |
else | |
ret2[#ret2 + 1] = v | |
end | |
end | |
return ret1, ret2 | |
end | |
-- Association lists -- | |
-- val mem_assoc : 'a -> ('a * 'b) list -> bool | |
-- Same as List.assoc, but simply return true if a binding exists, and false if no bindings exist for the given key. | |
function table.mem_assoc(k, tbl) | |
return tbl[k] ~= nil | |
end | |
-- val remove_assoc : 'a -> ('a * 'b) list -> ('a * 'b) list | |
-- remove_assoc a l returns the list of pairs l without the first pair with key a, if any. | |
function table.remove_assoc(k, tbl) | |
local ret = table.deepcopy(tbl) | |
ret[k] = nil | |
return ret | |
end | |
-- Lists of pairs -- | |
-- val split : ('a * 'b) list -> 'a list * 'b list | |
-- Transform a list of pairs into a pair of lists: split [(a1,b1); ...; (an,bn)] is ([a1; ...; an], [b1; ...; bn]). | |
function table.split(tbl) | |
local ret1, ret2 = {}, {} | |
for k,v in pairs(tbl) do | |
ret1[#ret1 + 1] = k | |
ret2[#ret2 + 1] = v | |
end | |
return ret1, ret2 | |
end | |
-- val combine : 'a list -> 'b list -> ('a * 'b) list | |
-- Transform a pair of lists into a list of pairs: combine [a1; ...; an] [b1; ...; bn] is [(a1,b1); ...; (an,bn)]. | |
-- Raise Invalid_argument if the two lists have different lengths. | |
function table.combine(tbl1, tbl2) | |
assert(#tbl1 == #tbl2, "table.combine: two lists must have same lengths") | |
local ret = {} | |
for i = 1,#tbl1 do | |
ret[tbl1[i]] = tbl2[i] | |
end | |
return ret | |
end | |
end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
return (function() | |
local function fromString(str) | |
assert(str and type(str) == "string" and str:find('%d-%.%d-%.%d+')) | |
return str:match('(%d+)%.(%d+)%.(%d+)') | |
end | |
local function toString(major, minor, patch) | |
return string.format('%d.%d.%d', major, minor, patch) | |
end | |
local function majorUp(str) | |
local major, minor, patch = fromString(str) | |
return toString(major+1, 0, 0) | |
end | |
local function minorUp(str) | |
local major, minor, patch = fromString(str) | |
return toString(major, minor+1, 0) | |
end | |
local function patchUp(str) | |
local major, minor, patch = fromString(str) | |
return toString(major, minor, patch+1) | |
end | |
-- return the value of the biggest key | |
local function getValueOfMaxKey(tbl) | |
local kMax = next(tbl) | |
for k,_ in pairs(tbl) do | |
if tonumber(k) > tonumber(kMax) then | |
kMax = k | |
end | |
end | |
return tbl[kMax] | |
end | |
-- todo clean code | |
-- return the sha of the better version | |
local function getShaOfBetterVersion(version, versions) | |
local betterVersion | |
if version:find("%d+%.%d+%.%d+") then -- MAJOR.MINOR.PATCH | |
local major, minor, patch = version:match("(%d-)%.(%d-)%.(.*)") | |
betterVersion = versions[major] and versions[major][minor] and versions[major][minor][patch] | |
elseif version:find("%d+%.%d+%.%*") then -- MAJOR.MINOR | |
local major, minor = version:match("(%d-)%.(%d-)%.%*") | |
betterVersion = versions[major] and versions[major][minor] and getValueOfMaxKey(versions[major][minor]) | |
elseif version:find("%d+%.%*") then -- MAJOR | |
local major = version:match("(%d-).*") | |
betterVersion = versions[major] and getValueOfMaxKey(getValueOfMaxKey(versions[major])) | |
elseif version:find("%*") then -- LATEST | |
betterVersion = getValueOfMaxKey(getValueOfMaxKey(getValueOfMaxKey(versions))) -- or return nil? | |
else | |
error("bad formatted version\n\texpected: * | MAJOR.* | MAJOR.MINOR.* | MAJOR.MINOR.PATCH\n\texample: 1.32.*") | |
end | |
return betterVersion or err(table.format("version %s not found", version)) | |
end | |
return { | |
fromString = fromString, | |
toString = toString, | |
majorUp = majorUp, | |
minorUp = minorUp, | |
patchUp = patchUp, | |
getShaOfBetterVersion = getShaOfBetterVersion | |
} | |
end) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment