diff --git a/yam/align/place.lua b/yam/align/place.lua new file mode 100644 index 0000000..91c34bf --- /dev/null +++ b/yam/align/place.lua @@ -0,0 +1,143 @@ +---------------------------------------------------------------------------------- +-- Aligns and distributes display objects +-- Usage: +-- you must provide the alignment options and a table with the list of display objects to align/distribute. +-- The first display object in the table will be used as reference to align all other objects. +-- Example: +-- place(Align.DISTRIBUTE_RIGHT,[rect1, rect2]) +-- will place rect2 by the right side of rect1 using the rect1.x + rect.width as reference.. +-- +-- Available alignment and distribution options are: +-- +-- DISTRIBUTE_RIGHT, DISTRIBUTE_LEFT, DISTRIBUTE_UP & DISTRIBUTE_DOWN +-- RIGHT, LEFT, TOP & BOTTOM +-- CENTER, CENTER_HORIZONTAL & CENTER_VERTICAL (Not implemented yet) +---------------------------------------------------------------------------------- + +local M = {} +require('yam.utils.bit') + +M.right = 1 +M.left = 2 +M.top = 4 +M.bottom = 8 +-- M.center = 16 +M.centerHorizontal = 32 +M.centerVertical = 64 +M.distributeRight = 128 +M.distributeLeft = 256 +M.distributeUp = 512 +M.distributeDown = 1024 + +local function placeTo(items, prop, size, operator) + local c = 2 + + items[c-1]:setReferencePoint(display.TopLeftReferencePoint) + items[1].x = items[1].x + items[1].y = items[1].y + + while (c <= #items) do + local reffSize = items[c - 1][size]; + local reffPos = items[c - 1][prop]; + local sizing = items[c][size]; + local objPos = items[c][prop]; + + items[c]:setReferencePoint(display.TopLeftReferencePoint) + if operator == 1 then + items[c][prop] = (reffPos + reffSize) + else + items[c][prop] = (reffPos - sizing) + end + -- items[c][prop] = items[c][prop] + ((reffPos - objPos) * operator + sizing) * operator; + + c = c+1 + end +end + +local function placeBy(items, prop, size, operator) + local c = 2 + items[c-1]:setReferencePoint(display.TopLeftReferencePoint) + items[1].x = items[1].x + items[1].y = items[1].y + + local reffSize = items[1][size]; + local reffPos = items[1][prop]; + while (c <= #items) do + items[c]:setReferencePoint(display.TopLeftReferencePoint) + if(items[c] ~= nil) then + local objSize = items[c][size] + local objPos = items[c][prop] + local sizing = reffSize + + local s = sizing - objSize + if(operator == 1) then + s = 0 + end + items[c][prop] = items[c][prop] + ((((reffPos - objPos + s)) * operator) * operator) + end + c = c+1 + end +end + +local function placeCenter(items, prop, size) + local c = 2; + items[c-1]:setReferencePoint(display.TopLeftReferencePoint) + items[1].x = items[1].x + items[1].y = items[1].y + + local sizing = forceSize + local reffSize = items[1][size] + local reffPos = items[1][prop] + local reff = reffSize/2 + + while (c <= #items) do + items[c]:setReferencePoint(display.TopLeftReferencePoint) + local sizing = items[c][size]; + local objPos = items[c][prop]; + items[c][prop] = items[c][prop] - ((objPos - reffPos) + (sizing * .5 - reff)); + + c = c+1 + end +end + +function M.align(alignment, items) + + -- Safety check + if #items == 0 then + return + end + + if(bit.band(alignment, M.right) ~= 0) then + placeBy(items, "x", "width", -1) + end + if(bit.band(alignment, M.left) ~= 0) then + placeBy(items, "x", "width", 1) + end + if(bit.band(alignment, M.top) ~= 0) then + placeBy(items, "y", "height", 1) + end + if(bit.band(alignment, M.bottom) ~= 0) then + placeBy(items, "y", "height", -1) + end + if(bit.band(alignment, M.centerHorizontal) ~= 0) then + placeCenter(items, "x","width") + end + if(bit.band(alignment, M.centerVertical) ~= 0) then + placeCenter(items, "y","height") + end + if(bit.band(alignment, M.distributeRight) ~= 0) then + placeTo(items, "x", "width", 1) + end + if(bit.band(alignment, M.distributeLeft) ~= 0) then + placeTo(items, "x","width", -1) + end + if(bit.band(alignment, M.distributeUp) ~= 0) then + placeTo(items, "y", "height", -1) + end + if(bit.band(alignment, M.distributeDown) ~= 0) then + placeTo(items, "y", "height", 1) + end + +end + +return M \ No newline at end of file diff --git a/yam/application.lua b/yam/application.lua new file mode 100644 index 0000000..18a0500 --- /dev/null +++ b/yam/application.lua @@ -0,0 +1,49 @@ +----------------------------------------------------------- +-- Setting global access to middleclass, underscore & i18n +----------------------------------------------------------- +require 'yam.middleclass' +_ = require 'yam.underscore' +require("yam.utils.I18n") +i18n = I18n:new() + +------------------------------------------------------ +-- Global Screen Dimensions +------------------------------------------------------ + centerX = display.contentCenterX + centerY = display.contentCenterY + screenX = display.screenOriginX + screenY = display.screenOriginY + screenWidth = display.contentWidth - screenX * 2 +screenHeight = display.contentHeight - screenY * 2 + screenLeft = screenX + screenRight = screenX + screenWidth + screenTop = screenY +screenBottom = screenY + screenHeight + + + +local orm = require "yam.db.orm" +local storyboard = require "storyboard" + +Yam = class('Yam') +function Yam:initialize(appName) + self.appName = appName + print ("yam: Initializing " .. appName) + -- Load add params + local params = require "data.parameters" + -- Custom user parameters + pcall(function() + local myConfig = require "data.myParameters" + for k,v in pairs(myConfig) do + params[k] = v + end + end) + + -- Initiate database + print ("yam: Initializing database") + _G.db = orm.initialize( system.pathForFile(appName .. ".db", system.DocumentsDirectory) ) + + -- Start navigation + storyboard.params = params + storyboard.gotoScene(storyboard.params.firstView) +end \ No newline at end of file diff --git a/yam/db/orm.lua b/yam/db/orm.lua new file mode 100644 index 0000000..458d1cb --- /dev/null +++ b/yam/db/orm.lua @@ -0,0 +1,246 @@ +------------------------------------------------------------------------------ +-- Object Relational Mapping to SQLite +-- Based on https://github.com/radamanthus/radlib +------------------------------------------------------------------------------ +local sql = require 'yam.db.sql' +local sqlite3 = require "sqlite3" + + +local M = {} + +------------------------------------------------------------------------------ +-- Utility function to correctly escape single quotes on string +------------------------------------------------------------------------------ +local toSqlString = function( str ) + local result = string.gsub( str, "'", "'''") + result = "'" .. result .. "'" + return result +end + +------------------------------------------------------------------------------ +-- Initialize the database connection +------------------------------------------------------------------------------ +local initialize = function( dbPath ) + if dbPath ~= nil then + _G.db = sqlite3.open( dbPath ) + else + _G.db = sqlite3.open_memory() + end + return db +end +M.initialize = initialize + +------------------------------------------------------------------------------ +-- Close the database +------------------------------------------------------------------------------ +local close = function() + _G.db:close() +end +M.close = close + +------------------------------------------------------------------------------ +-- Return all the contents of an SQLite table as a table structure +------------------------------------------------------------------------------ +local selectAll = function( tableName, params ) + local result = {} + local s = sql.generateSelect({ + tableName = tableName, + order = params.order, + limit = params.limit + }) + for row in _G.db:nrows(s) do + result[#result+1] = row + end + return result +end +M.selectAll = selectAll + +------------------------------------------------------------------------------ +-- Return contents of an SQLite table filtered by a WHERE query +-- Return value is a table structure +------------------------------------------------------------------------------ +local selectWhere = function(tableName, params ) + local result = {} + local s = sql.generateSelect({ + tableName = tableName, + where = params.where, + order = params.order, + limit = params.limit + }) + for row in _G.db:nrows(s) do + result[#result+1] = row + end + return result +end +M.selectWhere = selectWhere + +------------------------------------------------------------------------------ +-- Return the row from the given table, +-- selected from the given key/keyValue pair +------------------------------------------------------------------------------ +local selectOne = function(tableName, key, keyValue) + local result = {} + local s = sql.generateSelect({ + tableName = tableName, + where = key .. " = " .. keyValue, + limit = 1 + }) + for row in _G.db:nrows(s) do + result[1] = row + break + end + return result[1] +end +M.selectOne = selectOne + +------------------------------------------------------------------------------ +-- Returns the number of rows for the given table +------------------------------------------------------------------------------ +local getTableRowCount = function(tableName) + local rowCount = 0 + for row in _G.db:nrows("SELECT COUNT(*) as rowcount FROM " .. tableName) do + rowCount = row.rowcount + end + return rowCount +end +M.getTableRowCount = getTableRowCount + +------------------------------------------------------------------------------ +-- Inserts a row into the given table +------------------------------------------------------------------------------ +local insertRow = function( tableName, row ) + -- temporary holding variables + local columnList = " ( " + local valuesList = " VALUES(" + + -- format column values into SQL-safe strings + -- then concatenate them together + for i,v in pairs(row) do + local colName = i + local colValue = v + if type(v) == 'string' then + colValue = toSqlString(v) + end + columnList = columnList .. colName .. "," + valuesList = valuesList .. colValue .. "," + end + + -- strip off the trailing comma and add a closing parentheses + columnList = string.sub( columnList, 1, columnList:len()-1 ) .. ')' + valuesList = string.sub( valuesList, 1, valuesList:len()-1 ) .. ')' + + -- prepare the complete SQL command + local sql = "INSERT INTO " .. tableName .. columnList .. valuesList + + -- execute the SQL command for inserting the row + _G.db:exec( sql ) +end +M.insertRow = insertRow + +------------------------------------------------------------------------------ +-- Updates a row on the given table +------------------------------------------------------------------------------ +local updateRow = function( tableName, recordData ) + -- format column values into SQL-safe strings + -- then concatenate them together + local updateStr = '' + for i,v in pairs(recordData) do + if i ~= 'id' then + local colName = i + local colValue = v + if type(v) == 'string' then + colValue = toSqlString(v) + end + updateStr = updateStr .. colName .. " = " .. colValue .. "," + end + end + + -- remove the trailing comma + updateStr = string.sub( updateStr, 1, #updateStr-1 ) + + local sql = "UPDATE " .. tableName .. " SET " .. updateStr .. " WHERE id = " .. recordData.id + db:exec( sql ) +end +M.updateRow = updateRow + +------------------------------------------------------------------------------ +-- If a matching id already exists in the database, do an update +-- otherwise do an insert +------------------------------------------------------------------------------ +local createOrUpdate = function( tableName, recordData ) + local existingRecord = nil + if recordData.id ~= nil then + existingRecord = M.selectOne( tableName, 'id', recordData.id ) + end + + if existingRecord == nil then + M.insertRow( tableName, recordData ) + else + M.updateRow( tableName, recordData ) + end +end +M.createOrUpdate = createOrUpdate + +------------------------------------------------------------------------------ +-- Updates all rows in the given table +------------------------------------------------------------------------------ +local updateAll = function( tablename, updateSql ) + local str = "UPDATE " .. tablename .. + " SET " .. updateSql + db:exec( str ) +end +M.updateAll = updateAll + +------------------------------------------------------------------------------ +-- Updates one column for one row in a given table +------------------------------------------------------------------------------ +local updateAttribute = function( tablename, filter, columnName, columnValue ) + if type(columnValue) == 'string' then + columnValue = toSqlString( columnValue ) + end + local updateStr = "UPDATE " .. tablename .. + " SET " .. columnName .. " = " .. columnValue .. + " WHERE " .. filter + db:exec( updateStr ) +end +M.updateAttribute = updateAttribute + +------------------------------------------------------------------------------ +-- Updates multiple columns for one row in a given table +------------------------------------------------------------------------------ +local updateAttributes = function( tablename, filter, columns, columnValues ) + local updateStr = '' + local newValue = nil + for i,v in ipairs(columns) do + if type(v) == 'string' then + newValue = toSqlString( columnValues[i] ) + else + newValue = columnValues[i] + end + updateStr = updateStr .. v .. " = " .. newValue + if i < #columns then + updateStr = updateStr .. ", " + end + end + db:exec( + "UPDATE " .. tablename .. " SET " .. + updateStr .. + " WHERE " .. filter + ) +end +M.updateAttributes = updateAttributes + +------------------------------------------------------------------------------ +-- Updates all rows that match the filter in the given table +------------------------------------------------------------------------------ +local updateWhere = function( tablename, updateSql, filter ) + local str = "UPDATE " .. tablename .. + " SET " .. updateSql .. + " WHERE " .. filter + db:exec( str ) +end +M.updateWhere = updateWhere + + +return M + diff --git a/yam/db/sql.lua b/yam/db/sql.lua new file mode 100644 index 0000000..fe919a1 --- /dev/null +++ b/yam/db/sql.lua @@ -0,0 +1,79 @@ +------------------------------------------------------------------------------ +-- Module for SQL generation +-- Based on https://github.com/radamanthus/radlib +------------------------------------------------------------------------------ +local _ = require 'yam.underscore' + +local M = {} +------------------------------------------------------------------------------ +-- Generate the sql for the given field def flags +------------------------------------------------------------------------------ +local sqlForFieldFlags = function(fieldDef) + if fieldDef.flags ~= nil then + return _.join(fieldDef.flags, ' '):upper() + else + return '' + end +end +M.sqlForFieldFlags = sqlForFieldFlags + +------------------------------------------------------------------------------ +-- Generate a CREATE TABLE IF NOT EXISTS statement +-- for the given tablename and tablefield definitions +------------------------------------------------------------------------------ +local generateCreateTable = function(tableName, tableFields) + local result = '' + result = 'CREATE TABLE IF NOT EXISTS ' .. tableName .. '(' + for fieldName,fieldDef in pairs(tableFields) do + result = result .. '"' .. fieldName .. '" ' .. fieldDef.dataType:upper() + result = result .. ' ' .. M.sqlForFieldFlags(fieldDef) + result = result .. ',' + end + result = string.sub( result, 1, result:len()-1 ) + result = result .. ')' + return result +end +M.generateCreateTable = generateCreateTable + +------------------------------------------------------------------------------ +-- Generate a SELECT statement +-- +-- Parameters: +-- tableName +-- columns +-- where +-- order +-- limit +------------------------------------------------------------------------------ +local generateSelect = function(params) + local tableName = '' + if params.tableName == nil or params.tableName == '' then + return '' + else + tableName = params.tableName + end + + local columns = '' + if params.columns == nil or params.columns == '' then + columns = '*' + else + columns = params.columns + end + + local result = '' + result = 'SELECT ' .. columns .. ' FROM ' .. tableName + if params.where ~= nil and params.where ~= '' then + result = result .. ' WHERE ' .. params.where + end + if params.order ~= nil and params.order ~= '' then + result = result .. ' ORDER BY ' .. params.order + end + if params.limit ~= nil and params.limit ~= '' then + result = result .. ' LIMIT ' .. params.limit + end + return result +end +M.generateSelect = generateSelect + + +return M diff --git a/yam/middleclass.lua b/yam/middleclass.lua new file mode 100644 index 0000000..ff2e76a --- /dev/null +++ b/yam/middleclass.lua @@ -0,0 +1,139 @@ +-- middleclass.lua - v2.0 (2011-09) +-- Copyright (c) 2011 Enrique García Cota +-- 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. +-- Based on YaciCode, from Julien Patte and LuaObject, from Sebastien Rocca-Serra + +local _classes = setmetatable({}, {__mode = "k"}) + +local function _setClassDictionariesMetatables(klass) + local dict = klass.__instanceDict + dict.__index = dict + + local super = klass.super + if super then + local superStatic = super.static + setmetatable(dict, super.__instanceDict) + setmetatable(klass.static, { __index = function(_,k) return dict[k] or superStatic[k] end }) + else + setmetatable(klass.static, { __index = function(_,k) return dict[k] end }) + end +end + +local function _setClassMetatable(klass) + setmetatable(klass, { + __tostring = function() return "class " .. klass.name end, + __index = klass.static, + __newindex = klass.__instanceDict, + __call = function(self, ...) return self:new(...) end + }) +end + +local function _createClass(name, super) + local klass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict={} } + klass.subclasses = setmetatable({}, {__mode = "k"}) + + _setClassDictionariesMetatables(klass) + _setClassMetatable(klass) + _classes[klass] = true + + return klass +end + +local function _createLookupMetamethod(klass, name) + return function(...) + local method = klass.super[name] + assert( type(method)=='function', tostring(klass) .. " doesn't implement metamethod '" .. name .. "'" ) + return method(...) + end +end + +local function _setClassMetamethods(klass) + for _,m in ipairs(klass.__metamethods) do + klass[m]= _createLookupMetamethod(klass, m) + end +end + +local function _setDefaultInitializeMethod(klass, super) + klass.initialize = function(instance, ...) + return super.initialize(instance, ...) + end +end + +local function _includeMixin(klass, mixin) + assert(type(mixin)=='table', "mixin must be a table") + for name,method in pairs(mixin) do + if name ~= "included" and name ~= "static" then klass[name] = method end + end + if mixin.static then + for name,method in pairs(mixin.static) do + klass.static[name] = method + end + end + if type(mixin.included)=="function" then mixin:included(klass) end + klass.__mixins[mixin] = true +end + +Object = _createClass("Object", nil) + +Object.static.__metamethods = { '__add', '__call', '__concat', '__div', '__le', '__lt', + '__mod', '__mul', '__pow', '__sub', '__tostring', '__unm' } + +function Object.static:allocate() + assert(_classes[self], "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'") + return setmetatable({ class = self }, self.__instanceDict) +end + +function Object.static:new(...) + local instance = self:allocate() + instance:initialize(...) + return instance +end + +function Object.static:subclass(name) + assert(_classes[self], "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'") + assert(type(name) == "string", "You must provide a name(string) for your class") + + local subclass = _createClass(name, self) + _setClassMetamethods(subclass) + _setDefaultInitializeMethod(subclass, self) + self.subclasses[subclass] = true + self:subclassed(subclass) + + return subclass +end + +function Object.static:subclassed(other) end + +function Object.static:include( ... ) + assert(_classes[self], "Make sure you that you are using 'Class:include' instead of 'Class.include'") + for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end + return self +end + +function Object:initialize() end + +function Object:__tostring() return "instance of " .. tostring(self.class) end + +function class(name, super, ...) + super = super or Object + return super:subclass(name, ...) +end + +function instanceOf(aClass, obj) + if not _classes[aClass] or type(obj) ~= 'table' or not _classes[obj.class] then return false end + if obj.class == aClass then return true end + return subclassOf(aClass, obj.class) +end + +function subclassOf(other, aClass) + if not _classes[aClass] or not _classes[other] or aClass.super == nil then return false end + return aClass.super == other or subclassOf(other, aClass.super) +end + +function includes(mixin, aClass) + if not _classes[aClass] then return false end + if aClass.__mixins[mixin] then return true end + return includes(mixin, aClass.super) +end diff --git a/yam/mvc/Model.lua b/yam/mvc/Model.lua new file mode 100644 index 0000000..e44684e --- /dev/null +++ b/yam/mvc/Model.lua @@ -0,0 +1,167 @@ +-- Base class for SQLite aware model classes +-- Based on https://github.com/radamanthus/radlib +-- +-- USAGE +-- +-- Create a Lua file for your module. The file should look like this: +-- +-- require 'Model' +-- Product = Model:subclass('Model') +-- Product.tableName = 'products' +-- Product.tableFields = { +-- id = {type = 'integer', flags = {'primary_key', 'autoincrement', 'not_null'} }, +-- name = {type = 'string', flags = {'not_null'} } +-- } +-- +-- If the table does not yet exist, you can create it in your app initialization with this call: +-- +-- orm.initialize() +-- Product.createTable() +-- +-- Sample API calls +-- +-- local products = Product.findAll +-- +-- p = Product.new{id = 1, name = 'test', description = ''} +-- p.save +-- +-- p.updateAttribute('name', 'newName') +-- p.updateAttributes{name = 'newName', description = 'newDescription'} +-- +-- p = Product.find(1) +-- test_products = Product.where("name = 'test'") +-- +-- numberOfProducts = Product.count() +-- + +require 'yam.middleclass' +local orm = require 'yam.db.orm' +local sql = require 'yam.db.sql' + +Model = class('Model') + +------------------------------------------------------------------------------ +-- CLASS (STATIC) METHODS - START +------------------------------------------------------------------------------ +function Model:initialize(newRecord) + for k,v in pairs(newRecord) do + self[k] = v + end +end + +------------------------------------------------------------------------------ +-- Returns the number of rows in the table +------------------------------------------------------------------------------ +function Model.static:count() + return orm.getTableRowCount(self.tableName) +end + +------------------------------------------------------------------------------ +-- Creates the table +-- TODO: If options.recreate = true, it drops the table if it already exists +------------------------------------------------------------------------------ +function Model.static:createTable(options) + local createSql = sql.generateCreateTable(self.tableName, self.tableFields) + db:exec( createSql ) +end + +------------------------------------------------------------------------------ +-- Returns the record matching the given id. Returns nil if no match is found. +-- +-- NOTE: Until I figure out how to determine the caller's class, +-- I'll have to resort to this ugliness of using the klass parameter +------------------------------------------------------------------------------ +function Model.static:find(klass, id) + local record = orm.selectOne(klass.tableName, 'id', id) + if not( record == nil ) then + result = klass:new(record) + end + return result +end + +------------------------------------------------------------------------------ +-- Returns all rows in the table that match the given filter +------------------------------------------------------------------------------ +function Model.static:findAll( klass, params ) + local result = nil + if params == nil then + params = {} + end + if params.where == nil then + result = orm.selectAll( klass.tableName, params ) + else + result = orm.selectWhere( klass.tableName, params ) + end + return result +end + +------------------------------------------------------------------------------ +-- Updates all rows in the table that match the given filter +------------------------------------------------------------------------------ +function Model.static:updateAll( klass, updateSql, filter ) + if filter == nil then + orm.updateAll( klass.tableName, updateSql ) + else + orm.updateWhere( klass.tableName, updateSql, filter ) + end +end + +------------------------------------------------------------------------------ +-- CLASS (STATIC) METHODS - END +------------------------------------------------------------------------------ + + +------------------------------------------------------------------------------ +-- INSTANCE METHODS - START +------------------------------------------------------------------------------ + +------------------------------------------------------------------------------ +-- Reloads the record values from the database +------------------------------------------------------------------------------ +function Model:reload() + local updatedRecord = orm.selectOne( self.class.tableName, 'id', self.id ) + if updatedRecord ~= nil then + for k,v in pairs(updatedRecord) do + self[k] = v + end + end +end + +------------------------------------------------------------------------------ +-- Saves the content of the object to the database. +-- If a matching record already exists in the database, an UPDATE is done. +-- Otherwise an INSERT is done. +------------------------------------------------------------------------------ +function Model:save() + local updateTable = {} + for k in pairs(self.class.tableFields) do + updateTable[k] = self[k] + end + orm.createOrUpdate( self.class.tableName, updateTable ) +end + +------------------------------------------------------------------------------ +-- Updates one column value +------------------------------------------------------------------------------ +function Model:updateAttribute( columnName, columnValue ) + local filter = "id = " .. self.id + orm.updateAttribute( self.class.tableName, filter, columnName, columnValue ) +end + +------------------------------------------------------------------------------ +-- Updates an array of columns +------------------------------------------------------------------------------ +function Model:updateAttributes( updateTable ) + local filter = "id = " .. self.id + local columns = {} + local columnValues = {} + for k,v in pairs(updateTable) do + table.insert( columns, k ) + table.insert( columnValues, v ) + end + orm.updateAttributes( self.class.tableName, filter, columns, columnValues ) +end + +------------------------------------------------------------------------------ +-- INSTANCE METHODS - END +------------------------------------------------------------------------------ diff --git a/yam/mvc/ViewController.lua b/yam/mvc/ViewController.lua new file mode 100644 index 0000000..e7825d7 --- /dev/null +++ b/yam/mvc/ViewController.lua @@ -0,0 +1,46 @@ +require 'yam.middleclass' +local storyboard = require "storyboard" + +ViewController = class('ViewController') + +function ViewController:init(event) end +function ViewController:show(event) end +function ViewController:resize(event) end +function ViewController:hide(event) end +function ViewController:dispose(event) end + +function ViewController:initialize() + local scene = storyboard.newScene() + local this = self + + function scene:createScene( event ) + this.view = scene.view + this:init(event) + end + + function scene:willEnterScene( event ) + this:resize(event) + end + function scene:enterScene( event ) + this:show(event) + end + function scene:exitScene( event ) + this:hide(event); + end + function scene:destroyScene( event ) + this:dispose(event) + end + function onOrientationChange( event ) + this:resize(event); + end + + scene:addEventListener( "createScene", scene ) + scene:addEventListener( "willEnterScene", scene ) + scene:addEventListener( "enterScene", scene ) + scene:addEventListener( "exitScene", scene ) + scene:addEventListener( "destroyScene", scene ) + Runtime:addEventListener( "orientation", onOrientationChange ) + + self.scene = scene + self.params = storyboard.params +end diff --git a/yam/underscore.lua b/yam/underscore.lua new file mode 100644 index 0000000..7bcf7f9 --- /dev/null +++ b/yam/underscore.lua @@ -0,0 +1,934 @@ +local table, ipairs, pairs, math, string = table, ipairs, pairs, math, string + +local _ = {} +local chainable_mt = {} + +_.identity = function(value) return value end + +function _.reverse(list) + if _.isString(list) then + return _(list).chain():split():reverse():join():value() + else + local length = _.size(list) + for i = 1, length / 2, 1 do + list[i], list[length-i+1] = list[length-i+1], list[i] + end + return list + end +end + +function _.pop(list) + return table.remove(list, #list) +end + +function _.push(list, ...) + local values = {...} + _.each(values, function(v) + table.insert(list, v) + end) + return list +end + +function _.shift(list) + return table.remove(list, 1) +end + +function _.unshift(list, ...) + local values = {...} + _.each(_.reverse(values), function(v) + table.insert(list, 1, v) + end) + + return list +end + +function _.sort(list, func) + func = func or function(a,b) + return tostring(a) < tostring(b) + end + + table.sort(list, func) + return list +end + +function _.join(list, separator) + separator = separator or "" + return table.concat(list,separator) +end + +function _.slice(list, start, stop) + local array = {} + stop = stop or #list + + for index = start, stop, 1 do + table.insert(array, list[index]) + end + + return array +end + +function _.concat(...) + local values = _.flatten({...}, true) + local cloned = {} + + _.each(values, function(v) + table.insert(cloned, v) + end) + + return cloned +end + +function _.each(list, func) + local pairing = pairs + if _.isArray(list) then pairing = ipairs end + + for index, value in pairing(list) do + func(value, index, list) + end +end + +function _.map(list, func) + local new_list = {} + _.each(list, function(value, key, original_list) + table.insert(new_list, func(value, key, original_list)) + end) + + return new_list +end + +function _.reduce(list, func, memo) + local init = memo == nil + _.each(list, function(value, key, object) + if init then + memo = value + init = false + else + memo = func(memo, value, key, object) + end + end) + + if init then + error("Reduce of empty array with no initial value") + end + + return memo +end + +function _.reduceRight(list, func, memo) + local init = memo == nil + _.each(_.reverse(list), function(value, key, object) + if init then + memo = value + init = false + else + memo = func(memo, value, key, object) + end + end) + + if init then + error("Reduce of empty array with no initial value") + end + + return memo +end + +function _.find(list, func) + if func == nil then return nil end + + local result = nil + _.any(list, function(value, key, object) + if func(value, key, object) then + result = value + return true + end + end) + + return result +end + +function _.select(list, func) + local found = {} + _.each(list, function(value, key, object) + if func(value, key, object) then + table.insert(found, value) + end + end) + + return found +end + +function _.reject(list, func) + local found = {} + _.each(list, function(value, key, object) + if not func(value, key, object) then + table.insert(found, value) + end + end) + + return found +end + +function _.all(list, func) + if _.isEmpty(list) then return false end + + func = func or _.identity + + local found = true + _.each(list, function(value, index, object) + if found and not func(value, index, object) then + found = false + end + end) + + return found +end + +function _.any(list, func) + if _.isEmpty(list) then return false end + + func = func or _.identity + + local found = false + _.each(list, function(value, index, object) + if not found and func(value, index, object) then + found = true + end + end) + + return found +end + +function _.include(list, v) + return _.any(list, function(value) + return v == value + end) +end + +function _.pluck(list, key) + local found = {} + _.each(list, function(value) + table.insert(found, value[key]) + end) + + return found +end + +function _.where(list, properties) + local found = {} + return _.select(list, function(value) + return _.all(properties, function(v, k) + return value[k] == v + end) + end) +end + +function _.max(list, func) + if _.isEmpty(list) then + return -math.huge + elseif _.isFunction(func) then + local max = {computed=-math.huge} + _.each(list, function(value, key, object) + local computed = func(value, key, object) + if computed >= max.computed then + max = {computed=computed, value=value} + end + end) + return max.value + else + return math.max(unpack(list)) + end +end + +function _.min(list, func) + if _.isEmpty(list) then + return math.huge + elseif _.isFunction(func) then + local min = {computed=math.huge} + _.each(list, function(value, key, object) + local computed = func(value, key, object) + if computed < min.computed then + min = {computed=computed, value=value} + end + end) + return min.value + else + return math.min(unpack(list)) + end +end + +function _.invoke(list, func, ...) + local invoke_func, args = func, {...} + + if _.isString(func) then + invoke_func = function(value) + return value[func](value, unpack(args)) + end + end + + return _.collect(list, function(value) + return invoke_func(value, unpack(args)) + end) +end + +function _.sortBy(list, func) + func = func or _.identity + local sorted_func = function(a,b) + if a == nil then return false end + if b == nil then return true end + return func(a) < func(b) + end + + if _.isString(func) then + sorted_func = function(a,b) + return a[func](a) < b[func](b) + end + end + + table.sort(list, sorted_func) + return list +end + +function _.groupBy(list, func) + local group_func, result = func, {} + + if _.isString(func) then + group_func = function(v) + return v[func](v) + end + end + + _.each(list, function(value, key, object) + local key = group_func(value, key, object) + result[key] = result[key] or {} + table.insert(result[key], value) + end) + + return result +end + +function _.countBy(list, func) + local count_func, result = func, {} + + if _.isString(func) then + count_func = function(v) + return v[func](v) + end + end + + _.each(list, function(value, key, object) + local key = count_func(value, key, object) + result[key] = result[key] or 0 + result[key] = result[key] + 1 + end) + + return result +end + +function _.shuffle(list) + local rand, index, shuffled = 0, 1, {} + _.each(list, function(value) + rand = math.random(1, index) + index = index + 1 + shuffled[index - 1] = shuffled[rand] + shuffled[rand] = value + end) + + return shuffled +end + +function _.toArray(list, ...) + if not list then return {} end + if not _.isObject(list) then list = {list, ...} end + + local cloned = {} + _.each(list, function(value) + table.insert(cloned, value) + end) + + return cloned +end + +function _.size(list, ...) + local args = {...} + + if _.isArray(list) then + return #list + elseif _.isObject(list) then + local length = 0 + _.each(list, function() length = length + 1 end) + return length + elseif _.isString(list) then + return string.len(list) + elseif not _.isEmpty(args) then + return _.size(args) + 1 + end + + return 0 +end + +function _.memoize(func) + local list = {} + + return function(...) + if not list[...] then + list[...] = func(...) + end + + return list[...] + end +end + +function _.once(func) + local called = false + return function(...) + if not called then + called = true + return func(...) + end + end +end + +function _.after(times, func) + if times <= 0 then return func() end + + return function(...) + times = times - 1 + if times < 1 then + return func(...) + end + end +end + +function _.bind(func, object) + return function(...) + return func(object, ...) + end +end + +function _.wrap(func, wrapper) + return function(...) + return wrapper(func, ...) + end +end + +function _.compose(...) + local funcs = {...} + + return function(...) + local args = {...} + + _.each(_.reverse(funcs), function(func) + args = {func(unpack(args))} + end) + + return args[1] + end +end + +function _.range(...) + local args = {...} + local start, stop, step = unpack(args) + + if #args <= 1 then + stop = start or 0 + start = 0 + end + step = args[3] or 1 + + local length, index, array = + math.max(math.ceil((stop - start) / step), 0), + 0, {} + + while index < length do + table.insert(array, start) + index = index + 1 + start = start + step + end + + return array +end + +function _.first(list, count) + if not list then return nil end + count = count or 1 + + return _.slice(list, 1, count) +end + +function _.rest(list, start) + start = start or 2 + + return _.slice(list, start, #list) +end + +function _.initial(list, stop) + stop = stop or (#list - 1) + + return _.slice(list, 1, stop) +end + +function _.last(list, count) + if not list then return nil end + + if not count then + return list[#list] + else + local start, stop, array = #list - count + 1, #list, {} + return _.slice(list, start, stop) + end +end + +function _.compact(list) + return _.filter(list, function(v) + return not not v + end) +end + +function _.flatten(list, shallow, output) + output = output or {} + + _.each(list, function(value) + if _.isArray(value) then + if shallow then + _.each(value, function(v) table.insert(output, v) end) + else + _.flatten(value, false, output) + end + else + table.insert(output, value) + end + end) + + return output +end + +function _.without(list, ...) + local args = {...} + + return _.difference(list, args) +end + +function _.uniq(list, sorted, iterator) + local initial, results, seen = list, {}, {} + if iterator then + initial = _.map(list, iterator) + end + + _.each(initial, function(value, index) + if (sorted and (index==1 or seen[#seen]~=value)) or (not _.contains(seen, value)) then + table.insert(seen, value) + table.insert(results, list[index]) + end + end) + + return results +end + +function _.indexOf(list, value, start) + if not list then return 0 end + start = start or 1 + + for index = start, #list, 1 do + if value == list[index] then + return index + end + end + + return 0 +end + +function _.intersection(a, ...) + local b = {...} + return _.filter(_.uniq(a), function(item) + return _.every(b, function(other) + return _.indexOf(other, item) >= 1 + end) + end) +end + +function _.union(...) + return _.uniq(_.flatten({...}, true)) +end + +function _.difference(a, ...) + local b = _.flatten({...}, true) + return _.filter(a, function(value) + return not _.contains(b, value) + end) +end + +function _.zip(...) + local args = {...} + local length = _.max(_.map(args, function(a) return #a end)) + local results = {} + + for i=1, length, 1 do + table.insert(results, _.pluck(args, i)) + end + + return results +end + +function _.object(list, values) + if not list then return {} end + + local result = {} + _.each(list, function(value, index) + if values then + result[value] = values[index] + else + result[value[1]] = value[2] + end + end) + + return result +end + +function _.lastIndexOf(list, value, start) + if not list then return 0 end + start = start or #list + + for index = start, 1, -1 do + if value == list[index] then + return index + end + end + + return 0 +end + +function _.keys(list) + if not _.isObject(list) then error("Not an object") end + return _.map(list, function(_, key) + return key + end) +end + +function _.values(list) + if _.isArray(list) then return list end + return _.map(list, function(value) + return value + end) +end + +function _.pairs(list) + return _.map(list, function(value, key) + return {key, value} + end) +end + +function _.invert(list) + local array = {} + + _.each(list, function(value, key) + array[value] = key + end) + + return array +end + +function _.functions(list) + local method_names = {} + + _.each(list, function(value, key) + if _.isFunction(value) then + table.insert(method_names, key) + end + end) + + return method_names +end + +function _.extend(list, ...) + local lists = {...} + _.each(lists, function(source) + _.each(source, function(value, key) + list[key] = source[key] + end) + end) + + return list +end + +function _.pick(list, ...) + local keys = _.flatten({...}) + + local array = {} + _.each(keys, function(key) + if list[key] then + array[key] = list[key] + end + end) + + return array +end + +function _.omit(list, ...) + local keys = _.flatten({...}) + + local array = {} + _.each(list, function(value,key) + if not _.contains(keys, key) then + array[key] = list[key] + end + end) + + return array +end + +function _.defaults(list, ...) + local keys = {...} + + _.each(keys, function(source) + _.each(source, function(value, key) + if not list[key] then + list[key] = value + end + end) + end) + + return list +end + +function _.clone(list) + if not _.isObject(list) then return list end + + if _.isArray(list) then + return _.slice(list, 1, #list) + else + return _.extend({}, list) + end +end + +function _.isNaN(value) + return _.isNumber(value) and value ~= value +end + +function _.isEmpty(value) + if not value then + return true + elseif _.isArray(value) or _.isObject(value) then + return next(value) == nil + elseif _.isString(value) then + return string.len(value) == 0 + else + return false + end +end + +function _.isObject(value) + return type(value) == "function" or type(value) == "table" +end + +function _.isArray(value) + return _.isObject(value) and value[1] +end + +function _.isString(value) + return type(value) == "string" +end + +function _.isNumber(value) + return type(value) == "number" +end + +function _.isFunction(value) + return type(value) == "function" +end + +function _.isFinite(value) + return _.isNumber(value) and -math.huge < value and value < math.huge +end + +function _.isBoolean(value) + return type(value) == "boolean" +end + +function _.isNil(value) + return value == nil +end + +function _.tap(value, func) + func(value) + return value +end + +function splitIterator(value, pattern, start) + if pattern then + return string.find(value, pattern, start) + else + if start > string.len(value) then + return nil + else + return start+1, start + end + end +end + + +function _.split(value, pattern) + if not _.isString(value) then return {} end + local values = {} + local start = 1 + local start_pattern, end_pattern = splitIterator(value, pattern, start) + + while start_pattern do + table.insert( + values, + string.sub(value, start, start_pattern - 1) + ) + start = end_pattern + 1 + start_pattern, end_pattern = splitIterator(value, pattern, start) + end + + if start <= string.len(value) then + table.insert(values, string.sub(value, start)) + end + + return values +end + +function _.chain(value) + return _(value).chain() +end + +local id_counter = -1 +function _.uniqueId(prefix) + id_counter = id_counter + 1 + if prefix then + return prefix .. id_counter + else + return id_counter + end +end + +function _.times(n, func) + for i=0, (n-1), 1 do + func(i) + end +end + +local result = function(self, obj) + if _.isObject(self) and self._chain then + return _(obj).chain() + else + return obj + end +end + + +function _.mixin(obj) + _.each(_.functions(obj), function(name) + local func = obj[name] + _[name] = func + + chainable_mt[name] = function(target, ...) + local r = func(target._wrapped, ...) + if _.include({'pop','shift'}, name) then + return result(target, target._wrapped) + else + return result(target, r) + end + end + end) +end + +local entityMap = { + escape={ + ['&']='&', + ['<']='<', + ['>']='>', + ['"']='"', + ["'"]=''', + ['/']='/' + } +} +entityMap.unescape = _.invert(entityMap.escape) + +function _.escape(value) + value = value or '' + return value:gsub("[" .. _(entityMap.escape).chain():keys():join():value() .. "]", function(s) + return entityMap['escape'][s] + end) +end + +function _.unescape(value) + value = value or '' + _.each(entityMap.unescape, function(escaped, key) + value = value:gsub(key, function(s) + return escaped + end) + end) + + return value +end + +function _.result(obj, prop) + if not obj then return nil end + local value = obj[prop] + + if _.isFunction(value) then + return value(obj) + else + return value + end +end + +function _.print_r (t, name, indent) + local tableList = {} + function table_r (t, name, indent, full) + local serial=string.len(full) == 0 and name + or type(name)~="number" and '["'..tostring(name)..'"]' or '['..name..']' + io.write(indent,serial,' = ') + if type(t) == "table" then + if tableList[t] ~= nil then io.write('{}; -- ',tableList[t],' (self reference)\n') + else + tableList[t]=full..serial + if next(t) then -- Table not empty + io.write('{\n') + for key,value in pairs(t) do table_r(value,key,indent..'\t',full..serial) end + io.write(indent,'};\n') + else io.write('{};\n') end + end + else io.write(type(t)~="number" and type(t)~="boolean" and '"'..tostring(t)..'"' + or tostring(t),';\n') end + end + table_r(t,name or '__unnamed__',indent or '','') +end + +_.collect = _.map +_.inject = _.reduce +_.foldl = _.reduce +_.foldr = _.reduceRight +_.detect = _.find +_.filter = _.select +_.every = _.all +_.same = _.any +_.contains = _.include +_.head = _.first +_.take = _.first +_.drop = _.rest +_.tail = _.rest +_.methods = _.functions + +_.mixin(_) + +setmetatable(_,{ + __call = function(target, ...) + local wrapped = ... + if _.isObject(wrapped) and wrapped._wrapped then return wrapped end + + local instance = setmetatable({}, {__index=chainable_mt}) + instance.chain = function() + instance._chain = true + return instance + end + instance.value = function() return instance._wrapped end + + instance._wrapped = wrapped + return instance + end +}) + +return _ + diff --git a/yam/utils/I18n.lua b/yam/utils/I18n.lua new file mode 100644 index 0000000..1198d38 --- /dev/null +++ b/yam/utils/I18n.lua @@ -0,0 +1,63 @@ +------------------------------------------------- +-- +-- Based on: class_I18N.lua by Brill Pappin, Sixgreen Labs Inc. (http://developer.anscamobile.com/code/i18n-strings) +-- +-- Provides locale specific strings, with more specific values overriding more general values. +-- +------------------------------------------------- +require 'yam.middleclass' +local json = require("json") + +I18n = class('I18n') + +function I18n:initialize(resource) -- The constructor + + self.resource = resource or "data/locales/strings" + self.language = system.getPreference("locale", "language") or system.getPreference("ui", "language") + self.country = system.getPreference( "locale", "country" ) + + + self.files = { + self.resource..".i18n", + self.resource.."_"..self.language..".i18n", + self.resource.."_"..self.language.."_"..self.country..".i18n" + } + + -- Files are processed in order so that finer grained + -- message override ones that are more general. + local strmap = {} + for i = 1, #self.files do + local path = system.pathForFile( self.files[i], system.ResourcesDirectory ) + + if path then + local file = io.open( path, "r" ) + if file then -- nil if no file found + local contents = file:read( "*a" ) + io.close( file ) + + local resmap = json.decode(contents) + for key, value in pairs(resmap) do + strmap[key] = value; + end + + end + end + end + self.strings = strmap +end + +function I18n:getString(key) + if self.strings[key] then + return self.strings[key] + else + return "{{"..key.."}}" + end +end + +function I18n:__tostring() + return "I18N [resource="..self.resource..", language="..self.language..", country="..self.country.."]" +end + +function I18n:__call(key) + return self:getString(key) +end \ No newline at end of file diff --git a/yam/utils/bit.lua b/yam/utils/bit.lua new file mode 100755 index 0000000..94e60d7 --- /dev/null +++ b/yam/utils/bit.lua @@ -0,0 +1,260 @@ +--[[--------------- +LuaBit v0.4 +------------------- +a bitwise operation lib for lua. + +http://luaforge.net/projects/bit/ + +How to use: +------------------- + bit.bnot(n) -- bitwise not (~n) + bit.band(m, n) -- bitwise and (m & n) + bit.bor(m, n) -- bitwise or (m | n) + bit.bxor(m, n) -- bitwise xor (m ^ n) + bit.brshift(n, bits) -- right shift (n >> bits) + bit.blshift(n, bits) -- left shift (n << bits) + bit.blogic_rshift(n, bits) -- logic right shift(zero fill >>>) + +Please note that bit.brshift and bit.blshift only support number within +32 bits. + +2 utility functions are provided too: + bit.tobits(n) -- convert n into a bit table(which is a 1/0 sequence) + -- high bits first + bit.tonumb(bit_tbl) -- convert a bit table into a number +------------------- + +Under the MIT license. + +copyright(c) 2006~2007 hanzhao (abrash_han@hotmail.com) +--]]--------------- + +do + +------------------------ +-- bit lib implementions + +local function check_int(n) + -- checking not float + if(n - math.floor(n) > 0) then + error("trying to use bitwise operation on non-integer!") + end +end + +local function to_bits(n) + check_int(n) + if(n < 0) then + -- negative + return to_bits(bit.bnot(math.abs(n)) + 1) + end + -- to bits table + local tbl = {} + local cnt = 1 + while (n > 0) do + local last = math.mod(n,2) + if(last == 1) then + tbl[cnt] = 1 + else + tbl[cnt] = 0 + end + n = (n-last)/2 + cnt = cnt + 1 + end + + return tbl +end + +local function tbl_to_number(tbl) + local n = table.getn(tbl) + + local rslt = 0 + local power = 1 + for i = 1, n do + rslt = rslt + tbl[i]*power + power = power*2 + end + + return rslt +end + +local function expand(tbl_m, tbl_n) + local big = {} + local small = {} + if(table.getn(tbl_m) > table.getn(tbl_n)) then + big = tbl_m + small = tbl_n + else + big = tbl_n + small = tbl_m + end + -- expand small + for i = table.getn(small) + 1, table.getn(big) do + small[i] = 0 + end + +end + +local function bit_or(m, n) + local tbl_m = to_bits(m) + local tbl_n = to_bits(n) + expand(tbl_m, tbl_n) + + local tbl = {} + local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n)) + for i = 1, rslt do + if(tbl_m[i]== 0 and tbl_n[i] == 0) then + tbl[i] = 0 + else + tbl[i] = 1 + end + end + + return tbl_to_number(tbl) +end + +local function bit_and(m, n) + local tbl_m = to_bits(m) + local tbl_n = to_bits(n) + expand(tbl_m, tbl_n) + + local tbl = {} + local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n)) + for i = 1, rslt do + if(tbl_m[i]== 0 or tbl_n[i] == 0) then + tbl[i] = 0 + else + tbl[i] = 1 + end + end + + return tbl_to_number(tbl) +end + +local function bit_not(n) + + local tbl = to_bits(n) + local size = math.max(table.getn(tbl), 32) + for i = 1, size do + if(tbl[i] == 1) then + tbl[i] = 0 + else + tbl[i] = 1 + end + end + return tbl_to_number(tbl) +end + +local function bit_xor(m, n) + local tbl_m = to_bits(m) + local tbl_n = to_bits(n) + expand(tbl_m, tbl_n) + + local tbl = {} + local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n)) + for i = 1, rslt do + if(tbl_m[i] ~= tbl_n[i]) then + tbl[i] = 1 + else + tbl[i] = 0 + end + end + + --table.foreach(tbl, print) + + return tbl_to_number(tbl) +end + +local function bit_rshift(n, bits) + check_int(n) + + local high_bit = 0 + if(n < 0) then + -- negative + n = bit_not(math.abs(n)) + 1 + high_bit = 2147483648 -- 0x80000000 + end + + for i=1, bits do + n = n/2 + n = bit_or(math.floor(n), high_bit) + end + return math.floor(n) +end + +-- logic rightshift assures zero filling shift +local function bit_logic_rshift(n, bits) + check_int(n) + if(n < 0) then + -- negative + n = bit_not(math.abs(n)) + 1 + end + for i=1, bits do + n = n/2 + end + return math.floor(n) +end + +local function bit_lshift(n, bits) + check_int(n) + + if(n < 0) then + -- negative + n = bit_not(math.abs(n)) + 1 + end + + for i=1, bits do + n = n*2 + end + return bit_and(n, 4294967295) -- 0xFFFFFFFF +end + +local function bit_xor2(m, n) + local rhs = bit_or(bit_not(m), bit_not(n)) + local lhs = bit_or(m, n) + local rslt = bit_and(lhs, rhs) + return rslt +end + +-------------------- +-- bit lib interface + +bit = { + -- bit operations + bnot = bit_not, + band = bit_and, + bor = bit_or, + bxor = bit_xor, + brshift = bit_rshift, + blshift = bit_lshift, + bxor2 = bit_xor2, + blogic_rshift = bit_logic_rshift, + + -- utility func + tobits = to_bits, + tonumb = tbl_to_number, +} + +end + +--[[ +for i = 1, 100 do + for j = 1, 100 do + if(bit.bxor(i, j) ~= bit.bxor2(i, j)) then + error("bit.xor failed.") + end + end +end +--]] + + + + + + + + + + + + + diff --git a/yam/utils/inspect.lua b/yam/utils/inspect.lua new file mode 100644 index 0000000..5d89749 --- /dev/null +++ b/yam/utils/inspect.lua @@ -0,0 +1,243 @@ +----------------------------------------------------------------------------------------------------------------------- +-- inspect.lua - v1.2.0 (2012-10) +-- Enrique García Cota - enrique.garcia.cota [AT] gmail [DOT] com +-- human-readable representations of tables. +-- inspired by http://lua-users.org/wiki/TableSerialization +----------------------------------------------------------------------------------------------------------------------- + +local inspect ={} +inspect.__VERSION = '1.2.0' + +-- Apostrophizes the string if it has quotes, but not aphostrophes +-- Otherwise, it returns a regular quoted string +local function smartQuote(str) + if string.match( string.gsub(str,"[^'\"]",""), '^"+$' ) then + return "'" .. str .. "'" + end + return string.format("%q", str ) +end + +local controlCharsTranslation = { + ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", + ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\\"] = "\\\\" +} + +local function unescapeChar(c) return controlCharsTranslation[c] end + +local function unescape(str) + local result, _ = string.gsub( str, "(%c)", unescapeChar ) + return result +end + +local function isIdentifier(str) + return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" ) +end + +local function isArrayKey(k, length) + return type(k)=='number' and 1 <= k and k <= length +end + +local function isDictionaryKey(k, length) + return not isArrayKey(k, length) +end + +local sortOrdersByType = { + ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, + ['function'] = 5, ['userdata'] = 6, ['thread'] = 7 +} + +local function sortKeys(a,b) + local ta, tb = type(a), type(b) + if ta ~= tb then return sortOrdersByType[ta] < sortOrdersByType[tb] end + if ta == 'string' or ta == 'number' then return a < b end + return false +end + +local function getDictionaryKeys(t) + local length = #t + local keys = {} + for k,_ in pairs(t) do + if isDictionaryKey(k, length) then table.insert(keys,k) end + end + table.sort(keys, sortKeys) + return keys +end + +local function getToStringResultSafely(t, mt) + local __tostring = type(mt) == 'table' and mt.__tostring + local string, status + if type(__tostring) == 'function' then + status, string = pcall(__tostring, t) + string = status and string or 'error: ' .. tostring(string) + end + return string +end + +local Inspector = {} + +function Inspector:new(t, depth) + local inspector = { + buffer = {}, + depth = depth, + level = 0, + maxIds = { + ['function'] = 0, + ['userdata'] = 0, + ['thread'] = 0, + ['table'] = 0 + }, + ids = { + ['function'] = setmetatable({}, {__mode = "kv"}), + ['userdata'] = setmetatable({}, {__mode = "kv"}), + ['thread'] = setmetatable({}, {__mode = "kv"}), + ['table'] = setmetatable({}, {__mode = "kv"}) + }, + tableAppearances = setmetatable({}, {__mode = "k"}) + } + + setmetatable(inspector, {__index = Inspector}) + + inspector:countTableAppearances(t) + + return inspector:putValue(t) +end + +function Inspector:countTableAppearances(t) + if type(t) == 'table' then + if not self.tableAppearances[t] then + self.tableAppearances[t] = 1 + for k,v in pairs(t) do + self:countTableAppearances(k) + self:countTableAppearances(v) + end + else + self.tableAppearances[t] = self.tableAppearances[t] + 1 + end + self:countTableAppearances(getmetatable(t)) + end +end + +function Inspector:tabify() + self:puts("\n", string.rep(" ", self.level)) + return self +end + +function Inspector:up() + self.level = self.level - 1 +end + +function Inspector:down() + self.level = self.level + 1 +end + +function Inspector:puts(...) + local args = {...} + local len = #self.buffer + for i=1, #args do + len = len + 1 + self.buffer[len] = tostring(args[i]) + end + return self +end + +function Inspector:commaControl(comma) + if comma then self:puts(',') end + return true +end + +function Inspector:putTable(t) + if self:alreadyVisited(t) then + self:puts('') + elseif self.depth and self.level >= self.depth then + self:puts('{...}') + else + if self.tableAppearances[t] > 1 then + self:puts('<',self:getId(t),'>') + end + self:puts('{') + self:down() + + local length = #t + local mt = getmetatable(t) + + local string = getToStringResultSafely(t, mt) + if type(string) == 'string' and #string > 0 then + self:puts(' -- ', unescape(string)) + if length >= 1 then self:tabify() end -- tabify the array values + end + + local comma = false + for i=1, length do + comma = self:commaControl(comma) + self:puts(' '):putValue(t[i]) + end + + local dictKeys = getDictionaryKeys(t) + + for _,k in ipairs(dictKeys) do + comma = self:commaControl(comma) + self:tabify():putKey(k):puts(' = '):putValue(t[k]) + end + + if mt then + comma = self:commaControl(comma) + self:tabify():puts(' = '):putValue(mt) + end + + self:up() + + if #dictKeys > 0 or mt then -- dictionary table. Justify closing } + self:tabify() + elseif length > 0 then -- array tables have one extra space before closing } + self:puts(' ') + end + self:puts('}') + end + return self +end + +function Inspector:alreadyVisited(v) + return self.ids[type(v)][v] ~= nil +end + +function Inspector:getId(v) + local tv = type(v) + local id = self.ids[tv][v] + if not id then + id = self.maxIds[tv] + 1 + self.maxIds[tv] = id + self.ids[tv][v] = id + end + return id +end + +function Inspector:putValue(v) + local tv = type(v) + + if tv == 'string' then + self:puts(smartQuote(unescape(v))) + elseif tv == 'number' or tv == 'boolean' or tv == 'nil' then + self:puts(tostring(v)) + elseif tv == 'table' then + self:putTable(v) + else + self:puts('<',tv,' ',self:getId(v),'>') + end + return self +end + +function Inspector:putKey(k) + if isIdentifier(k) then return self:puts(k) end + return self:puts( "[" ):putValue(k):puts("]") +end + +function Inspector:tostring() + return table.concat(self.buffer) +end + +setmetatable(inspect, { __call = function(_,t,depth) + return Inspector:new(t, depth):tostring() +end }) + +return inspect +