From 656622cc88bbd5e30bff3980611b9e7c26059719 Mon Sep 17 00:00:00 2001 From: Paul Liverman III Date: Tue, 13 Mar 2018 02:10:13 -0700 Subject: [PATCH] Squashed 'locator/' content from commit 98598d4 git-subtree-dir: locator git-subtree-split: 98598d4ad0abec6b63aa48b51ed8085cfad69f25 --- .gitignore | 3 + LICENSE | 21 +++++++ ReadMe.md | 104 +++++++++++++++++++++++++++++++ add.lua | 45 ++++++++++++++ add.moon | 38 ++++++++++++ example.locator_config.moon | 9 +++ init.moon | 120 ++++++++++++++++++++++++++++++++++++ pull.lua | 45 ++++++++++++++ pull.moon | 38 ++++++++++++ 9 files changed, 423 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 ReadMe.md create mode 100644 add.lua create mode 100644 add.moon create mode 100644 example.locator_config.moon create mode 100644 init.moon create mode 100644 pull.lua create mode 100644 pull.moon diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ae321a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.lua +!pull.lua +!add.lua diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..29fad8e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Paul Liverman III + +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. diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..c227a72 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,104 @@ +# locator + +A service module locator for use with Lapis. +(Which doesn't use anythng Lapis-specific, so you could use it elsewhere.) + +Installation: + +1. subtree this project into the root of your project. +2. `echo 'return require((...) .. ".init")' > locator.lua` (and make sure it is + committed!) +3. Make `locator_config.moon` using the configuration format specified below. +4. `echo 'return require("locator").models' > models.moon` (to replace the + models autoloader provided with Lapis with our autoloader) + +## Usages + +1. `autoload`: A function to allow automatically loading modules when accessing + a table. +2. Implicit autoloaders: The module itself is an autoloader, and can be called + to create a specialized autoloader that checks directories local to where + your code is before other paths. +3. `make_migrations`: A function to make integrating migrations tables from + multiple Lapis projects into one to streamline applying migrations. + +### `autoload` function + +The core functionality is based on the `autoload` function, which returns a +table with `__call` and `__index` metamethods, allowing things like: + +``` +locator = require "locator" + +-- the module itself is an autoloader, so you can grab anything in a +-- sub-directory like so: +import Users, Posts from locator.models -- looks in 'models' directory first + +-- you can also use it (locator.models) as a function to require from models +Model = locator.models "Model" -- will look for 'models.Model' (& config paths) +``` + +You can create autoloaders yourself: `models = locator.autoload "models"` + +### Implicit autoloaders + +In sub-applications, make requiring things easier by acting like everything is +in your repository's root: + +``` +-- returns an autoloader that will try to require from wherever your +-- sub-application is installed before other paths +locate = require("locator")(...) + +util = locate "util" -- will grab *our* utility functions +``` + +Grab a table of all views: `views = locator.views` (will create an autoloader + when you try to access it) + +### `make_migrations` function + +In your migrations, do something like this: + +``` +import make_migrations from require "locator" +import create_table, types from require "lapis.db.schema" + +return make_migrations { + [1518418142]: => + create_table "articles", { + {"id", types.serial primary_key: true} + {"title", types.text unique: true} + {"content", types.text} + } +} +``` + +Whatever migrations you have defined will be combined with migrations specified +in sub-applications added to `locator_config`, subject to conditions in the +config file. + +## Config + +Example configuration: + +``` +{ + { + path: "applications.users" -- there is a sub-application at this location + migrations: {after: 1518414112} -- don't run this migration or any before it + } + { + path: "utility" -- another sub-application is here + } +} +``` + +The only required value per item in the configuration is `path`, formatted with +periods as a directory-separator. Currently, the only point of specifying a +migrations table is to specify a migration ID to ignore it and every migration +before it within that sub-application. + +Without the configuration file, locator will crash with an error, and without +a path specified, locator doesn't know where else to look for files to require, +so it is very essential. diff --git a/add.lua b/add.lua new file mode 100644 index 0000000..c313e22 --- /dev/null +++ b/add.lua @@ -0,0 +1,45 @@ +local config = require("locator_config") +local execute +execute = function(cmd, capture_exit_code) + if capture_exit_code == nil then + capture_exit_code = true + end + local handle + if capture_exit_code then + handle = io.popen(tostring(cmd) .. "\necho $?") + else + handle = io.popen(cmd) + end + local result = handle:read("*a") + handle:close() + local exit_start, exit_end = result:find("(%d*)[%c]$") + local exit_code = tonumber(result:sub(exit_start, exit_end):sub(1, -2)) + local output = result:sub(1, exit_start - 1) + if exit_code == 0 then + return output + else + return error("sub-process '" .. tostring(cmd) .. "' returned status " .. tostring(exit_code) .. ".\n\n" .. tostring(output)) + end +end +local list = execute("git remote") +local remotes = { } +for line in list:gmatch("[^\n]+") do + remotes[line] = true +end +for _index_0 = 1, #config do + local item = config[_index_0] + if item.remote and item.remote.fetch then + if not (item.remote.branch) then + item.remote.branch = "master" + end + if item.remote.name then + if not (remotes[item.remote.name]) then + execute("git remote add -f " .. tostring(item.remote.name) .. " " .. tostring(item.remote.fetch)) + if item.remote.push and not ("boolean" == type(item.remote.push)) then + execute("git remote set-url --push " .. tostring(item.remote.name) .. " " .. tostring(item.remote.push)) + end + end + end + execute("git subtree add --prefix " .. tostring(item.path:gsub("%.", "/")) .. " " .. tostring(item.remote.fetch) .. " " .. tostring(item.remote.branch) .. " --squash") + end +end diff --git a/add.moon b/add.moon new file mode 100644 index 0000000..37e5cd0 --- /dev/null +++ b/add.moon @@ -0,0 +1,38 @@ +config = require "locator_config" + +execute = (cmd, capture_exit_code=true) -> + local handle + if capture_exit_code + handle = io.popen "#{cmd}\necho $?" + else + handle = io.popen cmd + result = handle\read "*a" + handle\close! + + exit_start, exit_end = result\find "(%d*)[%c]$" + exit_code = tonumber result\sub(exit_start, exit_end)\sub 1, -2 + output = result\sub 1, exit_start - 1 + + if exit_code == 0 + return output + else + error "sub-process '#{cmd}' returned status #{exit_code}.\n\n#{output}" + +list = execute "git remote" +remotes = {} +for line in list\gmatch "[^\n]+" + remotes[line] = true + +for item in *config + if item.remote and item.remote.fetch -- if configured to pull + unless item.remote.branch + item.remote.branch = "master" + + if item.remote.name -- if we want a named remote + unless remotes[item.remote.name] -- add it if needed + execute "git remote add -f #{item.remote.name} #{item.remote.fetch}" + if item.remote.push and not ("boolean" == type item.remote.push) + execute "git remote set-url --push #{item.remote.name} #{item.remote.push}" + + -- we actually ignore names with in-script usage.. + execute "git subtree add --prefix #{item.path\gsub "%.", "/"} #{item.remote.fetch} #{item.remote.branch} --squash" diff --git a/example.locator_config.moon b/example.locator_config.moon new file mode 100644 index 0000000..923d194 --- /dev/null +++ b/example.locator_config.moon @@ -0,0 +1,9 @@ +{ + { + path: "applications.users" -- there is a sub-application at this location + migrations: {after: 1518414112} -- don't run this migration or any before it + } + { + path: "utility" -- another sub-application is here + } +} diff --git a/init.moon b/init.moon new file mode 100644 index 0000000..6ff26b9 --- /dev/null +++ b/init.moon @@ -0,0 +1,120 @@ +config = require "locator_config" -- TODO combine w values from Lapis's config, those overwriting these (this will be a legacy option) + +import insert, sort from table + +-- require, but only errors when a module errors during loading +check_require = (path) -> + ok, value = pcall -> require path + if ok or ("string" == type(value) and value\find "module '#{path}' not found") + return ok, value + else + error value + +-- locates and returns a module +-- if a path is specified, it will be checked before other paths +-- checks the project root, then each path specified in locator_config +locate = (name, path) -> + print "locate ->" + if path + print " try '#{path}.#{name}'" + ok, value = check_require "#{path}.#{name}" + return value if ok + + print " try '#{name}'" + ok, value = check_require name + return value if ok + + for item in *config + if path + print " try '#{item.path}.#{path}.#{name}'" + ok, value = check_require "#{item.path}.#{path}.#{name}" + else + print " try '#{item.path}.#{name}'" + ok, value = check_require "#{item.path}.#{name}" + return value if ok + + if path + error "locator could not find '#{path}.#{name}'" + else + error "locator could not find '#{name}'" + +-- works like Lapis's autoload, but +-- includes trying sub-application paths & can be called to access a value +autoload = (path, tab={}) -> + return setmetatable tab, { + __call: (t, name) -> + t[name] = locate name, path + return t[name] + __index: (t, name) -> + t[name] = locate name, path + return t[name] + } + +-- pass your migrations, it returns them + all sub-application migrations +-- (legacy) see example config for how to specify to not include early migrations +make_migrations = (app_migrations={}) -> + for item in *config + ok, migrations = check_require "#{item.path}.migrations" + if ok + sorted = {} + for m in pairs migrations + insert sorted, m + sort sorted + for i in *sorted + -- only allow migrations after specified config value, or if no 'after' is specified + if (item.migrations and ((item.migrations.after and i > item.migrations.after) or not item.migrations.after)) or not item.migrations + -- if your migrations and theirs share a value, combine them + if app_fn = app_migrations[i] + app_migrations[i] = (...) -> + app_fn(...) + migrations[i](...) + -- else just add them + else + app_migrations[i] = migrations[i] + + return app_migrations + +-- sub-applications can define custom functions in a `locator_config` file in +-- their root directory. These functions are aggregated by name and called in +-- the order defined by the paths in the root locator_config (with the root +-- being called first) +registry = setmetatable {}, { + __index: (t, name) -> + registered_functions = {} + + if config[name] + insert registered_functions, config[name] + + for item in *config + ok, register = check_require "#{item.path}.locator_config" + if ok and register[name] + insert registered_functions, register[name] + + if #registered_functions > 0 + t[name] = (...) -> + for i=1, #registered_functions-1 + registered_functions[i](...) + return registered_functions[#registered_functions](...) + else + t[name] = -> + + return t[name] +} + +-- public interface: +-- functions: autoload, make_migrations +-- tables: locate (locator alias), registry +return setmetatable { + :locate, :autoload, :make_migrations, :registry +}, { + __call: (t, here="") -> + if "init" == here\sub -4 + here = here\sub 1, -6 + unless here\len! > 0 + here = "" + return autoload here + + __index: (t, name) -> + t[name] = autoload name + return t[name] +} diff --git a/pull.lua b/pull.lua new file mode 100644 index 0000000..d3b1c27 --- /dev/null +++ b/pull.lua @@ -0,0 +1,45 @@ +local config = require("locator_config") +local execute +execute = function(cmd, capture_exit_code) + if capture_exit_code == nil then + capture_exit_code = true + end + local handle + if capture_exit_code then + handle = io.popen(tostring(cmd) .. "\necho $?") + else + handle = io.popen(cmd) + end + local result = handle:read("*a") + handle:close() + local exit_start, exit_end = result:find("(%d*)[%c]$") + local exit_code = tonumber(result:sub(exit_start, exit_end):sub(1, -2)) + local output = result:sub(1, exit_start - 1) + if exit_code == 0 then + return output + else + return error("sub-process '" .. tostring(cmd) .. "' returned status " .. tostring(exit_code) .. ".\n\n" .. tostring(output)) + end +end +local list = execute("git remote") +local remotes = { } +for line in list:gmatch("[^\n]+") do + remotes[line] = true +end +for _index_0 = 1, #config do + local item = config[_index_0] + if item.remote and item.remote.fetch then + if not (item.remote.branch) then + item.remote.branch = "master" + end + if item.remote.name then + if not (remotes[item.remote.name]) then + execute("git remote add -f " .. tostring(item.remote.name) .. " " .. tostring(item.remote.fetch)) + if item.remote.push and not ("boolean" == type(item.remote.push)) then + execute("git remote set-url --push " .. tostring(item.remote.name) .. " " .. tostring(item.remote.push)) + end + end + end + execute("git subtree pull --prefix " .. tostring(item.path:gsub("%.", "/")) .. " " .. tostring(item.remote.fetch) .. " " .. tostring(item.remote.branch) .. " --squash") + end +end diff --git a/pull.moon b/pull.moon new file mode 100644 index 0000000..54e3fbc --- /dev/null +++ b/pull.moon @@ -0,0 +1,38 @@ +config = require "locator_config" + +execute = (cmd, capture_exit_code=true) -> + local handle + if capture_exit_code + handle = io.popen "#{cmd}\necho $?" + else + handle = io.popen cmd + result = handle\read "*a" + handle\close! + + exit_start, exit_end = result\find "(%d*)[%c]$" + exit_code = tonumber result\sub(exit_start, exit_end)\sub 1, -2 + output = result\sub 1, exit_start - 1 + + if exit_code == 0 + return output + else + error "sub-process '#{cmd}' returned status #{exit_code}.\n\n#{output}" + +list = execute "git remote" +remotes = {} +for line in list\gmatch "[^\n]+" + remotes[line] = true + +for item in *config + if item.remote and item.remote.fetch -- if configured to pull + unless item.remote.branch + item.remote.branch = "master" + + if item.remote.name -- if we want a named remote + unless remotes[item.remote.name] -- add it if needed + execute "git remote add -f #{item.remote.name} #{item.remote.fetch}" + if item.remote.push and not ("boolean" == type item.remote.push) + execute "git remote set-url --push #{item.remote.name} #{item.remote.push}" + + -- we actually ignore names with in-script usage.. + execute "git subtree pull --prefix #{item.path\gsub "%.", "/"} #{item.remote.fetch} #{item.remote.branch} --squash"