Merge commit '656622cc88bbd5e30bff3980611b9e7c26059719' as 'locator'

This commit is contained in:
Paul Liverman III 2018-03-13 02:10:13 -07:00
commit c3a14a6392
9 changed files with 423 additions and 0 deletions

3
locator/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.lua
!pull.lua
!add.lua

21
locator/LICENSE Normal file
View File

@ -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.

104
locator/ReadMe.md Normal file
View File

@ -0,0 +1,104 @@
# locator
A <strike>service</strike> 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.

45
locator/add.lua Normal file
View File

@ -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

38
locator/add.moon Normal file
View File

@ -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"

View File

@ -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
}
}

120
locator/init.moon Normal file
View File

@ -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]
}

45
locator/pull.lua Normal file
View File

@ -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

38
locator/pull.moon Normal file
View File

@ -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"