dotfiles from arch
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
local m = require 'lpeglabel'
|
||||
local matcher = require 'glob.matcher'
|
||||
|
||||
local function prop(name, pat)
|
||||
return m.Cg(m.Cc(true), name) * pat
|
||||
end
|
||||
|
||||
local function object(type, pat)
|
||||
return m.Ct(
|
||||
m.Cg(m.Cc(type), 'type') *
|
||||
m.Cg(pat, 'value')
|
||||
)
|
||||
end
|
||||
|
||||
local function expect(p, err)
|
||||
return p + m.T(err)
|
||||
end
|
||||
|
||||
local parser = m.P {
|
||||
'Main',
|
||||
['Sp'] = m.S(' \t')^0,
|
||||
['Slash'] = m.S('/')^1,
|
||||
['Main'] = m.Ct(m.V'Sp' * m.P'{' * m.V'Pattern' * (',' * expect(m.V'Pattern', 'Miss exp after ","'))^0 * m.P'}')
|
||||
+ m.Ct(m.V'Pattern')
|
||||
+ m.T'Main Failed'
|
||||
,
|
||||
['Pattern'] = m.Ct(m.V'Sp' * prop('neg', m.P'!') * expect(m.V'Unit', 'Miss exp after "!"'))
|
||||
+ m.Ct(m.V'Unit')
|
||||
,
|
||||
['NeedRoot'] = prop('root', (m.P'.' * m.V'Slash' + m.V'Slash')),
|
||||
['Unit'] = m.V'Sp' * m.V'NeedRoot'^-1 * expect(m.V'Exp', 'Miss exp') * m.V'Sp',
|
||||
['Exp'] = m.V'Sp' * (m.V'FSymbol' + object('/', m.V'Slash') + m.V'Word')^0 * m.V'Sp',
|
||||
['Word'] = object('word', m.Ct((m.V'CSymbol' + m.V'Char' - m.V'FSymbol')^1)),
|
||||
['CSymbol'] = object('*', m.P'*')
|
||||
+ object('?', m.P'?')
|
||||
+ object('[]', m.V'Range')
|
||||
,
|
||||
['SimpleChar'] = m.P(1) - m.S',{}[]*?/',
|
||||
['EscChar'] = m.P'\\' / '' * m.P(1),
|
||||
['Char'] = object('char', m.Cs((m.V'EscChar' + m.V'SimpleChar')^1)),
|
||||
['FSymbol'] = object('**', m.P'**'),
|
||||
['Range'] = m.P'[' * m.Ct(m.V'RangeUnit'^0) * m.P']'^-1,
|
||||
['RangeUnit'] = m.Ct(- m.P']' * m.C(m.P(1)) * (m.P'-' * - m.P']' * m.C(m.P(1)))^-1),
|
||||
}
|
||||
|
||||
---@class gitignore
|
||||
---@field pattern string[]
|
||||
---@field options table
|
||||
---@field errors table[]
|
||||
---@field matcher table
|
||||
---@field interface function[]
|
||||
---@field data table
|
||||
local mt = {}
|
||||
mt.__index = mt
|
||||
mt.__name = 'gitignore'
|
||||
|
||||
function mt:addPattern(pat)
|
||||
if type(pat) ~= 'string' then
|
||||
return
|
||||
end
|
||||
self.pattern[#self.pattern+1] = pat
|
||||
if self.options.ignoreCase then
|
||||
pat = pat:lower()
|
||||
end
|
||||
local states, err = parser:match(pat)
|
||||
if not states then
|
||||
self.errors[#self.errors+1] = {
|
||||
pattern = pat,
|
||||
message = err
|
||||
}
|
||||
return
|
||||
end
|
||||
for _, state in ipairs(states) do
|
||||
self.matcher[#self.matcher+1] = matcher(state)
|
||||
end
|
||||
end
|
||||
|
||||
function mt:setOption(op, val)
|
||||
if val == nil then
|
||||
val = true
|
||||
end
|
||||
self.options[op] = val
|
||||
end
|
||||
|
||||
---@param key string | "'type'" | "'list'"
|
||||
---@param func function | "function (path) end"
|
||||
function mt:setInterface(key, func)
|
||||
if type(func) ~= 'function' then
|
||||
return
|
||||
end
|
||||
self.interface[key] = func
|
||||
end
|
||||
|
||||
function mt:callInterface(name, params)
|
||||
local func = self.interface[name]
|
||||
return func(params, self.data)
|
||||
end
|
||||
|
||||
function mt:hasInterface(name)
|
||||
return self.interface[name] ~= nil
|
||||
end
|
||||
|
||||
function mt:checkDirectory(catch, path, matcher0)
|
||||
if not self:hasInterface 'type' then
|
||||
return true
|
||||
end
|
||||
if not matcher0:isNeedDirectory() then
|
||||
return true
|
||||
end
|
||||
if #catch < #path then
|
||||
-- if path is 'a/b/c' and catch is 'a/b'
|
||||
-- then the catch must be a directory
|
||||
return true
|
||||
else
|
||||
return self:callInterface('type', path) == 'directory'
|
||||
end
|
||||
end
|
||||
|
||||
function mt:simpleMatch(path)
|
||||
path = self:getRelativePath(path)
|
||||
for i = #self.matcher, 1, -1 do
|
||||
local matcher0 = self.matcher[i]
|
||||
local catch = matcher0(path)
|
||||
if catch and self:checkDirectory(catch, path, matcher0) then
|
||||
if matcher0:isNegative() then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function mt:finishMatch(path)
|
||||
local paths = {}
|
||||
for filename in path:gmatch '[^/\\]+' do
|
||||
paths[#paths+1] = filename
|
||||
end
|
||||
for i = 1, #paths do
|
||||
local newPath = table.concat(paths, '/', 1, i)
|
||||
local passed = self:simpleMatch(newPath)
|
||||
if passed == true then
|
||||
return true
|
||||
elseif passed == false then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function mt:getRelativePath(path)
|
||||
local root = self.options.root or ''
|
||||
if self.options.ignoreCase then
|
||||
path = path:lower()
|
||||
root = root:lower()
|
||||
end
|
||||
path = path:gsub('^[/\\]+', ''):gsub('[/\\]+', '/')
|
||||
root = root:gsub('^[/\\]+', ''):gsub('[/\\]+', '/')
|
||||
if path:sub(1, #root) == root then
|
||||
path = path:sub(#root + 1)
|
||||
path = path:gsub('^[/\\]+', '')
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
---@param callback async fun(path: string)
|
||||
---@param hook? async fun(ev: string, ...)
|
||||
---@async
|
||||
function mt:scan(path, callback, hook)
|
||||
local files = {}
|
||||
local list = {}
|
||||
|
||||
---@async
|
||||
local function check(current)
|
||||
local fileType = self:callInterface('type', current)
|
||||
if fileType == 'file' then
|
||||
if callback then
|
||||
callback(current)
|
||||
end
|
||||
files[#files+1] = current
|
||||
elseif fileType == 'directory' then
|
||||
local result = self:callInterface('list', current)
|
||||
if type(result) == 'table' then
|
||||
for _, path0 in ipairs(result) do
|
||||
local filename = path0:match '([^/\\]+)[/\\]*$'
|
||||
if filename
|
||||
and filename ~= '.'
|
||||
and filename ~= '..' then
|
||||
list[#list+1] = path0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not self:simpleMatch(path) then
|
||||
check(path)
|
||||
end
|
||||
while #list > 0 do
|
||||
local current = list[#list]
|
||||
if not current then
|
||||
break
|
||||
end
|
||||
list[#list] = nil
|
||||
if hook then
|
||||
hook('scan', current)
|
||||
end
|
||||
if not self:simpleMatch(current) then
|
||||
check(current)
|
||||
end
|
||||
end
|
||||
return files
|
||||
end
|
||||
|
||||
function mt:__call(path)
|
||||
path = self:getRelativePath(path)
|
||||
return self:finishMatch(path)
|
||||
end
|
||||
|
||||
return function (pattern, options, interface)
|
||||
local self = setmetatable({
|
||||
pattern = {},
|
||||
options = {},
|
||||
matcher = {},
|
||||
errors = {},
|
||||
interface = {},
|
||||
data = {},
|
||||
}, mt)
|
||||
|
||||
if type(options) == 'table' then
|
||||
for op, val in pairs(options) do
|
||||
self:setOption(op, val)
|
||||
end
|
||||
end
|
||||
|
||||
if type(pattern) == 'table' then
|
||||
for _, pat in ipairs(pattern) do
|
||||
self:addPattern(pat)
|
||||
end
|
||||
else
|
||||
self:addPattern(pattern)
|
||||
end
|
||||
|
||||
if type(interface) == 'table' then
|
||||
for key, func in pairs(interface) do
|
||||
self:setInterface(key, func)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -0,0 +1,125 @@
|
||||
local m = require 'lpeglabel'
|
||||
local matcher = require 'glob.matcher'
|
||||
|
||||
local function prop(name, pat)
|
||||
return m.Cg(m.Cc(true), name) * pat
|
||||
end
|
||||
|
||||
local function object(type, pat)
|
||||
return m.Ct(
|
||||
m.Cg(m.Cc(type), 'type') *
|
||||
m.Cg(pat, 'value')
|
||||
)
|
||||
end
|
||||
|
||||
local function expect(p, err)
|
||||
return p + m.T(err)
|
||||
end
|
||||
|
||||
local parser = m.P {
|
||||
'Main',
|
||||
['Sp'] = m.S(' \t')^0,
|
||||
['Slash'] = m.P('/')^1,
|
||||
['Main'] = m.Ct(m.V'Sp' * m.P'{' * m.V'Pattern' * (',' * expect(m.V'Pattern', 'Miss exp after ","'))^0 * m.P'}')
|
||||
+ m.Ct(m.V'Pattern')
|
||||
+ m.T'Main Failed'
|
||||
,
|
||||
['Pattern'] = m.Ct(m.V'Sp' * prop('neg', m.P'!') * expect(m.V'Unit', 'Miss exp after "!"'))
|
||||
+ m.Ct(m.V'Unit')
|
||||
,
|
||||
['NeedRoot'] = prop('root', (m.P'.' * m.V'Slash' + m.V'Slash')),
|
||||
['Unit'] = m.V'Sp' * m.V'NeedRoot'^-1 * expect(m.V'Exp', 'Miss exp') * m.V'Sp',
|
||||
['Exp'] = m.V'Sp' * (m.V'FSymbol' + object('/', m.V'Slash') + m.V'Word')^0 * m.V'Sp',
|
||||
['Word'] = object('word', m.Ct((m.V'CSymbol' + m.V'Char' - m.V'FSymbol')^1)),
|
||||
['CSymbol'] = object('*', m.P'*')
|
||||
+ object('?', m.P'?')
|
||||
+ object('[]', m.V'Range')
|
||||
,
|
||||
['SimpleChar'] = m.P(1) - m.S',{}[]*?/',
|
||||
['EscChar'] = m.P'\\' / '' * m.P(1),
|
||||
['Char'] = object('char', m.Cs((m.V'EscChar' + m.V'SimpleChar')^1)),
|
||||
['FSymbol'] = object('**', m.P'**'),
|
||||
['RangeWord'] = 1 - m.P']',
|
||||
['Range'] = m.P'[' * m.Ct(m.V'RangeUnit'^0) * m.P']'^-1,
|
||||
['RangeUnit'] = m.Ct(m.C(m.V'RangeWord') * m.P'-' * m.C(m.V'RangeWord'))
|
||||
+ m.V'RangeWord',
|
||||
}
|
||||
|
||||
local mt = {}
|
||||
mt.__index = mt
|
||||
mt.__name = 'glob'
|
||||
|
||||
function mt:addPattern(pat)
|
||||
if type(pat) ~= 'string' then
|
||||
return
|
||||
end
|
||||
self.pattern[#self.pattern+1] = pat
|
||||
if self.options.ignoreCase then
|
||||
pat = pat:lower()
|
||||
end
|
||||
local states, err = parser:match(pat)
|
||||
if not states then
|
||||
self.errors[#self.errors+1] = {
|
||||
pattern = pat,
|
||||
message = err
|
||||
}
|
||||
return
|
||||
end
|
||||
for _, state in ipairs(states) do
|
||||
if state.neg then
|
||||
self.refused[#self.refused+1] = matcher(state)
|
||||
else
|
||||
self.passed[#self.passed+1] = matcher(state)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mt:setOption(op, val)
|
||||
if val == nil then
|
||||
val = true
|
||||
end
|
||||
self.options[op] = val
|
||||
end
|
||||
|
||||
function mt:__call(path)
|
||||
if self.options.ignoreCase then
|
||||
path = path:lower()
|
||||
end
|
||||
path = path:gsub('^[/\\]+', '')
|
||||
for _, refused in ipairs(self.refused) do
|
||||
if refused(path) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
for _, passed in ipairs(self.passed) do
|
||||
if passed(path) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return function (pattern, options)
|
||||
local self = setmetatable({
|
||||
pattern = {},
|
||||
options = {},
|
||||
passed = {},
|
||||
refused = {},
|
||||
errors = {},
|
||||
}, mt)
|
||||
|
||||
if type(pattern) == 'table' then
|
||||
for _, pat in ipairs(pattern) do
|
||||
self:addPattern(pat)
|
||||
end
|
||||
else
|
||||
self:addPattern(pattern)
|
||||
end
|
||||
|
||||
if type(options) == 'table' then
|
||||
for op, val in pairs(options) do
|
||||
self:setOption(op, val)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
@@ -0,0 +1,4 @@
|
||||
return {
|
||||
glob = require 'glob.glob',
|
||||
gitignore = require 'glob.gitignore',
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
local m = require 'lpeglabel'
|
||||
|
||||
local Slash = m.S('/\\')^1
|
||||
local Symbol = m.S',{}[]*?/\\'
|
||||
local Char = 1 - Symbol
|
||||
local Path = (1 - m.S[[\/*?"<>|]])^1 * Slash
|
||||
local NoWord = #(m.P(-1) + Symbol)
|
||||
local function whatHappened()
|
||||
return m.Cmt(m.P(1)^1, function (...)
|
||||
print(...)
|
||||
end)
|
||||
end
|
||||
|
||||
local mt = {}
|
||||
mt.__index = mt
|
||||
mt.__name = 'matcher'
|
||||
|
||||
function mt:exp(state, index)
|
||||
local exp = state[index]
|
||||
if not exp then
|
||||
return
|
||||
end
|
||||
if exp.type == 'word' then
|
||||
return self:word(exp, state, index + 1)
|
||||
elseif exp.type == 'char' then
|
||||
return self:char(exp, state, index + 1)
|
||||
elseif exp.type == '**' then
|
||||
return self:anyPath(exp, state, index + 1)
|
||||
elseif exp.type == '*' then
|
||||
return self:anyChar(exp, state, index + 1)
|
||||
elseif exp.type == '?' then
|
||||
return self:oneChar(exp, state, index + 1)
|
||||
elseif exp.type == '[]' then
|
||||
return self:range(exp, state, index + 1)
|
||||
elseif exp.type == '/' then
|
||||
return self:slash(exp, state, index + 1)
|
||||
end
|
||||
end
|
||||
|
||||
function mt:word(exp, state, index)
|
||||
local current = self:exp(exp.value, 1)
|
||||
local after = self:exp(state, index)
|
||||
if after then
|
||||
return current * Slash * after
|
||||
else
|
||||
return current
|
||||
end
|
||||
end
|
||||
|
||||
function mt:char(exp, state, index)
|
||||
local current = m.P(exp.value)
|
||||
local after = self:exp(state, index)
|
||||
if after then
|
||||
return current * after * NoWord
|
||||
else
|
||||
return current * NoWord
|
||||
end
|
||||
end
|
||||
|
||||
function mt:anyPath(_, state, index)
|
||||
local after = self:exp(state, index)
|
||||
if after then
|
||||
return m.P {
|
||||
'Main',
|
||||
Main = after
|
||||
+ Path * m.V'Main'
|
||||
}
|
||||
else
|
||||
return Path^0
|
||||
end
|
||||
end
|
||||
|
||||
function mt:anyChar(_, state, index)
|
||||
local after = self:exp(state, index)
|
||||
if after then
|
||||
return m.P {
|
||||
'Main',
|
||||
Main = after
|
||||
+ Char * m.V'Main'
|
||||
}
|
||||
else
|
||||
return Char^0
|
||||
end
|
||||
end
|
||||
|
||||
function mt:oneChar(_, state, index)
|
||||
local after = self:exp(state, index)
|
||||
if after then
|
||||
return Char * after
|
||||
else
|
||||
return Char
|
||||
end
|
||||
end
|
||||
|
||||
function mt:range(exp, state, index)
|
||||
local after = self:exp(state, index)
|
||||
local ranges = {}
|
||||
local selects = {}
|
||||
for _, range in ipairs(exp.value) do
|
||||
if #range == 1 then
|
||||
selects[#selects+1] = range[1]
|
||||
elseif #range == 2 then
|
||||
ranges[#ranges+1] = range[1] .. range[2]
|
||||
end
|
||||
end
|
||||
local current = m.S(table.concat(selects)) + m.R(table.unpack(ranges))
|
||||
if after then
|
||||
return current * after
|
||||
else
|
||||
return current
|
||||
end
|
||||
end
|
||||
|
||||
function mt:slash(_, state, index)
|
||||
local after = self:exp(state, index)
|
||||
if after then
|
||||
return after
|
||||
else
|
||||
self.needDirectory = true
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
function mt:pattern(state)
|
||||
if state.root then
|
||||
local after = self:exp(state, 1)
|
||||
if after then
|
||||
return m.C(after)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
else
|
||||
return m.C(self:anyPath(nil, state, 1))
|
||||
end
|
||||
end
|
||||
|
||||
function mt:isNeedDirectory()
|
||||
return self.needDirectory == true
|
||||
end
|
||||
|
||||
function mt:isNegative()
|
||||
return self.state.neg == true
|
||||
end
|
||||
|
||||
function mt:__call(path)
|
||||
return self.matcher:match(path)
|
||||
end
|
||||
|
||||
return function (state, options)
|
||||
local self = setmetatable({
|
||||
options = options,
|
||||
state = state,
|
||||
}, mt)
|
||||
self.matcher = self:pattern(state)
|
||||
if not self.matcher then
|
||||
return nil
|
||||
end
|
||||
return self
|
||||
end
|
||||
Reference in New Issue
Block a user