Browse Source

Merge commit '9ef588eb0e' as 'locator'

Paul Liverman III 6 years ago
parent
commit
e9d8481f88
9 changed files with 423 additions and 0 deletions
  1. 3
    0
      locator/.gitignore
  2. 21
    0
      locator/LICENSE
  3. 104
    0
      locator/ReadMe.md
  4. 45
    0
      locator/add.lua
  5. 38
    0
      locator/add.moon
  6. 9
    0
      locator/example/locator_config.moon
  7. 120
    0
      locator/init.moon
  8. 45
    0
      locator/pull.lua
  9. 38
    0
      locator/pull.moon

+ 3
- 0
locator/.gitignore View File

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

+ 21
- 0
locator/LICENSE View File

@@ -0,0 +1,21 @@
1
+MIT License
2
+
3
+Copyright (c) 2018 Paul Liverman III
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in all
13
+copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 104
- 0
locator/ReadMe.md View File

@@ -0,0 +1,104 @@
1
+# locator
2
+
3
+A <strike>service</strike> module locator for use with Lapis.
4
+(Which doesn't use anythng Lapis-specific, so you could use it elsewhere.)
5
+
6
+Installation:
7
+
8
+1. subtree this project into the root of your project.
9
+2. `echo 'return require((...) .. ".init")' > locator.lua` (and make sure it is
10
+   committed!)
11
+3. Make `locator_config.moon` using the configuration format specified below.
12
+4. `echo 'return require("locator").models' > models.moon` (to replace the
13
+   models autoloader provided with Lapis with our autoloader)
14
+
15
+## Usages
16
+
17
+1. `autoload`: A function to allow automatically loading modules when accessing
18
+   a table.
19
+2. Implicit autoloaders: The module itself is an autoloader, and can be called
20
+   to create a specialized autoloader that checks directories local to where
21
+   your code is before other paths.
22
+3. `make_migrations`: A function to make integrating migrations tables from
23
+   multiple Lapis projects into one to streamline applying migrations.
24
+
25
+### `autoload` function
26
+
27
+The core functionality is based on the `autoload` function, which returns a
28
+table with `__call` and `__index` metamethods, allowing things like:
29
+
30
+```
31
+locator = require "locator"
32
+
33
+-- the module itself is an autoloader, so you can grab anything in a
34
+-- sub-directory like so:
35
+import Users, Posts from locator.models -- looks in 'models' directory first
36
+
37
+-- you can also use it (locator.models) as a function to require from models
38
+Model = locator.models "Model" -- will look for 'models.Model' (& config paths)
39
+```
40
+
41
+You can create autoloaders yourself: `models = locator.autoload "models"`
42
+
43
+### Implicit autoloaders
44
+
45
+In sub-applications, make requiring things easier by acting like everything is
46
+in your repository's root:
47
+
48
+```
49
+-- returns an autoloader that will try to require from wherever your
50
+--  sub-application is installed before other paths
51
+locate = require("locator")(...)
52
+
53
+util = locate "util" -- will grab *our* utility functions
54
+```
55
+
56
+Grab a table of all views: `views = locator.views` (will create an autoloader
57
+  when you try to access it)
58
+
59
+### `make_migrations` function
60
+
61
+In your migrations, do something like this:
62
+
63
+```
64
+import make_migrations from require "locator"
65
+import create_table, types from require "lapis.db.schema"
66
+
67
+return make_migrations {
68
+  [1518418142]: =>
69
+    create_table "articles", {
70
+      {"id", types.serial primary_key: true}
71
+      {"title", types.text unique: true}
72
+      {"content", types.text}
73
+    }
74
+}
75
+```
76
+
77
+Whatever migrations you have defined will be combined with migrations specified
78
+in sub-applications added to `locator_config`, subject to conditions in the
79
+config file.
80
+
81
+## Config
82
+
83
+Example configuration:
84
+
85
+```
86
+{
87
+  {
88
+    path: "applications.users" -- there is a sub-application at this location
89
+    migrations: {after: 1518414112} -- don't run this migration or any before it
90
+  }
91
+  {
92
+    path: "utility" -- another sub-application is here
93
+  }
94
+}
95
+```
96
+
97
+The only required value per item in the configuration is `path`, formatted with
98
+periods as a directory-separator. Currently, the only point of specifying a
99
+migrations table is to specify a migration ID to ignore it and every migration
100
+before it within that sub-application.
101
+
102
+Without the configuration file, locator will crash with an error, and without
103
+a path specified, locator doesn't know where else to look for files to require,
104
+so it is very essential.

+ 45
- 0
locator/add.lua View File

@@ -0,0 +1,45 @@
1
+local config = require("locator_config")
2
+local execute
3
+execute = function(cmd, capture_exit_code)
4
+  if capture_exit_code == nil then
5
+    capture_exit_code = true
6
+  end
7
+  local handle
8
+  if capture_exit_code then
9
+    handle = io.popen(tostring(cmd) .. "\necho $?")
10
+  else
11
+    handle = io.popen(cmd)
12
+  end
13
+  local result = handle:read("*a")
14
+  handle:close()
15
+  local exit_start, exit_end = result:find("(%d*)[%c]$")
16
+  local exit_code = tonumber(result:sub(exit_start, exit_end):sub(1, -2))
17
+  local output = result:sub(1, exit_start - 1)
18
+  if exit_code == 0 then
19
+    return output
20
+  else
21
+    return error("sub-process '" .. tostring(cmd) .. "' returned status " .. tostring(exit_code) .. ".\n\n" .. tostring(output))
22
+  end
23
+end
24
+local list = execute("git remote")
25
+local remotes = { }
26
+for line in list:gmatch("[^\n]+") do
27
+  remotes[line] = true
28
+end
29
+for _index_0 = 1, #config do
30
+  local item = config[_index_0]
31
+  if item.remote and item.remote.fetch then
32
+    if not (item.remote.branch) then
33
+      item.remote.branch = "master"
34
+    end
35
+    if item.remote.name then
36
+      if not (remotes[item.remote.name]) then
37
+        execute("git remote add -f " .. tostring(item.remote.name) .. " " .. tostring(item.remote.fetch))
38
+        if item.remote.push and not ("boolean" == type(item.remote.push)) then
39
+          execute("git remote set-url --push " .. tostring(item.remote.name) .. " " .. tostring(item.remote.push))
40
+        end
41
+      end
42
+    end
43
+    execute("git subtree add --prefix " .. tostring(item.path:gsub("%.", "/")) .. " " .. tostring(item.remote.fetch) .. " " .. tostring(item.remote.branch) .. " --squash")
44
+  end
45
+end

+ 38
- 0
locator/add.moon View File

@@ -0,0 +1,38 @@
1
+config = require "locator_config"
2
+
3
+execute = (cmd, capture_exit_code=true) ->
4
+  local handle
5
+  if capture_exit_code
6
+    handle = io.popen "#{cmd}\necho $?"
7
+  else
8
+    handle = io.popen cmd
9
+  result = handle\read "*a"
10
+  handle\close!
11
+
12
+  exit_start, exit_end = result\find "(%d*)[%c]$"
13
+  exit_code = tonumber result\sub(exit_start, exit_end)\sub 1, -2
14
+  output = result\sub 1, exit_start - 1
15
+
16
+  if exit_code == 0
17
+    return output
18
+  else
19
+    error "sub-process '#{cmd}' returned status #{exit_code}.\n\n#{output}"
20
+
21
+list = execute "git remote"
22
+remotes = {}
23
+for line in list\gmatch "[^\n]+"
24
+  remotes[line] = true
25
+
26
+for item in *config
27
+  if item.remote and item.remote.fetch -- if configured to pull
28
+    unless item.remote.branch
29
+      item.remote.branch = "master"
30
+
31
+    if item.remote.name -- if we want a named remote
32
+      unless remotes[item.remote.name] -- add it if needed
33
+        execute "git remote add -f #{item.remote.name} #{item.remote.fetch}"
34
+        if item.remote.push and not ("boolean" == type item.remote.push)
35
+          execute "git remote set-url --push #{item.remote.name} #{item.remote.push}"
36
+
37
+    -- we actually ignore names with in-script usage..
38
+    execute "git subtree add --prefix #{item.path\gsub "%.", "/"} #{item.remote.fetch} #{item.remote.branch} --squash"

+ 9
- 0
locator/example/locator_config.moon View File

@@ -0,0 +1,9 @@
1
+{
2
+  {
3
+    path: "applications.users" -- there is a sub-application at this location
4
+    migrations: {after: 1518414112} -- don't run this migration or any before it
5
+  }
6
+  {
7
+    path: "utility" -- another sub-application is here
8
+  }
9
+}

+ 120
- 0
locator/init.moon View File

@@ -0,0 +1,120 @@
1
+config = require "locator_config" -- TODO combine w values from Lapis's config, those overwriting these (this will be a legacy option)
2
+
3
+import insert, sort from table
4
+
5
+-- require, but only errors when a module errors during loading
6
+check_require = (path) ->
7
+  ok, value = pcall -> require path
8
+  if ok or ("string" == type(value) and value\find "module '#{path}' not found")
9
+    return ok, value
10
+  else
11
+    error value
12
+
13
+-- locates and returns a module
14
+--  if a path is specified, it will be checked before other paths
15
+--  checks the project root, then each path specified in locator_config
16
+locate = (name, path) ->
17
+  print "locate ->"
18
+  if path
19
+    print "  try '#{path}.#{name}'"
20
+    ok, value = check_require "#{path}.#{name}"
21
+    return value if ok
22
+
23
+  print "  try '#{name}'"
24
+  ok, value = check_require name
25
+  return value if ok
26
+
27
+  for item in *config
28
+    if path
29
+      print "  try '#{item.path}.#{path}.#{name}'"
30
+      ok, value = check_require "#{item.path}.#{path}.#{name}"
31
+    else
32
+      print "  try '#{item.path}.#{name}'"
33
+      ok, value = check_require "#{item.path}.#{name}"
34
+    return value if ok
35
+
36
+  if path
37
+    error "locator could not find '#{path}.#{name}'"
38
+  else
39
+    error "locator could not find '#{name}'"
40
+
41
+-- works like Lapis's autoload, but
42
+--  includes trying sub-application paths & can be called to access a value
43
+autoload = (path, tab={}) ->
44
+  return setmetatable tab, {
45
+    __call: (t, name) ->
46
+      t[name] = locate name, path
47
+      return t[name]
48
+    __index: (t, name) ->
49
+      t[name] = locate name, path
50
+      return t[name]
51
+  }
52
+
53
+-- pass your migrations, it returns them + all sub-application migrations
54
+--  (legacy) see example config for how to specify to not include early migrations
55
+make_migrations = (app_migrations={}) ->
56
+  for item in *config
57
+    ok, migrations = check_require "#{item.path}.migrations"
58
+    if ok
59
+      sorted = {}
60
+      for m in pairs migrations
61
+        insert sorted, m
62
+      sort sorted
63
+      for i in *sorted
64
+        -- only allow migrations after specified config value, or if no 'after' is specified
65
+        if (item.migrations and ((item.migrations.after and i > item.migrations.after) or not item.migrations.after)) or not item.migrations
66
+          -- if your migrations and theirs share a value, combine them
67
+          if app_fn = app_migrations[i]
68
+            app_migrations[i] = (...) ->
69
+              app_fn(...)
70
+              migrations[i](...)
71
+          -- else just add them
72
+          else
73
+            app_migrations[i] = migrations[i]
74
+
75
+  return app_migrations
76
+
77
+-- sub-applications can define custom functions in a `locator_config` file in
78
+--  their root directory. These functions are aggregated by name and called in
79
+--  the order defined by the paths in the root locator_config (with the root
80
+--  being called first)
81
+registry = setmetatable {}, {
82
+  __index: (t, name) ->
83
+    registered_functions = {}
84
+
85
+    if config[name]
86
+      insert registered_functions, config[name]
87
+
88
+    for item in *config
89
+      ok, register = check_require "#{item.path}.locator_config"
90
+      if ok and register[name]
91
+        insert registered_functions, register[name]
92
+
93
+    if #registered_functions > 0
94
+      t[name] = (...) ->
95
+        for i=1, #registered_functions-1
96
+          registered_functions[i](...)
97
+        return registered_functions[#registered_functions](...)
98
+    else
99
+      t[name] = ->
100
+
101
+    return t[name]
102
+}
103
+
104
+-- public interface:
105
+--  functions: autoload, make_migrations
106
+--  tables: locate (locator alias), registry
107
+return setmetatable {
108
+  :locate, :autoload, :make_migrations, :registry
109
+}, {
110
+  __call: (t, here) ->
111
+    if here and here\find "%."
112
+      here = here\sub 1, here\len! - here\match(".*%.(.+)")\len! - 1
113
+    else
114
+      here = nil
115
+    return autoload here
116
+
117
+  __index: (t, name) ->
118
+    t[name] = autoload name
119
+    return t[name]
120
+}

+ 45
- 0
locator/pull.lua View File

@@ -0,0 +1,45 @@
1
+local config = require("locator_config")
2
+local execute
3
+execute = function(cmd, capture_exit_code)
4
+  if capture_exit_code == nil then
5
+    capture_exit_code = true
6
+  end
7
+  local handle
8
+  if capture_exit_code then
9
+    handle = io.popen(tostring(cmd) .. "\necho $?")
10
+  else
11
+    handle = io.popen(cmd)
12
+  end
13
+  local result = handle:read("*a")
14
+  handle:close()
15
+  local exit_start, exit_end = result:find("(%d*)[%c]$")
16
+  local exit_code = tonumber(result:sub(exit_start, exit_end):sub(1, -2))
17
+  local output = result:sub(1, exit_start - 1)
18
+  if exit_code == 0 then
19
+    return output
20
+  else
21
+    return error("sub-process '" .. tostring(cmd) .. "' returned status " .. tostring(exit_code) .. ".\n\n" .. tostring(output))
22
+  end
23
+end
24
+local list = execute("git remote")
25
+local remotes = { }
26
+for line in list:gmatch("[^\n]+") do
27
+  remotes[line] = true
28
+end
29
+for _index_0 = 1, #config do
30
+  local item = config[_index_0]
31
+  if item.remote and item.remote.fetch then
32
+    if not (item.remote.branch) then
33
+      item.remote.branch = "master"
34
+    end
35
+    if item.remote.name then
36
+      if not (remotes[item.remote.name]) then
37
+        execute("git remote add -f " .. tostring(item.remote.name) .. " " .. tostring(item.remote.fetch))
38
+        if item.remote.push and not ("boolean" == type(item.remote.push)) then
39
+          execute("git remote set-url --push " .. tostring(item.remote.name) .. " " .. tostring(item.remote.push))
40
+        end
41
+      end
42
+    end
43
+    execute("git subtree pull --prefix " .. tostring(item.path:gsub("%.", "/")) .. " " .. tostring(item.remote.fetch) .. " " .. tostring(item.remote.branch) .. " --squash")
44
+  end
45
+end

+ 38
- 0
locator/pull.moon View File

@@ -0,0 +1,38 @@
1
+config = require "locator_config"
2
+
3
+execute = (cmd, capture_exit_code=true) ->
4
+  local handle
5
+  if capture_exit_code
6
+    handle = io.popen "#{cmd}\necho $?"
7
+  else
8
+    handle = io.popen cmd
9
+  result = handle\read "*a"
10
+  handle\close!
11
+
12
+  exit_start, exit_end = result\find "(%d*)[%c]$"
13
+  exit_code = tonumber result\sub(exit_start, exit_end)\sub 1, -2
14
+  output = result\sub 1, exit_start - 1
15
+
16
+  if exit_code == 0
17
+    return output
18
+  else
19
+    error "sub-process '#{cmd}' returned status #{exit_code}.\n\n#{output}"
20
+
21
+list = execute "git remote"
22
+remotes = {}
23
+for line in list\gmatch "[^\n]+"
24
+  remotes[line] = true
25
+
26
+for item in *config
27
+  if item.remote and item.remote.fetch -- if configured to pull
28
+    unless item.remote.branch
29
+      item.remote.branch = "master"
30
+
31
+    if item.remote.name -- if we want a named remote
32
+      unless remotes[item.remote.name] -- add it if needed
33
+        execute "git remote add -f #{item.remote.name} #{item.remote.fetch}"
34
+        if item.remote.push and not ("boolean" == type item.remote.push)
35
+          execute "git remote set-url --push #{item.remote.name} #{item.remote.push}"
36
+
37
+    -- we actually ignore names with in-script usage..
38
+    execute "git subtree pull --prefix #{item.path\gsub "%.", "/"} #{item.remote.fetch} #{item.remote.branch} --squash"