Created
June 23, 2013 20:09
-
-
Save XanDDemoX/5846359 to your computer and use it in GitHub Desktop.
Codea Project Gist Created with AutoGist
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
AutoGist Tab Order | |
------------------------------ | |
This file should not be included in the Codea project. | |
#Main | |
#Gist | |
#Installer | |
#json | |
#Base64 | |
#ChangeLog | |
#ReorderTabs |
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
-- Base64.lua | |
-- #!/usr/bin/env lua | |
-- working lua base64 codec (c) 2006-2008 by Alex Kloss | |
-- compatible with lua 5.1 | |
-- http://www.it-rfc.de | |
-- licensed under the terms of the LGPL2 | |
-- modified by Rui Viana for gitty client | |
Base64 = class() | |
-- bitshift functions (<<, >> equivalent) | |
-- shift left | |
function Base64.lsh(value,shift) | |
return (value*(2^shift)) % 256 | |
end | |
-- shift right | |
function Base64.rsh(value,shift) | |
return math.floor(value/2^shift) % 256 | |
end | |
-- return single bit (for OR) | |
function Base64.bit(x,b) | |
return (x % 2^b - x % 2^(b-1) > 0) | |
end | |
-- logic OR for number values | |
function Base64.lor(x,y) | |
result = 0 | |
for p=1,8 do result = result + (((Base64.bit(x,p) or Base64.bit(y,p)) == true) and 2^(p-1) or 0) end | |
return result | |
end | |
-- encryption table | |
local base64chars = {[0]='A',[1]='B',[2]='C',[3]='D',[4]='E',[5]='F',[6]='G',[7]='H',[8]='I',[9]='J',[10]='K',[11]='L',[12]='M',[13]='N',[14]='O',[15]='P',[16]='Q',[17]='R',[18]='S',[19]='T',[20]='U',[21]='V',[22]='W',[23]='X',[24]='Y',[25]='Z',[26]='a',[27]='b',[28]='c',[29]='d',[30]='e',[31]='f',[32]='g',[33]='h',[34]='i',[35]='j',[36]='k',[37]='l',[38]='m',[39]='n',[40]='o',[41]='p',[42]='q',[43]='r',[44]='s',[45]='t',[46]='u',[47]='v',[48]='w',[49]='x',[50]='y',[51]='z',[52]='0',[53]='1',[54]='2',[55]='3',[56]='4',[57]='5',[58]='6',[59]='7',[60]='8',[61]='9',[62]='-',[63]='_'} | |
-- function encode | |
-- encodes input string to base64. | |
function Base64.encode(data) | |
local bytes = {} | |
local result = "" | |
for spos=0,string.len(data)-1,3 do | |
for byte=1,3 do bytes[byte] = string.byte(string.sub(data,(spos+byte))) or 0 end | |
result = string.format('%s%s%s%s%s', | |
result, | |
base64chars[Base64.rsh(bytes[1],2)], | |
base64chars[Base64.lor( | |
Base64.lsh((bytes[1] % 4),4), | |
Base64.rsh(bytes[2],4))] or "=", | |
((#data-spos) > 1) and | |
base64chars[Base64.lor(Base64.lsh(bytes[2] % 16,2), | |
Base64.rsh(bytes[3],6))] or "=", | |
((#data-spos) > 2) and base64chars[(bytes[3] % 64)] or "=") | |
end | |
return result | |
end | |
-- decryption table | |
local base64bytes = {['A']=0,['B']=1,['C']=2,['D']=3,['E']=4,['F']=5,['G']=6,['H']=7,['I']=8,['J']=9,['K']=10,['L']=11,['M']=12,['N']=13,['O']=14,['P']=15,['Q']=16,['R']=17,['S']=18,['T']=19,['U']=20,['V']=21,['W']=22,['X']=23,['Y']=24,['Z']=25,['a']=26,['b']=27,['c']=28,['d']=29,['e']=30,['f']=31,['g']=32,['h']=33,['i']=34,['j']=35,['k']=36,['l']=37,['m']=38,['n']=39,['o']=40,['p']=41,['q']=42,['r']=43,['s']=44,['t']=45,['u']=46,['v']=47,['w']=48,['x']=49,['y']=50,['z']=51,['0']=52,['1']=53,['2']=54,['3']=55,['4']=56,['5']=57,['6']=58,['7']=59,['8']=60,['9']=61,['-']=62,['_']=63,['=']=nil} | |
-- function decode | |
-- decode base64 input to string | |
function Base64.decode(data) | |
local chars = {} | |
local result="" | |
for dpos=0,string.len(data)-1,4 do | |
for char=1,4 do chars[char] = base64bytes[(string.sub(data,(dpos+char),(dpos+char)) or "=")] end | |
result = string.format('%s%s%s%s', | |
result, | |
string.char(Base64.lor(Base64.lsh(chars[1],2), Base64.rsh(chars[2],4))), | |
(chars[3] ~= nil) and | |
string.char(Base64.lor(Base64.lsh(chars[2],4), Base64.rsh(chars[3],2))) or "", | |
(chars[4] ~= nil) and | |
string.char(Base64.lor(Base64.lsh(chars[3],6) % 192, (chars[4]))) or "") | |
end | |
return result | |
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
--[[ | |
---------------------------------------------------------------------------------- | |
------------------------------AutoGist by Chris Houser---------------------------- | |
---------------------------------------------------------------------------------- | |
------------- | |
Version Notes | |
------------- | |
VERSION 2.1.4 | |
-Fixed Manual Gist Tab Order | |
VERSION 2.1.3 | |
-Installer revamped. Run a created installer once and the project is pulled. It will no longer save json tab. | |
VERSION 2.1.2 | |
-Preserves the Tab order when a project is pulled or Installer is used. | |
-New file 1aTabOder is saved to Gist. It is used to save and load the tab order. When using AutoGist, this file will not be pulled into a project. | |
VERSION 2.1.1 | |
-Small fix in manual gisted project Description | |
-Now Lets you know if an update is needed | |
Version 2.08 | |
-All data is stored as a global variable | |
-AutoGist is now a class so it's called differently in main | |
-Version can be in any format | |
-Revision keys are now saved with the version | |
Version 2.06 | |
-Updated Selection Options in preparation for UpdateManager | |
Version 2.05 | |
-Update AutoGist button added | |
Version 2.02 | |
-Added the ability to allow user to set the project name in the installer | |
Version 2.01 | |
-Added the ability to create an installer for your project. | |
-You can not comment out code that you do not want to be part of the gist. such as the functions AutoGist uses. | |
- to comment out use "-- /*" (no space) to start comment and "-- */" (no space) to end comment | |
Version 2.00 | |
-Retrieve you Gist Token from inside AutoGist uses basic Auth | |
-gets and saves Gist Auth Token | |
--]] |
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
AutoGist = class() | |
function AutoGist:init(projectname,description,version) | |
self.projectName = projectname | |
self.version = tostring(version) | |
self.description = description | |
self.agData = nil | |
self.projectData = nil | |
self.authToken = nil | |
self.gistID = nil | |
self.buildID = nil | |
self:loadData() | |
end | |
--AutoGist | |
--Credit to @acp from the Codea Forums for Gister | |
function AutoGist:backup(public) | |
if BUILD == true and self.projectData.Version[self.version] == nil then | |
print("You cannot update Version while BUILD is true") | |
return | |
end | |
if BUILD == true then | |
if self.projectData.Version[self.version].buildID then | |
local v = self.projectData.Version[self.version] | |
v.buildNum = v.buildNum + 1 | |
self.description = self.projectName.." Test Build: v"..self.version.." Build: "..v.buildNum | |
self.projectData.Version[self.version].buildNum = v.buildNum | |
local p = self:getProject(self.projectName,false) | |
self:doGist(p,v.buildID) | |
elseif self.projectData.Version[self.version] then | |
local v = self.projectData.Version[self.version] | |
v.buildNum = 1 | |
self.description = self.projectName.." Test Build: v"..self.version.." Build: "..v.buildNum | |
local p = self:getProject(self.projectName,false) | |
self:doGist(p,nil) | |
else | |
print("Must Gist a Version Before Builds are active.") | |
end | |
else | |
--Version Already Exists | |
if self.projectData.Version[self.version] then | |
--Version Exists do nothing | |
else | |
--table.insert(self.projectData.Version,self.version) | |
self.projectData.Version[self.version] = {} | |
self.description = self.projectName.." Release v"..self.version.." -"..self.description | |
self:saveData(self.projectData) | |
local p = self:getProject(self.projectName,public) | |
self:doGist(p,self.gistID) | |
end | |
end | |
end | |
--- Spits a project into a gist-like table | |
-- @param project The project to JSON | |
-- @param public Should this be a public gist? | |
-- @return Project as a Gist | |
function AutoGist:getProject(project, public) | |
local projectTabs = listProjectTabs(project) | |
local projectFiles = {} | |
for _, v in ipairs(projectTabs) do | |
table.insert(projectFiles, { | |
key = v .. ".lua", | |
file = AutoGist:commentOut(readProjectTab(project .. ":" .. v))}) | |
end | |
local gist = {} | |
gist.public = public | |
gist.description = self.description or nil | |
gist.files = {} | |
for _, v in ipairs(projectFiles) do | |
gist.files[v.key] = {content = v.file} | |
end | |
--Sent tab Order | |
local j,k = self:tabOrder(project) | |
gist.files[j] = {content = k} | |
print("Project packaged and sent. Please wait for response.") | |
return gist | |
end | |
--- Posts the Gist to Github | |
-- @param project The project to gist | |
-- @param update URL of gist to update (if any) | |
-- @return nil (redir to browser if success) | |
function AutoGist:doGist(project, update) | |
------------------------------------------------------------------------------- | |
--If set to true, A new gist for builds will be created when Version is updated | |
local newGistBuildWithVersion = true | |
------------------------------------------------------------------------------- | |
local projectJson = json.encode(project) | |
---Sucess Return | |
local handleSuccess = function(data) | |
--print(data) | |
local gistReturn = json.decode(data) | |
assert(gistReturn,"Invalid Gist Token, Please reset token") | |
sound(SOUND_PICKUP, 11797) | |
--Checking if it already has a build id and that build is true | |
if BUILD == true then | |
if self.projectData.Version[self.version].buildID then | |
local b = self.projectData.Version[self.version].buildNum | |
print("Gist Updated:\n Version: "..self.version.."\nBuild: "..b) | |
self:saveData(self.projectData) | |
else | |
print("Opening new Build gist url") | |
local b = 1 | |
self.projectData.Version[self.version].buildID = gistReturn.id | |
self.projectData.Version[self.version].buildNum = 1 | |
self:saveData(self.projectData) | |
openURL(gistReturn.html_url) | |
end | |
else | |
if self.projectData.gistID then | |
self.projectData.Version[self.version].buildID = nil | |
self.projectData.Version[self.version].buildNum = nil | |
self.projectData.Version[self.version].revisionID = gistReturn.history[1].version | |
print("Gist Updated:\n Version: "..self.version) | |
self:saveData(self.projectData) | |
else | |
print("New Gist:\n Version: "..self.version) | |
self.projectData.gistID = gistReturn.id | |
self.projectData.Version[self.version].revisionID = gistReturn.history[1].version | |
self:saveData(self.projectData) | |
openURL(gistReturn.html_url) | |
end | |
end | |
end | |
local handleFailure = function(data) | |
sound(SOUND_EXPLODE, 32351) | |
print(data) | |
if data == "The request timed out" then print("But your gist should be there") end | |
end | |
local opts = { | |
data = projectJson} | |
local requestUrl | |
if update and #update > 0 then | |
opts.method = "PATCH" | |
requestUrl = "https://api.github.com/gists/" .. update | |
else | |
opts.method = "POST" | |
requestUrl = "https://api.github.com/gists" | |
end | |
if self.authToken then | |
opts.headers = {Authorization = "Bearer " .. self.authToken} | |
end | |
http.request(requestUrl, | |
handleSuccess, | |
handleFailure, | |
opts) | |
end | |
function AutoGist:doManualGist(project, update) | |
project.description = "Codea Project Gist Created with AutoGist" | |
projectJson = json.encode(project) | |
local handleSuccess = function(data) | |
print(data) | |
local gistReturn = json.decode(data) | |
sound(SOUND_PICKUP, 11797) | |
openURL(gistReturn.html_url) | |
end | |
local handleFailure = function(data) | |
sound(SOUND_EXPLODE, 32351) | |
print(data) | |
if data == "The request timed out" then print("But your gist should be there") end | |
end | |
local opts = { | |
data = projectJson} | |
local requestUrl | |
if update and #update > 0 then | |
opts.method = "PATCH" | |
requestUrl = "https://api.github.com/gists/" .. update | |
else | |
opts.method = "POST" | |
requestUrl = "https://api.github.com/gists" | |
end | |
if self.authToken then | |
opts.headers = {Authorization = "Bearer " .. self.authToken} | |
end | |
http.request(requestUrl, | |
handleSuccess, | |
handleFailure, | |
opts) | |
end | |
function AutoGist:fetchGist(gistId,target) | |
print("Attempting to fetch the gist.") | |
if target == nil or listProjectTabs(target) == nil then | |
print("Project target must be specified and exist") | |
return | |
end | |
local gistUrl = "https://api.github.com/gists/" .. gistId | |
print(gistUrl) | |
local handleSuccess = function(data) | |
print(data) | |
local gist = json.decode(data) | |
---------------------- | |
--Look for tab file | |
---------------------- | |
if gist.files["1aTabOrder"] then | |
print("***Tab Order Found***") | |
local taborder = gist.files["1aTabOrder"].content | |
local strStart =1 | |
local strEnd =0 | |
strStart = string.find(taborder,"#",strEnd) | |
strEnd = string.find(taborder,"\n",strStart) | |
while strStart do | |
local tmp = string.sub(taborder,strStart+1,strEnd-1) | |
local name = target..":"..tmp | |
tmp = tmp..".lua" | |
saveProjectTab(name,gist.files[tmp].content) | |
strStart = string.find(taborder,"#",strEnd) | |
strEnd = string.find(taborder,"\n",strStart) | |
end | |
else | |
for k,v in pairs(gist.files) do | |
local name = target .. ":" .. string.gsub(k,".lua","") | |
saveProjectTab(name, v.content) | |
end | |
end | |
sound(SOUND_PICKUP, 11797) | |
print("Success!") | |
end | |
local handleFailure = function(data) | |
sound(SOUND_EXPLODE, 32351) | |
print(data) | |
end | |
http.request(gistUrl,handleSuccess, handleFailure) | |
end | |
-------------------------- | |
--Fetches info on a gist to check for updates | |
-------------------------- | |
function AutoGist:fetchInfo(id) | |
local gistUrl = "https://api.github.com/gists/" .. id | |
local handleSuccess = function(data) | |
local gist = json.decode(data) | |
return gist | |
end | |
local handleFailure = function(data) | |
sound(SOUND_EXPLODE, 32351) | |
print(data) | |
end | |
http.request(gistUrl,handleSuccess, handleFailure) | |
end | |
------------------------- | |
--Sets the Version key manually | |
------------------------- | |
function AutoGist:setKey(releaseKey) | |
if releaseKey then | |
print(" Release Gist set to: "..releaseKey) | |
self.agData.projects[self.projectName].gistID = releaseKey | |
end | |
self:saveData() | |
end | |
function AutoGist:getAuth(user, pass,callBack) | |
local url = "https://api.github.com/authorizations" | |
local d = {} | |
d.scopes = {"gist"} | |
d.note = "AutoGist Codea" | |
projectAuth = json.encode(d) | |
opts = { data = projectAuth } | |
opts.method = "POST" | |
opts.headers = {Authorization="Basic "..Base64.encode(user..":"..pass)} | |
local handleSuccess = function(data) | |
local out = json.decode(data) | |
local token = out.token | |
self.agData.authtoken = token | |
self.authToken = token | |
self:saveData() | |
print("Token Recieved: "..token) | |
if callBack then callBack() end | |
end | |
http.request(url, | |
handleSuccess,function(i) print(i) end, | |
opts) | |
end | |
function AutoGist:createInstall(name,id) | |
local handleSuccess = function(data) | |
local gistReturn = json.decode(data) | |
assert(gistReturn,"Invalid Gist Token, Please reset token") | |
print("Install Created!") | |
openURL(gistReturn.html_url) | |
end | |
local installer = readProjectTab("AutoGist:Installer") | |
installer = string.sub(installer,3) | |
installer = string.gsub(installer,"#PROJECT",name) | |
installer = string.gsub(installer,"@PROJECT",name) | |
installer = string.gsub(installer,"@GISTID",id) | |
--print(installer) | |
local gist = json.decode(installer) | |
gist.public = true | |
gist.description = name.." AutoInstall" | |
installer = json.encode(gist) | |
--print(installer) | |
local opts = {data = installer} | |
opts.method = "POST" | |
opts.headers = {Authorization = "Bearer " .. self.authToken} | |
http.request("https://api.github.com/gists", | |
handleSuccess, | |
opts) | |
end | |
--This function removes comments | |
function AutoGist:commentOut(tab) | |
local str = string.gsub(tab, "--%/%*.--%*%/"," ") | |
return str | |
end | |
--Loads Global Data | |
function AutoGist:loadData() | |
if readGlobalData("AutoGist_Storage") then | |
self.agData = json.decode(readGlobalData("AutoGist_Storage")) | |
if not self.agData.projects then self.agData.projects = {} end | |
if not self.agData.projects[self.projectName] then | |
self.agData.projects[self.projectName] = {Name=self.projectName, | |
Description=self.description, | |
Version={}} | |
-- self.agData.projects[self.projectName].Version[self.version] = {} | |
end | |
--set class values | |
self.projectData = self.agData.projects[self.projectName] | |
self.authToken = self.agData.authtoken | |
self.gistID = self.projectData.gistID | |
else | |
self.agData = {} | |
self.agData.projects = {} | |
self.agData.projects[self.projectName] = {Name=self.projectName, | |
Description=self.description, | |
Version = {} } | |
--self.agData.projects[self.projectName].Version[self.version] = {} | |
end | |
if readLocalData("AutoGist_GistID") then | |
local str = readLocalData("AutoGist_GistID") | |
if type(str) == "number" then str = string.format("%.0f",str) end | |
self.agData.projects[self.projectName].gistID = str | |
self.gistID = str | |
saveLocalData("AutoGist_GistID",nil) | |
end | |
self:saveData() | |
end | |
--------------------- | |
--Saves the global data | |
--------------------- | |
function AutoGist:saveData(project) | |
if project then | |
self.agData.projects[self.projectName] = project | |
end | |
saveGlobalData("AutoGist_Storage",json.encode(self.agData) ) | |
end | |
--------------------- | |
--List Gisted Versions | |
--------------------- | |
function AutoGist:listVersions() | |
print("---------") | |
print("Versions") | |
print("--------") | |
for i,j in pairs(self.projectData.Version) do | |
print("Version: "..i) | |
end | |
end | |
-------------------- | |
--delete a project | |
-------------------- | |
function AutoGist:deleteProject(name) | |
self.agData.projects[name] = nil | |
self:saveData() | |
self:loadData() | |
end | |
------------------- | |
--save Project Tab Order | |
-------------------- | |
function AutoGist:tabOrder(p) | |
local name = "1aTabOrder" | |
local tabs = listProjectTabs(p) | |
local str = self.projectName.." Tab Order\n------------------------------\n" | |
str = str.."This file should not be included in the Codea project.\n" | |
for i,j in pairs(tabs) do | |
str = str.."#"..j.."\n" | |
end | |
return name,str | |
end | |
--Used when editing the install File | |
function AutoGist:decodeInstaller() | |
local inst = readProjectTab("AutoGist:Installer") | |
inst = string.sub(inst,3) | |
local tmp = json.decode(inst) | |
saveProjectTab("InstallerDecoded",tmp.files["Main.lua"].content) | |
end | |
function AutoGist:encodeInstaller() | |
local cont = readProjectTab("AutoGist:InstallerDecoded") | |
cont = string.gsub(cont,"--%[%[","") | |
cont = string.gsub(cont,"--%]%]","") | |
local inst = {files={}} | |
inst.files["Main.lua"] = {content} | |
inst.files["Main.lua"].content = cont | |
local tmp = json.encode(inst) | |
tmp = "--"..tmp | |
saveProjectTab("Installer",tmp) | |
print("Installer Encoded") | |
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
--{"files":{"Main.lua":{"content":"\n--#PROJECT Single Install\n--Installer created by @Briarfox\n--- This will pull the #PROJECT project into Codea for you\n-- Instructions:\n-- * Create a new project in Codea named #PROJECT If you chose another name please change the variable Below\n--This is case sensitive\nProjectName = \"@PROJECT\"\n-- * Paste this into the Main (not from the raw view, as iSafari will escape special characters)\n-- * Make sure there is a single tab in the project\n-- * Run and wait for success!\n-- If all went well, you should have a #PROJECT project now\n\n\nfunction setup()\n local jsonCode\n getJsonLib()\nend\n\nfunction getJsonLib()\n local tabs = listProjectTabs()\n if #tabs == 1 then\n print(\"Attempting to load json...\")\n local handleSuccess = function(data)\n --saveProjectTab(\"json\", data)\n jsonCode = data\n --sound(SOUND_POWERUP, 42179)\n print(\"json code loaded...\")\n if jsonCode then\n print(\"Attempting to pull project...\")\n l = loadstring(jsonCode)\n l()\n GetProject() \n end\n end\n http.request(\"https://dl.dropboxusercontent.com/s/9e4nvqeu4hsux2q/Json.lua?token_hash=AAFyMB98j4bnt_1gawf9wSke52hsoC7hsIvARcTuZNeOEw&dl=1\", handleSuccess)\n end\nend\n\nfunction GetProject()\n \n local handleSuccess = function(data,i,j)\n if listProjectTabs(ProjectName) == nil then\n sound(SOUND_EXPLODE, 32351)\n error(\"AutoGist project must exist first\")\n end\n local gist = json.decode(data)\n local projName = ProjectName\n if gist.files[\"1aTabOrder\"] then\n print(\"***Tab Order Found***\")\n local taborder = gist.files[\"1aTabOrder\"].content\n local strStart =1\n local strEnd =0\n strStart = string.find(taborder,\"#\",strEnd)\n strEnd = string.find(taborder,\"\\n\",strStart)\n while strStart do\n local tmp = string.sub(taborder,strStart+1,strEnd-1)\n local name = ProjectName..\":\"..tmp\n tmp = tmp..\".lua\"\n saveProjectTab(name,gist.files[tmp].content)\n strStart = string.find(taborder,\"#\",strEnd)\n strEnd = string.find(taborder,\"\\n\",strStart)\n \n end \n else\n for k,v in pairs(gist.files) do\n local name = ProjectName .. \":\" .. string.gsub(k,\".lua\",\"\")\n saveProjectTab(name, v.content)\n end\n end\n sound(SOUND_PICKUP, 11797)\n print(\"Success!\")\n end\n local handleFailure = function(data)\n sound(SOUND_EXPLODE, 32351)\n print(data)\n end \n http.request(\"https://api.github.com/gists/@GISTID\",handleSuccess, handleFailure)\nend\n\n\n\n"}}} |
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
-- Module options: | |
local always_try_using_lpeg = false | |
local register_global_module_table = true | |
local global_module_name = 'json' | |
--[==[ | |
David Kolf's JSON module for Lua 5.1/5.2 | |
======================================== | |
*Version 2.3* | |
In the default configuration this module writes no global values, not even | |
the module table. Import it using | |
json = require ("dkjson") | |
In environments where `require` or a similiar function are not available | |
and you cannot receive the return value of the module, you can set the | |
option `register_global_module_table` to `true`. The module table will | |
then be saved in the global variable with the name given by the option | |
`global_module_name`. | |
Exported functions and values: | |
`json.encode (object [, state])` | |
-------------------------------- | |
Create a string representing the object. `Object` can be a table, | |
a string, a number, a boolean, `nil`, `json.null` or any object with | |
a function `__tojson` in its metatable. A table can only use strings | |
and numbers as keys and its values have to be valid objects as | |
well. It raises an error for any invalid data types or reference | |
cycles. | |
`state` is an optional table with the following fields: | |
- `indent` | |
When `indent` (a boolean) is set, the created string will contain | |
newlines and indentations. Otherwise it will be one long line. | |
- `keyorder` | |
`keyorder` is an array to specify the ordering of keys in the | |
encoded output. If an object has keys which are not in this array | |
they are written after the sorted keys. | |
- `level` | |
This is the initial level of indentation used when `indent` is | |
set. For each level two spaces are added. When absent it is set | |
to 0. | |
- `buffer` | |
`buffer` is an array to store the strings for the result so they | |
can be concatenated at once. When it isn't given, the encode | |
function will create it temporary and will return the | |
concatenated result. | |
- `bufferlen` | |
When `bufferlen` is set, it has to be the index of the last | |
element of `buffer`. | |
- `tables` | |
`tables` is a set to detect reference cycles. It is created | |
temporary when absent. Every table that is currently processed | |
is used as key, the value is `true`. | |
When `state.buffer` was set, the return value will be `true` on | |
success. Without `state.buffer` the return value will be a string. | |
`json.decode (string [, position [, null]])` | |
-------------------------------------------- | |
Decode `string` starting at `position` or at 1 if `position` was | |
omitted. | |
`null` is an optional value to be returned for null values. The | |
default is `nil`, but you could set it to `json.null` or any other | |
value. | |
The return values are the object or `nil`, the position of the next | |
character that doesn't belong to the object, and in case of errors | |
an error message. | |
Two metatables are created. Every array or object that is decoded gets | |
a metatable with the `__jsontype` field set to either `array` or | |
`object`. If you want to provide your own metatables use the syntax | |
json.decode (string, position, null, objectmeta, arraymeta) | |
To prevent the assigning of metatables pass `nil`: | |
json.decode (string, position, null, nil) | |
`<metatable>.__jsonorder` | |
------------------------- | |
`__jsonorder` can overwrite the `keyorder` for a specific table. | |
`<metatable>.__jsontype` | |
------------------------ | |
`__jsontype` can be either `"array"` or `"object"`. This value is only | |
checked for empty tables. (The default for empty tables is `"array"`). | |
`<metatable>.__tojson (self, state)` | |
------------------------------------ | |
You can provide your own `__tojson` function in a metatable. In this | |
function you can either add directly to the buffer and return true, | |
or you can return a string. On errors nil and a message should be | |
returned. | |
`json.null` | |
----------- | |
You can use this value for setting explicit `null` values. | |
`json.version` | |
-------------- | |
Set to `"dkjson 2.3"`. | |
`json.quotestring (string)` | |
--------------------------- | |
Quote a UTF-8 string and escape critical characters using JSON | |
escape sequences. This function is only necessary when you build | |
your own `__tojson` functions. | |
`json.addnewline (state)` | |
------------------------- | |
When `state.indent` is set, add a newline to `state.buffer` and spaces | |
according to `state.level`. | |
LPeg support | |
------------ | |
When the local configuration variable `always_try_using_lpeg` is set, | |
this module tries to load LPeg to replace the `decode` function. The | |
speed increase is significant. You can get the LPeg module at | |
<http://www.inf.puc-rio.br/~roberto/lpeg/>. | |
When LPeg couldn't be loaded, the pure Lua functions stay active. | |
In case you don't want this module to require LPeg on its own, | |
disable the option `always_try_using_lpeg` in the options section at | |
the top of the module. | |
In this case you can later load LPeg support using | |
### `json.use_lpeg ()` | |
Require the LPeg module and replace the functions `quotestring` and | |
and `decode` with functions that use LPeg patterns. | |
This function returns the module table, so you can load the module | |
using: | |
json = require "dkjson".use_lpeg() | |
Alternatively you can use `pcall` so the JSON module still works when | |
LPeg isn't found. | |
json = require "dkjson" | |
pcall (json.use_lpeg) | |
### `json.using_lpeg` | |
This variable is set to `true` when LPeg was loaded successfully. | |
--------------------------------------------------------------------- | |
Contact | |
------- | |
You can contact the author by sending an e-mail to 'david' at the | |
domain 'dkolf.de'. | |
--------------------------------------------------------------------- | |
*Copyright (C) 2010-2013 David Heiko Kolf* | |
Permission is hereby granted, free of charge, to any person obtaining | |
a copy of this software and associated documentation files (the | |
"Software"), to deal in the Software without restriction, including | |
without limitation the rights to use, copy, modify, merge, publish, | |
distribute, sublicense, and/or sell copies of the Software, and to | |
permit persons to whom the Software is furnished to do so, subject to | |
the following conditions: | |
The above copyright notice and this permission notice shall be | |
included in all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | |
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. | |
<!-- This documentation can be parsed using Markdown to generate HTML. | |
The source code is enclosed in a HTML comment so it won't be displayed | |
by browsers, but it should be removed from the final HTML file as | |
it isn't a valid HTML comment (and wastes space). | |
--> | |
<!--]==] | |
-- global dependencies: | |
local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset = | |
pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset | |
local error, require, pcall, select = error, require, pcall, select | |
local floor, huge = math.floor, math.huge | |
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat = | |
string.rep, string.gsub, string.sub, string.byte, string.char, | |
string.find, string.len, string.format | |
local concat = table.concat | |
local json = { version = "dkjson 2.3" } | |
if register_global_module_table then | |
_G[global_module_name] = json | |
end | |
local _ENV = nil -- blocking globals in Lua 5.2 | |
pcall (function() | |
-- Enable access to blocked metatables. | |
-- Don't worry, this module doesn't change anything in them. | |
local debmeta = require "debug".getmetatable | |
if debmeta then getmetatable = debmeta end | |
end) | |
json.null = setmetatable ({}, { | |
__tojson = function () return "null" end | |
}) | |
local function isarray (tbl) | |
local max, n, arraylen = 0, 0, 0 | |
for k,v in pairs (tbl) do | |
if k == 'n' and type(v) == 'number' then | |
arraylen = v | |
if v > max then | |
max = v | |
end | |
else | |
if type(k) ~= 'number' or k < 1 or floor(k) ~= k then | |
return false | |
end | |
if k > max then | |
max = k | |
end | |
n = n + 1 | |
end | |
end | |
if max > 10 and max > arraylen and max > n * 2 then | |
return false -- don't create an array with too many holes | |
end | |
return true, max | |
end | |
local escapecodes = { | |
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", | |
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t" | |
} | |
local function escapeutf8 (uchar) | |
local value = escapecodes[uchar] | |
if value then | |
return value | |
end | |
local a, b, c, d = strbyte (uchar, 1, 4) | |
a, b, c, d = a or 0, b or 0, c or 0, d or 0 | |
if a <= 0x7f then | |
value = a | |
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then | |
value = (a - 0xc0) * 0x40 + b - 0x80 | |
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then | |
value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80 | |
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then | |
value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80 | |
else | |
return "" | |
end | |
if value <= 0xffff then | |
return strformat ("\\u%.4x", value) | |
elseif value <= 0x10ffff then | |
-- encode as UTF-16 surrogate pair | |
value = value - 0x10000 | |
local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400) | |
return strformat ("\\u%.4x\\u%.4x", highsur, lowsur) | |
else | |
return "" | |
end | |
end | |
local function fsub (str, pattern, repl) | |
-- gsub always builds a new string in a buffer, even when no match | |
-- exists. First using find should be more efficient when most strings | |
-- don't contain the pattern. | |
if strfind (str, pattern) then | |
return gsub (str, pattern, repl) | |
else | |
return str | |
end | |
end | |
local function quotestring (value) | |
-- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js | |
value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8) | |
if strfind (value, "[\194\216\220\225\226\239]") then | |
value = fsub (value, "\194[\128-\159\173]", escapeutf8) | |
value = fsub (value, "\216[\128-\132]", escapeutf8) | |
value = fsub (value, "\220\143", escapeutf8) | |
value = fsub (value, "\225\158[\180\181]", escapeutf8) | |
value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8) | |
value = fsub (value, "\226\129[\160-\175]", escapeutf8) | |
value = fsub (value, "\239\187\191", escapeutf8) | |
value = fsub (value, "\239\191[\176-\191]", escapeutf8) | |
end | |
return "\"" .. value .. "\"" | |
end | |
json.quotestring = quotestring | |
local function addnewline2 (level, buffer, buflen) | |
buffer[buflen+1] = "\n" | |
buffer[buflen+2] = strrep (" ", level) | |
buflen = buflen + 2 | |
return buflen | |
end | |
function json.addnewline (state) | |
if state.indent then | |
state.bufferlen = addnewline2 (state.level or 0, | |
state.buffer, state.bufferlen or #(state.buffer)) | |
end | |
end | |
local encode2 -- forward declaration | |
local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder) | |
local kt = type (key) | |
if kt ~= 'string' and kt ~= 'number' then | |
return nil, "type '" .. kt .. "' is not supported as a key by JSON." | |
end | |
if prev then | |
buflen = buflen + 1 | |
buffer[buflen] = "," | |
end | |
if indent then | |
buflen = addnewline2 (level, buffer, buflen) | |
end | |
buffer[buflen+1] = quotestring (key) | |
buffer[buflen+2] = ":" | |
return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder) | |
end | |
encode2 = function (value, indent, level, buffer, buflen, tables, globalorder) | |
local valtype = type (value) | |
local valmeta = getmetatable (value) | |
valmeta = type (valmeta) == 'table' and valmeta -- only tables | |
local valtojson = valmeta and valmeta.__tojson | |
if valtojson then | |
if tables[value] then | |
return nil, "reference cycle" | |
end | |
tables[value] = true | |
local state = { | |
indent = indent, level = level, buffer = buffer, | |
bufferlen = buflen, tables = tables, keyorder = globalorder | |
} | |
local ret, msg = valtojson (value, state) | |
if not ret then return nil, msg end | |
tables[value] = nil | |
buflen = state.bufferlen | |
if type (ret) == 'string' then | |
buflen = buflen + 1 | |
buffer[buflen] = ret | |
end | |
elseif value == nil then | |
buflen = buflen + 1 | |
buffer[buflen] = "null" | |
elseif valtype == 'number' then | |
local s | |
if value ~= value or value >= huge or -value >= huge then | |
-- This is the behaviour of the original JSON implementation. | |
s = "null" | |
else | |
s = tostring (value) | |
end | |
buflen = buflen + 1 | |
buffer[buflen] = s | |
elseif valtype == 'boolean' then | |
buflen = buflen + 1 | |
buffer[buflen] = value and "true" or "false" | |
elseif valtype == 'string' then | |
buflen = buflen + 1 | |
buffer[buflen] = quotestring (value) | |
elseif valtype == 'table' then | |
if tables[value] then | |
return nil, "reference cycle" | |
end | |
tables[value] = true | |
level = level + 1 | |
local isa, n = isarray (value) | |
if n == 0 and valmeta and valmeta.__jsontype == 'object' then | |
isa = false | |
end | |
local msg | |
if isa then -- JSON array | |
buflen = buflen + 1 | |
buffer[buflen] = "[" | |
for i = 1, n do | |
buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder) | |
if not buflen then return nil, msg end | |
if i < n then | |
buflen = buflen + 1 | |
buffer[buflen] = "," | |
end | |
end | |
buflen = buflen + 1 | |
buffer[buflen] = "]" | |
else -- JSON object | |
local prev = false | |
buflen = buflen + 1 | |
buffer[buflen] = "{" | |
local order = valmeta and valmeta.__jsonorder or globalorder | |
if order then | |
local used = {} | |
n = #order | |
for i = 1, n do | |
local k = order[i] | |
local v = value[k] | |
if v then | |
used[k] = true | |
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder) | |
prev = true -- add a seperator before the next element | |
end | |
end | |
for k,v in pairs (value) do | |
if not used[k] then | |
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder) | |
if not buflen then return nil, msg end | |
prev = true -- add a seperator before the next element | |
end | |
end | |
else -- unordered | |
for k,v in pairs (value) do | |
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder) | |
if not buflen then return nil, msg end | |
prev = true -- add a seperator before the next element | |
end | |
end | |
if indent then | |
buflen = addnewline2 (level - 1, buffer, buflen) | |
end | |
buflen = buflen + 1 | |
buffer[buflen] = "}" | |
end | |
tables[value] = nil | |
else | |
return nil, "type '" .. valtype .. "' is not supported by JSON." | |
end | |
return buflen | |
end | |
function json.encode (value, state) | |
state = state or {} | |
local oldbuffer = state.buffer | |
local buffer = oldbuffer or {} | |
local ret, msg = encode2 (value, state.indent, state.level or 0, | |
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder) | |
if not ret then | |
error (msg, 2) | |
elseif oldbuffer then | |
state.bufferlen = ret | |
return true | |
else | |
return concat (buffer) | |
end | |
end | |
local function loc (str, where) | |
local line, pos, linepos = 1, 1, 0 | |
while true do | |
pos = strfind (str, "\n", pos, true) | |
if pos and pos < where then | |
line = line + 1 | |
linepos = pos | |
pos = pos + 1 | |
else | |
break | |
end | |
end | |
return "line " .. line .. ", column " .. (where - linepos) | |
end | |
local function unterminated (str, what, where) | |
return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where) | |
end | |
local function scanwhite (str, pos) | |
while true do | |
pos = strfind (str, "%S", pos) | |
if not pos then return nil end | |
if strsub (str, pos, pos + 2) == "\239\187\191" then | |
-- UTF-8 Byte Order Mark | |
pos = pos + 3 | |
else | |
return pos | |
end | |
end | |
end | |
local escapechars = { | |
["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f", | |
["n"] = "\n", ["r"] = "\r", ["t"] = "\t" | |
} | |
local function unichar (value) | |
if value < 0 then | |
return nil | |
elseif value <= 0x007f then | |
return strchar (value) | |
elseif value <= 0x07ff then | |
return strchar (0xc0 + floor(value/0x40), | |
0x80 + (floor(value) % 0x40)) | |
elseif value <= 0xffff then | |
return strchar (0xe0 + floor(value/0x1000), | |
0x80 + (floor(value/0x40) % 0x40), | |
0x80 + (floor(value) % 0x40)) | |
elseif value <= 0x10ffff then | |
return strchar (0xf0 + floor(value/0x40000), | |
0x80 + (floor(value/0x1000) % 0x40), | |
0x80 + (floor(value/0x40) % 0x40), | |
0x80 + (floor(value) % 0x40)) | |
else | |
return nil | |
end | |
end | |
local function scanstring (str, pos) | |
local lastpos = pos + 1 | |
local buffer, n = {}, 0 | |
while true do | |
local nextpos = strfind (str, "[\"\\]", lastpos) | |
if not nextpos then | |
return unterminated (str, "string", pos) | |
end | |
if nextpos > lastpos then | |
n = n + 1 | |
buffer[n] = strsub (str, lastpos, nextpos - 1) | |
end | |
if strsub (str, nextpos, nextpos) == "\"" then | |
lastpos = nextpos + 1 | |
break | |
else | |
local escchar = strsub (str, nextpos + 1, nextpos + 1) | |
local value | |
if escchar == "u" then | |
value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16) | |
if value then | |
local value2 | |
if 0xD800 <= value and value <= 0xDBff then | |
-- we have the high surrogate of UTF-16. Check if there is a | |
-- low surrogate escaped nearby to combine them. | |
if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then | |
value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16) | |
if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then | |
value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000 | |
else | |
value2 = nil -- in case it was out of range for a low surrogate | |
end | |
end | |
end | |
value = value and unichar (value) | |
if value then | |
if value2 then | |
lastpos = nextpos + 12 | |
else | |
lastpos = nextpos + 6 | |
end | |
end | |
end | |
end | |
if not value then | |
value = escapechars[escchar] or escchar | |
lastpos = nextpos + 2 | |
end | |
n = n + 1 | |
buffer[n] = value | |
end | |
end | |
if n == 1 then | |
return buffer[1], lastpos | |
elseif n > 1 then | |
return concat (buffer), lastpos | |
else | |
return "", lastpos | |
end | |
end | |
local scanvalue -- forward declaration | |
local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta) | |
local len = strlen (str) | |
local tbl, n = {}, 0 | |
local pos = startpos + 1 | |
if what == 'object' then | |
setmetatable (tbl, objectmeta) | |
else | |
setmetatable (tbl, arraymeta) | |
end | |
while true do | |
pos = scanwhite (str, pos) | |
if not pos then return unterminated (str, what, startpos) end | |
local char = strsub (str, pos, pos) | |
if char == closechar then | |
return tbl, pos + 1 | |
end | |
local val1, err | |
val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) | |
if err then return nil, pos, err end | |
pos = scanwhite (str, pos) | |
if not pos then return unterminated (str, what, startpos) end | |
char = strsub (str, pos, pos) | |
if char == ":" then | |
if val1 == nil then | |
return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")" | |
end | |
pos = scanwhite (str, pos + 1) | |
if not pos then return unterminated (str, what, startpos) end | |
local val2 | |
val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta) | |
if err then return nil, pos, err end | |
tbl[val1] = val2 | |
pos = scanwhite (str, pos) | |
if not pos then return unterminated (str, what, startpos) end | |
char = strsub (str, pos, pos) | |
else | |
n = n + 1 | |
tbl[n] = val1 | |
end | |
if char == "," then | |
pos = pos + 1 | |
end | |
end | |
end | |
scanvalue = function (str, pos, nullval, objectmeta, arraymeta) | |
pos = pos or 1 | |
pos = scanwhite (str, pos) | |
if not pos then | |
return nil, strlen (str) + 1, "no valid JSON value (reached the end)" | |
end | |
local char = strsub (str, pos, pos) | |
if char == "{" then | |
return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta) | |
elseif char == "[" then | |
return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta) | |
elseif char == "\"" then | |
return scanstring (str, pos) | |
else | |
local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos) | |
if pstart then | |
local number = tonumber (strsub (str, pstart, pend)) | |
if number then | |
return number, pend + 1 | |
end | |
end | |
pstart, pend = strfind (str, "^%a%w*", pos) | |
if pstart then | |
local name = strsub (str, pstart, pend) | |
if name == "true" then | |
return true, pend + 1 | |
elseif name == "false" then | |
return false, pend + 1 | |
elseif name == "null" then | |
return nullval, pend + 1 | |
end | |
end | |
return nil, pos, "no valid JSON value at " .. loc (str, pos) | |
end | |
end | |
local function optionalmetatables(...) | |
if select("#", ...) > 0 then | |
return ... | |
else | |
return {__jsontype = 'object'}, {__jsontype = 'array'} | |
end | |
end | |
function json.decode (str, pos, nullval, ...) | |
local objectmeta, arraymeta = optionalmetatables(...) | |
return scanvalue (str, pos, nullval, objectmeta, arraymeta) | |
end | |
function json.use_lpeg () | |
local g = require ("lpeg") | |
local pegmatch = g.match | |
local P, S, R, V = g.P, g.S, g.R, g.V | |
local function ErrorCall (str, pos, msg, state) | |
if not state.msg then | |
state.msg = msg .. " at " .. loc (str, pos) | |
state.pos = pos | |
end | |
return false | |
end | |
local function Err (msg) | |
return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall) | |
end | |
local Space = (S" \n\r\t" + P"\239\187\191")^0 | |
local PlainChar = 1 - S"\"\\\n\r" | |
local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars | |
local HexDigit = R("09", "af", "AF") | |
local function UTF16Surrogate (match, pos, high, low) | |
high, low = tonumber (high, 16), tonumber (low, 16) | |
if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then | |
return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000) | |
else | |
return false | |
end | |
end | |
local function UTF16BMP (hex) | |
return unichar (tonumber (hex, 16)) | |
end | |
local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit)) | |
local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP | |
local Char = UnicodeEscape + EscapeSequence + PlainChar | |
local String = P"\"" * g.Cs (Char ^ 0) * (P"\"" + Err "unterminated string") | |
local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0)) | |
local Fractal = P"." * R"09"^0 | |
local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1 | |
local Number = (Integer * Fractal^(-1) * Exponent^(-1))/tonumber | |
local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1) | |
local SimpleValue = Number + String + Constant | |
local ArrayContent, ObjectContent | |
-- The functions parsearray and parseobject parse only a single value/pair | |
-- at a time and store them directly to avoid hitting the LPeg limits. | |
local function parsearray (str, pos, nullval, state) | |
local obj, cont | |
local npos | |
local t, nt = {}, 0 | |
repeat | |
obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state) | |
if not npos then break end | |
pos = npos | |
nt = nt + 1 | |
t[nt] = obj | |
until cont == 'last' | |
return pos, setmetatable (t, state.arraymeta) | |
end | |
local function parseobject (str, pos, nullval, state) | |
local obj, key, cont | |
local npos | |
local t = {} | |
repeat | |
key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state) | |
if not npos then break end | |
pos = npos | |
t[key] = obj | |
until cont == 'last' | |
return pos, setmetatable (t, state.objectmeta) | |
end | |
local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected") | |
local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected") | |
local Value = Space * (Array + Object + SimpleValue) | |
local ExpectedValue = Value + Space * Err "value expected" | |
ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp() | |
local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue) | |
ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp() | |
local DecodeValue = ExpectedValue * g.Cp () | |
function json.decode (str, pos, nullval, ...) | |
local state = {} | |
state.objectmeta, state.arraymeta = optionalmetatables(...) | |
local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state) | |
if state.msg then | |
return nil, state.pos, state.msg | |
else | |
return obj, retpos | |
end | |
end | |
-- use this function only once: | |
json.use_lpeg = function () return json end | |
json.using_lpeg = true | |
return json -- so you can get the module using json = require "dkjson".use_lpeg() | |
end | |
if always_try_using_lpeg then | |
pcall (json.use_lpeg) | |
end | |
return json | |
--> |
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
-- AutoGist | |
--Place these at the top of your main | |
--VERSION should be a string and can be any format that you wish to keep track of versions | |
--VERSION = "X.X.X" -- Use this to set Version Numbers Or set Version in class creation | |
--BUILD = false -- Include this Global if you want a separate Gist for builds *true* creates a build gist | |
VERSION = "2.1.4" | |
--If Build is true version number is ignored and a Private gist is created for builds | |
--If Build is false or nil the Version checks for a Release gist update | |
function setup() | |
--create an instance of AutoGist | |
--Add this to the project that you would like to backup | |
--@param ProjectName -- must match your project | |
--@param Description of your project | |
--@param version number. I like using a global VERSION at the top. | |
autoGist = AutoGist("AutoGist","AutoGist your Codea projects!",VERSION)--Do not edit this one | |
-- create plugin manager and add ReorderTabs Plugin | |
autoPlugins=createAutoPlugins() | |
autoPlugins.plugins[ReorderTabs.name] = ReorderTabs(autoGist) | |
--This must be called to check for backups | |
--@param Create a public Gist = true, private gist is false | |
--autoGist:backup(false) | |
--Below used in AutoGist UI only | |
---------------------------- | |
authParams() | |
end | |
-- creates plugin manager | |
function createAutoPlugins() | |
local plugins = { | |
cur = false,params={},plugins = {}, | |
addbk = function(self) parameter.action("Back",function()output.clear()parameter.clear() | |
if self:hasfunc(self.cur,"dispose") then self.cur:dispose() self:setup() self.cur = false | |
else authParams() end end) end, | |
foreach=function(self,f) for n,p in pairs(self.plugins) do f({name=n,plugin=p})end end, | |
hasfunc=function(self,o,f) return type(o)=="table" and type(o[f])== "function"end, | |
setup=function(self) output.clear() parameter.clear() self:addbk() | |
self:foreach(function(p) if self:hasfunc(p.plugin,"setup") then | |
parameter.action(p.name,function() parameter.clear() parameter.action(p.name) | |
self.cur = p.plugin self:addbk() self.cur:setup(self) end) end end) end, | |
draw=function(self) background(0) if self.cur ~= false and self:hasfunc(self.cur,"draw") then | |
self.cur:draw() end end, | |
touched=function(self,t) if self:hasfunc(self.cur,"touched") then self.cur:touched(t) end end, | |
orientationChanged = function(self,n) if self:hasfunc(self.cur,"orientationChanged") then | |
self.cur:orientationChanged(n) end end | |
} | |
return plugins | |
end | |
-- forward draw and touched to plugin manager | |
function draw() | |
autoPlugins:draw() | |
end | |
function touched(touch) | |
autoPlugins:touched(touch) | |
end | |
function orientationChanged(new) | |
if autoPlugins ~= nil then autoPlugins:orientationChanged(new) end | |
end | |
function authParams() | |
if autoGist.agData.authtoken ~=nil then | |
parameter.action("ResetGistAuth",function() | |
autoGist.agData.authtoken = nil | |
autoGist:saveData() | |
parameter.clear() authParams() end) | |
getParams() | |
-- add plugins button | |
parameter.action("Plugins",function() autoPlugins:setup() end) | |
else | |
parameter.text("GistUserName") | |
parameter.text("GistPassword") | |
parameter.action("Get Gist Auth",function() autoGist:getAuth(GistUserName,GistPassword,function() | |
authParams() getParams() end) | |
parameter.clear() end) | |
end | |
end | |
function getParams() | |
parameter.action("Manual_Gist",function() parameter.clear() manualGist()end) | |
parameter.action("Create_Installer",function() parameter.clear() createInstaller() end) | |
--parameter.action("Update_AutoGist", function() autoGist:fetchGist("5525674","AutoGist") end) | |
--checkUpdate() | |
end | |
function manualGist() | |
parameter.action("Back",function() parameter.clear() authParams()end) | |
parameter.text("Codea_Project_Name") | |
parameter.text("Gist_ID") | |
parameter.boolean("Public", true) | |
parameter.action("Gist_It", function() | |
local project = autoGist:getProject(Codea_Project_Name, Public) | |
autoGist:doManualGist(project, Gist_ID) | |
end) | |
parameter.action("Get_Gist_Project",function() autoGist:fetchGist(Gist_ID,Codea_Project_Name) end) | |
end | |
function createInstaller() | |
parameter.action("Back",function() parameter.clear() authParams()end) | |
parameter.text("Name_Of_Project") | |
parameter.text("GistId_Of_Project") | |
parameter.action("Create_Install",function() autoGist:createInstall(Name_Of_Project,GistId_Of_Project) end) | |
end | |
function checkUpdate() | |
local gistUrl = "https://api.github.com/gists/5525674" | |
local handleSuccess = function(data) | |
local gist = json.decode(data) | |
local version = string.match(gist.description,"%d+%.%d+%.%d+") | |
--print(gist.description) | |
--print(version) | |
if VERSION ~= version then | |
print("New version "..version) | |
print("Click Update") | |
parameter.action("Update_AutoGist", function() autoGist:fetchGist("5525674","AutoGist") end) | |
end | |
end | |
local handleFailure = function(data) | |
sound(SOUND_EXPLODE, 32351) | |
print("AutoGist Failed to check for updates") | |
end | |
http.request(gistUrl,handleSuccess, handleFailure) | |
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
-------------------------------------------------------- | |
-- ReorderTabs Plugin for AutoGist -- | |
-- Version: 1.0.0 Written By XanDDemoX -- | |
-------------------------------------------------------- | |
ReorderTabs = class() | |
-- static members | |
ReorderTabs.name = "Reorder Project Tabs" | |
-- set default window config | |
ReorderTabs.defaultwindow = { | |
headerheight=80, | |
headertextcol = color(255, 255, 255, 255), | |
headerbackcol = color(50, 102, 74, 255), | |
tabbackcols ={color(19, 80, 188, 255),color(98, 18, 188, 255)}, | |
tabtextcols ={color(255,255,255,255),color(255,255,255,255)}, | |
tabselectedbackcol = color(255,0,0), | |
tabselectedtextcol = color(255,255,255), | |
tabheight = 50, | |
scrollspeed = 20, | |
scrollratio = 1/4, | |
progressbarbackcol = color(0, 36, 255, 255), | |
progressbartextcol = color(255,255,255), | |
progressbarbarcol = color(34, 125, 37, 255), | |
progressbarwidth = 300, | |
progressbarheight = 30 | |
} | |
-- instance member | |
function ReorderTabs:init(autogist,window) | |
-- autogist instance | |
self.autoGist = autogist | |
-- set initialstate | |
self:dispose() | |
-- set window params | |
self.window = window or ReorderTabs.defaultwindow | |
self.view = {x=0,y=0,w=WIDTH,h=(HEIGHT-self.window.headerheight), | |
minY = 0, maxY = HEIGHT, totalheight = HEIGHT | |
} | |
self.currentTouch = nil | |
self.isPressed = false | |
end | |
-- setup ui parameterts - automatically called by plugin manager | |
function ReorderTabs:setup() | |
parameter.text("Codea_Project_Name") | |
parameter.action("Load",function() self:loadTabs() end) | |
parameter.action("Move Tabs Up",function() self:moveTabsUp() end) | |
parameter.action("Move Tabs Down",function() self:moveTabsDown() end) | |
parameter.action("Clear Selection",function() self:setSelected(function()return false end)end) | |
parameter.text("Optional_Target_Project_Name") | |
parameter.action("Save" ,function() self:saveTabs(Optional_Target_Project_Name) end) | |
end | |
-- dispose /reset to initial state - automatically called by plugin manager | |
function ReorderTabs:dispose() | |
while self._isSaving == true do | |
self:continueSave() | |
end | |
self.target = nil | |
self.project = nil | |
self.order = {} | |
self.tabsdsp ={} | |
self.loaded = false | |
self._saveRoutine = nil | |
self._isSaving = false | |
self._saveProgress = 0 | |
self._savePrepared = false | |
self._saveFinished = false | |
end | |
-- loads the tabs from the project name entered with auto gist | |
function ReorderTabs:loadTabs() | |
if self._isSaving == true then return end -- if saving dont allow disopse by user presssing save button | |
self:dispose() | |
self.target = Codea_Project_Name -- check project exists | |
if ReorderTabs.projectExists(self.target) == false then | |
self.target = nil | |
return | |
end | |
-- get project via autogist | |
self.project = self.autoGist:getProject(self.target,true) | |
-- hide output | |
output.clear() | |
self:printpmsg(" tabs loaded!") | |
-- get the tab order from the project | |
self.order = ReorderTabs.getProjectOrder(self.project,self.target) | |
-- create the display from the loaded order | |
self:createTabsDisplay() | |
-- set loaded flag | |
self.loaded = true | |
end | |
-- creates or refreshes the ui items used to draw the display | |
function ReorderTabs:createTabsDisplay(selected) | |
selected = selected or {} | |
-- create items and determine where to draw them | |
local curY = 0 | |
for i,tab in ipairs(self.order) do | |
-- setup items with params from window settings | |
self.tabsdsp[tab] = {x=0,y=curY,w=WIDTH,h=self.window.tabheight,selected=s, | |
backcol=ReorderTabs.gettblColour(self.window.tabbackcols,i), | |
textcol=ReorderTabs.gettblColour(self.window.tabtextcols,i), | |
sbackcol = self.window.tabselectedbackcol, | |
stextcol = self.window.tabselectedtextcol | |
} | |
curY = curY - self.window.tabheight | |
end | |
-- set any selected | |
for i,v in ipairs(selected) do | |
self.tabsdsp[v.value].selected = true | |
end | |
-- setup the view movement constraints | |
self.view.minY = 0 | |
self.view.maxY =(self.window.tabheight*#self.order)+self.window.headerheight | |
self.view.totalheight = self.view.maxY | |
self.view.maxY =self.view.maxY-HEIGHT | |
end | |
function ReorderTabs:getSelected() | |
if self.loaded == false then return {} end | |
local selected ={} | |
for i,v in ipairs(self.order) do | |
td = self.tabsdsp[v] | |
if td.selected == true then | |
table.insert(selected,{index=i,value=v,newindex=0}) | |
end | |
end | |
return selected | |
end | |
--sets ui tabs selected or deselected from a function, table or index in order array | |
function ReorderTabs:setSelected(tabs,selected) | |
if self.loaded == false or self._isSaving == true or tabs == nil then return end | |
if selected == nil then selected = true end | |
local tabsType = type(tabs) | |
if tabsType == "table" then -- if table select items based on items from self.order passed | |
for i,v in ipairs(tabs) do | |
self.tabsdsp[self.order[v]].selected = selected | |
end | |
elseif tabsType == "function" then -- if function pass all items to function and set selected based on result | |
for i,v in ipairs(self.order) do | |
self.tabsdsp[v].selected = tabs(v,self.tabsdsp[v]) | |
end | |
elseif tabsType == "number" then -- if number set based on index | |
self.tabsdsp[self.order[tabs]].selected = selected | |
end | |
end | |
-- shifts the selected tabs up or down by one | |
function ReorderTabs:shiftSelectedTabs(up) | |
amount = amount or 0 | |
local clone = {} | |
local selected ={} | |
local td = nil | |
local count = 0 | |
local itemcount=#self.order | |
-- create clone and count selected | |
for i,v in ipairs(self.order) do | |
table.insert(clone,v) | |
td = self.tabsdsp[v] | |
if td.selected == true then | |
table.insert(selected,{index=i,value=v,newindex=0}) | |
count = count + 1 | |
end | |
end | |
-- if nothin to do do nothing | |
if count == 0 then | |
return self.order,selected | |
end | |
-- get create new indexes for selected items | |
local newIndex = 0 | |
for i,v in pairs(selected) do | |
if up then -- create and constrain indexes | |
newIndex = v.index -1 | |
if newIndex < 1 then | |
newIndex = 1 | |
end | |
else | |
newIndex = v.index +1 | |
if newIndex > itemcount then | |
newIndex = itemcount | |
end | |
end | |
v.newindex = newIndex | |
end | |
-- sort ascending if moving up and sort descending if moving down | |
table.sort(selected,function(x,y) if up then return x.newindex < y.newindex | |
else return x.newindex > y.newindex end end) | |
-- ammend clone array with items from self.order to stop items getting all mixed up :) | |
local v1 = nil | |
local v2 = nil | |
for i,v in pairs(selected) do | |
v1 = self.order[v.index] | |
v2 = self.order[v.newindex] | |
table.remove(clone,v.index) | |
table.insert(clone,v.newindex,v.value) | |
end | |
return clone,selected | |
end | |
-- shifts the selected items up by one and refreshes display | |
function ReorderTabs:moveTabsUp() | |
if self.loaded == false or self._isSaving == true then return end | |
local clone,selected =self:shiftSelectedTabs(true) | |
self.order = clone | |
self:createTabsDisplay(selected) | |
end | |
-- shifts the selected items down by one and refreshes display | |
function ReorderTabs:moveTabsDown() | |
if self.loaded == false or self._isSaving == true then return end | |
local clone,selected =self:shiftSelectedTabs(false) | |
self.order =clone | |
self:createTabsDisplay(selected) | |
end | |
-- starts the save operation (the slightly scary bit) | |
function ReorderTabs:saveTabs(targetProject) | |
if self.loaded == false or self._isSaving == true then return end | |
-- set target project to either a new target or the current loaded project | |
targetProject = targetProject or self.target | |
-- check twice so we can specifically see if self.target is nil - | |
--targetProject could be from a parameter | |
if targetProject == nil or targetProject == "" then targetProject = self.target end | |
if targetProject == nil or targetProject == "" then return end | |
-- check target project exists | |
if ReorderTabs.projectExists(targetProject) == false then | |
return | |
end | |
-- display messages | |
self:printpmsg(" saving tabs await confirmation!") | |
self:printpmsg(" preparing to save do not exit!...") | |
-- setup coroutine to do saving | |
self._saveRoutine = coroutine.create(function(progress,fin) | |
local finished = fin or false | |
-- setup progress counter and progress amount | |
local p = progress or 0 | |
local pval = (100 / #self.order) | |
while finished == false do | |
-- first prepare the project | |
--(which means delete all tabs so we can guarentee the order they're put back in >_<) | |
if self._savePrepared == false then | |
-- get lookup to see if the tabs are in the target project | |
local projectTabs = | |
ReorderTabs.getProjectTabNamesLookup(targetProject) | |
for i,v in ipairs(self.order) do | |
-- we can remove all tabs except the main | |
if projectTabs[v.value]~= nil and v.value ~= "Main" then | |
-- remove tab | |
-- only remove tabs from the loaded project - this way it wont delete any tabs | |
-- if the target project is set to an unintended project (i.e not the same or blank) | |
saveProjectTab(targetProject..":"..v.value,nil) | |
self:printpmsg(", Tab: "..v.value.." prepared!") | |
p, finished = coroutine.yield(p,finished) | |
end | |
end | |
self:printpmsg(" prepared!") | |
-- switch to saving when done and notify of progress | |
self._savePrepared = true | |
p, finished = coroutine.yield(p,finished) | |
else | |
-- now save all the tabs back in using their data from autogist | |
for i,v in ipairs(self.order) do | |
--save tab | |
saveProjectTab(targetProject..":"..v.value,self.project.files[v.gkey].content) | |
self:printpmsg(", Tab: "..v.value.." saved : "..v.key) | |
-- add progress value to current progress (for display) | |
p = p + pval | |
finished = false | |
-- update progress | |
p, finished = coroutine.yield(p,finished) | |
end | |
finished = true | |
end | |
end | |
self:printpmsg(" tab order saved!") | |
-- update progress so finished flag makes it through | |
p, finished = coroutine.yield(p,finished) | |
end) | |
-- set saving flag to start the save | |
self._isSaving = true | |
end | |
-- continues the save when saving or does nothing | |
-- this enables progress updates | |
function ReorderTabs:continueSave() | |
local ok = false | |
if self._isSaving == true and self._saveFinished == false then | |
ok,self._saveProgress,self._saveFinished = | |
coroutine.resume(self._saveRoutine,self._saveProgress,self._saveFinished) | |
-- if finshed reset save state so we can save again | |
if self._saveFinished then | |
self._isSaving = false | |
self._saveFinished = false | |
self._savePrepared = false | |
self._saveProgress = 0 | |
self._saveRoutine = nil | |
end | |
end | |
end | |
-- prints a message to output | |
function ReorderTabs:printpmsg(txt) | |
print("Project: "..self.target..txt) | |
end | |
-- draws the ui (automatically called by plugin manager) | |
function ReorderTabs:draw() | |
if self.loaded == false then return end | |
pushStyle() | |
-- first item start | |
translate(0,HEIGHT-(self.window.headerheight+self.window.tabheight)) | |
translate(self.view.x,self.view.y) | |
-- tabs | |
local td = nil | |
for i,tab in ipairs(self.order) do | |
td = self.tabsdsp[tab] | |
-- tab background | |
if td.selected == true then | |
fill(td.sbackcol) | |
else | |
fill(td.backcol) | |
end | |
rect(0,td.y,WIDTH,self.window.tabheight) | |
-- tab text | |
if td.selected == true then | |
fill(td.stextcol) | |
else | |
fill(td.textcol) | |
end | |
textAlign(CENTER) | |
text(tab.value,WIDTH/2, (td.y+(self.window.tabheight/2))) | |
end | |
--headerstart | |
translate(0,self.window.headerheight+(self.window.tabheight-self.window.headerheight)) | |
-- header bar -- | |
fill(self.window.headerbackcol) | |
rect(0,0,WIDTH,self.window.headerheight) | |
-- header text | |
fill(self.window.headertextcol) | |
textAlign(CENTER) | |
text(self.target,WIDTH/2, (self.window.headerheight/2)) | |
self:drawProgressBar() | |
popStyle() | |
-- continue the save at the end of the draw | |
self:continueSave() | |
end | |
function ReorderTabs:drawProgressBar() | |
if self.loaded == true and self._isSaving == true and self._saveFinished == false then | |
-- draw the backgound | |
fill(self.window.progressbarbackcol) | |
rect((WIDTH-self.window.progressbarwidth),0,self.window.progressbarwidth,self.window.progressbarheight) | |
-- draw the bar | |
fill(self.window.progressbarbarcol) | |
local barwidth = (self.window.progressbarwidth /100) * self._saveProgress | |
rect((WIDTH-self.window.progressbarwidth),0,barwidth,self.window.progressbarheight) | |
fill(self.window.progressbartextcol) | |
-- draw the text | |
text(tostring(math.floor(self._saveProgress)).."%", | |
(WIDTH-(self.window.progressbarwidth/2)), | |
(self.window.progressbarheight/2)) | |
--print(tostring(math.floor(self._saveProgress)).."%") | |
end | |
end | |
-- recieve touch events and process (automatically called by plugin manager) | |
function ReorderTabs:touched(touch) | |
-- if not loaded load on a double tap | |
if self.loaded == false then | |
if touch.state == ENDED and touch.tapCount == 2 then | |
self:loadTabs() | |
end | |
return | |
end | |
-- track the first touch to begin | |
if not self.isPressed then | |
if touch.state == BEGAN then | |
self.currentTouch = touch | |
self.isPressed = true | |
end | |
else -- see if its the same | |
if self.currentTouch.id == touch.id then | |
self.currentTouch = touch | |
-- track whilst its moving and adjust view based on deltaY | |
if touch.state == MOVING and self.view.totalheight > HEIGHT then | |
if touch.deltaY > 0 then | |
self.view.y = self.view.y + | |
((self.window.tabheight*self.window.scrollratio) + | |
(self.window.scrollspeed*touch.deltaY)*DeltaTime) | |
else | |
self.view.y = self.view.y - | |
((self.window.tabheight*self.window.scrollratio) + | |
((self.window.scrollspeed*-touch.deltaY) *DeltaTime)) | |
end | |
-- constrain | |
if self.view.y < self.view.minY then | |
self.view.y = self.view.minY | |
elseif self.view.y > self.view.maxY then | |
self.view.y =self.view.maxY | |
end | |
elseif touch.state == ENDED then -- reset when it ends | |
self.currentTouch = nil | |
self.isPressed = false | |
end | |
end | |
end | |
if touch.state == ENDED and touch.tapCount == 2 then -- detect double taps and | |
--invert the matching tabs selected state | |
local tab = nil | |
for i,v in ipairs(self.order) do | |
tab = self.tabsdsp[v] | |
if ReorderTabs.tabcontains(tab,self.view,touch.x,touch.y) then | |
self:setSelected(i,not tab.selected) | |
end | |
end | |
end | |
end | |
-- refresh when orientation changes | |
function ReorderTabs:orientationChanged(new) | |
if self.loaded == true then | |
self:createTabsDisplay(self:getSelected()) | |
end | |
end | |
--static funcs | |
-- get project order from autogist project | |
function ReorderTabs.getProjectOrder(project,target) | |
local taborder = project.files["1aTabOrder"].content | |
local name ="" | |
local strStart=1 | |
local strEnd=0 | |
local tabs = {} | |
repeat | |
name,strStart,strEnd = ReorderTabs.getNextTabName(taborder,strStart,strEnd) | |
if name ~= nil then | |
local tab ={key=target..":"..name..".lua",value=name,gkey =name..".lua"} | |
table.insert(tabs,tab) | |
end | |
until name == nil | |
return tabs | |
end | |
-- get the next tab reading the autogist tab order | |
function ReorderTabs.getNextTabName(taborder,strStart,strEnd) | |
strStart = string.find(taborder,"#",strEnd) | |
strEnd = string.find(taborder,"\n",strStart) | |
if strStart ~= nil then | |
local name = string.sub(taborder,strStart+1,strEnd-1) | |
return name,strStart,strEnd | |
end | |
end | |
-- check whether project exists | |
function ReorderTabs.projectExists(target) | |
if target == nil or listProjectTabs(target)== nil or #listProjectTabs(target) == 0 then | |
target = target or "Project" | |
print(target.." does not exist!") | |
return false | |
end | |
return true | |
end | |
-- get a projects tabnames as a lookup to check if the project contains a tab (by inverting i and v) | |
function ReorderTabs.getProjectTabNamesLookup(name) | |
local tabs = listProjectTabs(Codea_Project_Name) | |
local lookup = {} | |
for i,v in ipairs(tabs) do | |
lookup[v] = i | |
end | |
return lookup | |
end | |
-- get the next item from a table based on a counter | |
function ReorderTabs.gettblColour(tbl,i) | |
return tbl[1+math.fmod(i,#tbl)] | |
end | |
-- see if a tab contains a touch within the view | |
function ReorderTabs.tabcontains(tab,view,x,y,t) | |
t=t or 0 | |
local tv = t / 2 | |
local sl = tab.x - tv | |
local sr = (tab.x + tab.w) + tv | |
local st = ((((view.h-tab.h)+tab.y) + tab.h)+view.y) - tv | |
local sb = (((view.h-tab.h)+tab.y) +view.y)+ tv | |
local hasxy = x > sl and x < sr and y < st and y > sb | |
return hasxy | |
end | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment