Initial commit
This commit is contained in:
commit
b925fbc378
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
clink_history*
|
||||
clink.log
|
3
.init.lua
Normal file
3
.init.lua
Normal file
@ -0,0 +1,3 @@
|
||||
-- The line below extends package.path with modules
|
||||
-- directory to allow to require them
|
||||
package.path = debug.getinfo(1, "S").source:match[[^@?(.*[\/])[^\/]-$]] .."modules/?.lua;".. package.path
|
126
clink_settings
Normal file
126
clink_settings
Normal file
@ -0,0 +1,126 @@
|
||||
# name: Enable automatic suggestions
|
||||
# type: boolean
|
||||
autosuggest.enable = True
|
||||
|
||||
# name: Selects default key bindings
|
||||
# type: enum
|
||||
# options: bash,windows
|
||||
clink.default_bindings = windows
|
||||
|
||||
# name: Use GUI popup windows
|
||||
# type: boolean
|
||||
clink.gui_popups = False
|
||||
|
||||
# name: Pressing Ctrl-D exits session
|
||||
# type: boolean
|
||||
cmd.ctrld_exits = False
|
||||
|
||||
# name: Argument color
|
||||
# type: color
|
||||
color.arg = bold
|
||||
|
||||
# name: Argument info color
|
||||
# type: color
|
||||
color.arginfo = sgr 1;38;5;172
|
||||
|
||||
# name: Argmatcher color
|
||||
# type: color
|
||||
color.argmatcher = sgr 1;38;5;40
|
||||
|
||||
# name: Shell command completions
|
||||
# type: color
|
||||
color.cmd = sgr 1;38;5;231
|
||||
|
||||
# name: Color for < and > redirection symbols
|
||||
# type: color
|
||||
color.cmdredir = sgr 38;5;172
|
||||
|
||||
# name: Color for & and | command separators
|
||||
# type: color
|
||||
color.cmdsep = sgr 38;5;214
|
||||
|
||||
# name: Color for comment row
|
||||
# type: color
|
||||
color.comment_row = sgr 38;5;87;48;5;18
|
||||
|
||||
# name: Description completion color
|
||||
# type: color
|
||||
color.description = sgr 38;5;39
|
||||
|
||||
# name: Doskey completions
|
||||
# type: color
|
||||
color.doskey = sgr 1;38;5;75
|
||||
|
||||
# name: Color for executable command word
|
||||
# type: color
|
||||
color.executable = sgr 38;5;33
|
||||
|
||||
# name: Filtered completion color
|
||||
# type: color
|
||||
color.filtered = sgr 38;5;231
|
||||
|
||||
# name: Flag color
|
||||
# type: color
|
||||
color.flag = sgr 38;5;117
|
||||
|
||||
# name: Hidden file completions
|
||||
# type: color
|
||||
color.hidden = sgr 38;5;160
|
||||
|
||||
# name: Horizontal scroll marker color
|
||||
# type: color
|
||||
color.horizscroll = sgr 38;5;16;48;5;30
|
||||
|
||||
# name: Input text color
|
||||
# type: color
|
||||
color.input = sgr 38;5;222
|
||||
|
||||
# name: For user-interaction prompts
|
||||
# type: color
|
||||
color.interact = bold
|
||||
|
||||
# name: Message area color
|
||||
# type: color
|
||||
color.message = default
|
||||
|
||||
# name: Readonly file completions
|
||||
# type: color
|
||||
color.readonly = sgr 38;5;28
|
||||
|
||||
# name: Selected completion color
|
||||
# type: color
|
||||
color.selected_completion = sgr 38;5;16;48;5;254
|
||||
|
||||
# name: Selection color
|
||||
# type: color
|
||||
color.selection = sgr 38;5;16;48;5;179
|
||||
|
||||
# name: Color for suggestion text
|
||||
# type: color
|
||||
color.suggestion = sgr 38;5;239
|
||||
|
||||
# name: Unexpected argument color
|
||||
# type: color
|
||||
color.unexpected = default
|
||||
|
||||
# name: Color for unrecognized command word
|
||||
# type: color
|
||||
color.unrecognized = sgr 38;5;203
|
||||
|
||||
# name: The number of history lines to save
|
||||
# type: integer
|
||||
history.max_lines = 25000
|
||||
|
||||
# name: Expand envvars when completing
|
||||
# type: boolean
|
||||
match.expand_envvars = True
|
||||
|
||||
# name: Try substring if no prefix matches
|
||||
# type: boolean
|
||||
match.substring = True
|
||||
|
||||
# name: Controls when past prompts are collapsed
|
||||
# type: enum
|
||||
# options: off,always,same_dir
|
||||
prompt.transient = off
|
||||
|
334
fzf.lua
Normal file
334
fzf.lua
Normal file
@ -0,0 +1,334 @@
|
||||
--------------------------------------------------------------------------------
|
||||
-- FZF integration for Clink.
|
||||
--
|
||||
-- Clink is available at https://chrisant996.github.io/clink
|
||||
-- FZF is available from https://nicedoc.io/junegunn/fzf
|
||||
--
|
||||
-- To use this:
|
||||
--
|
||||
-- 1. Copy this script into your Clink scripts directory.
|
||||
--
|
||||
-- 2. Either put fzf.exe in a directory listed in the system PATH environment
|
||||
-- variable, or run 'clink set fzf.exe_location <directoryname>' to tell
|
||||
-- Clink where to find fzf.exe.
|
||||
--
|
||||
-- 3. The default key bindings are as follows, when using Clink v1.2.46 or
|
||||
-- higher:
|
||||
--[[
|
||||
|
||||
# Default key bindings for fzf with Clink.
|
||||
"\C-t": "luafunc:fzf_file" # Ctrl+T lists files recursively; choose one or multiple to insert them.
|
||||
"\C-r": "luafunc:fzf_history" # Ctrl+R lists history entries; choose one to insert it.
|
||||
"\M-c": "luafunc:fzf_directory" # Alt+C lists subdirectories; choose one to 'cd /d' to it.
|
||||
"\M-b": "luafunc:fzf_bindings" # Alt+B lists key bindings; choose one to invoke it.
|
||||
"\e[27;5;32~": "luafunc:fzf_complete" # Ctrl+Space uses fzf to filter match completions.
|
||||
|
||||
]]
|
||||
-- 4. Optional: You can use your own custom key bindings if you want.
|
||||
-- Run 'clink set fzf.default_bindings false' and add key bindings to
|
||||
-- your .inputrc file manually. The default key bindings are listed
|
||||
-- above in .inputrc format for convenience.
|
||||
--
|
||||
-- 5. Optional: You can set the following environment variables to
|
||||
-- customize the behavior:
|
||||
--
|
||||
-- FZF_CTRL_T_OPTS = fzf options for fzf_file() function.
|
||||
-- FZF_CTRL_R_OPTS = fzf options for fzf_history() function.
|
||||
-- FZF_ALT_C_OPTS = fzf options for fzf_directory() function.
|
||||
-- FZF_BINDINGS_OPTS = fzf options for fzf_bindings() function.
|
||||
-- FZF_COMPLETE_OPTS = fzf options for fzf_complete() function.
|
||||
--
|
||||
-- FZF_CTRL_T_COMMAND = command to run for collecting files for fzf_file() function.
|
||||
-- FZF_ALT_C_COMMAND = command to run for collecting directories for fzf_directory() function.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Compatibility check.
|
||||
if not io.popenrw then
|
||||
print('fzf.lua requires a newer version of Clink; please upgrade.')
|
||||
return
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Settings available via 'clink set'.
|
||||
|
||||
settings.add('fzf.height', '40%', 'Height to use for the --height flag')
|
||||
settings.add('fzf.exe_location', '', 'Location of fzf.exe if not on the PATH')
|
||||
|
||||
if rl.setbinding then
|
||||
|
||||
settings.add('fzf.default_bindings', true, 'Use default key bindings', 'If the default key bindings interfere with your own, you can turn off the\ndefault key bindings and add bindings manually to your .inputrc file.\n\nChanging this takes effect for the next session.')
|
||||
|
||||
if settings.get('fzf.default_bindings') then
|
||||
rl.setbinding([["\C-t"]], [["luafunc:fzf_file"]])
|
||||
rl.setbinding([["\C-r"]], [["luafunc:fzf_history"]])
|
||||
rl.setbinding([["\M-c"]], [["luafunc:fzf_directory"]])
|
||||
rl.setbinding([["\M-b"]], [["luafunc:fzf_bindings"]])
|
||||
rl.setbinding([["\e[27;5;32~"]], [["luafunc:fzf_complete"]])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Helpers.
|
||||
|
||||
local diag = false
|
||||
local fzf_complete_intercept = false
|
||||
|
||||
local function get_fzf(env)
|
||||
local height = settings.get('fzf.height')
|
||||
local command = settings.get('fzf.exe_location')
|
||||
if os.expandenv and command then
|
||||
-- Expand so that os.getshortpathname() can work even when envvars are
|
||||
-- present.
|
||||
command = os.expandenv(command)
|
||||
end
|
||||
if not command or command == '' then
|
||||
command = 'fzf.exe'
|
||||
else
|
||||
-- CMD.exe cannot use pipe redirection with a quoted program name, so
|
||||
-- try to use a short name.
|
||||
local short = os.getshortpathname(command)
|
||||
if short then
|
||||
command = short
|
||||
end
|
||||
end
|
||||
if command and command ~= '' and height and height ~= '' then
|
||||
command = command..' --height '..height
|
||||
end
|
||||
if env then
|
||||
local options = os.getenv(env)
|
||||
if options then
|
||||
command = command..' '..options
|
||||
end
|
||||
end
|
||||
return command
|
||||
end
|
||||
|
||||
local function get_clink()
|
||||
local clink_alias = os.getalias('clink')
|
||||
if not clink_alias or clink_alias == '' then
|
||||
return ''
|
||||
end
|
||||
return clink_alias:gsub(' $[*]', '')
|
||||
end
|
||||
|
||||
local function replace_dir(str, line_state)
|
||||
local dir = '.'
|
||||
if line_state:getwordcount() > 0 then
|
||||
local info = line_state:getwordinfo(line_state:getwordcount())
|
||||
if info then
|
||||
local word = line_state:getline():sub(info.offset, line_state:getcursor())
|
||||
if word and #word > 0 then
|
||||
dir = word
|
||||
end
|
||||
end
|
||||
end
|
||||
return str:gsub('$dir', dir)
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Functions for use with 'luafunc:' key bindings.
|
||||
|
||||
function fzf_complete(rl_buffer)
|
||||
fzf_complete_intercept = true
|
||||
rl.invokecommand('complete')
|
||||
if fzf_complete_intercept then
|
||||
rl_buffer:ding()
|
||||
end
|
||||
fzf_complete_intercept = false
|
||||
rl_buffer:refreshline()
|
||||
end
|
||||
|
||||
function fzf_history(rl_buffer)
|
||||
local clink_command = get_clink()
|
||||
if #clink_command == 0 then
|
||||
rl_buffer:ding()
|
||||
return
|
||||
end
|
||||
|
||||
-- Build command to get history for the current Clink session.
|
||||
local history = clink_command..' --session '..clink.getsession()..' history --bare'
|
||||
if diag then
|
||||
history = history..' --diag'
|
||||
end
|
||||
|
||||
-- This intentionally does not use '--query' because that isn't safe:
|
||||
-- Depending on what the user has typed so far, passing it as an argument
|
||||
-- may cause the command to interpreted differently than expected.
|
||||
-- E.g. suppose the user typed: "pgm.exe & rd /s
|
||||
-- Then fzf would be invoked as: fzf.exe --query""pgm.exe & rd /s"
|
||||
-- And since the & is not inside quotes, the 'rd /s' command gets actually
|
||||
-- run by mistake!
|
||||
local r = io.popen(history..' | '..get_fzf("FZF_CTRL_R_OPTS")..' -i --tac')
|
||||
if not r then
|
||||
rl_buffer:ding()
|
||||
return
|
||||
end
|
||||
|
||||
local str = r:read('*all')
|
||||
str = str and str:gsub('[\r\n]', '') or ''
|
||||
r:close()
|
||||
|
||||
-- If something was selected, insert it.
|
||||
if #str > 0 then
|
||||
rl_buffer:beginundogroup()
|
||||
rl_buffer:remove(0, -1)
|
||||
rl_buffer:insert(str)
|
||||
rl_buffer:endundogroup()
|
||||
end
|
||||
|
||||
rl_buffer:refreshline()
|
||||
end
|
||||
|
||||
function fzf_file(rl_buffer, line_state)
|
||||
local ctrl_t_command = os.getenv('FZF_CTRL_T_COMMAND')
|
||||
if not ctrl_t_command then
|
||||
ctrl_t_command = 'dir /b /s /a:-s $dir'
|
||||
end
|
||||
|
||||
ctrl_t_command = replace_dir(ctrl_t_command, line_state)
|
||||
|
||||
print('"'..ctrl_t_command..'"')
|
||||
local r = io.popen(ctrl_t_command..' | '..get_fzf('FZF_CTRL_T_OPTS')..' -i -m')
|
||||
if not r then
|
||||
rl_buffer:ding()
|
||||
return
|
||||
end
|
||||
|
||||
local str = r:read('*line')
|
||||
str = str and str:gsub('[\r\n]+', ' ') or ''
|
||||
str = str:gsub(' +$', '')
|
||||
r:close()
|
||||
|
||||
if #str > 0 then
|
||||
rl_buffer:insert(str)
|
||||
end
|
||||
|
||||
rl_buffer:refreshline()
|
||||
end
|
||||
|
||||
function fzf_directory(rl_buffer, line_state)
|
||||
local alt_c_opts = os.getenv('FZF_ALT_C_OPTS')
|
||||
if not alt_c_opts then
|
||||
alt_c_opts = ""
|
||||
end
|
||||
|
||||
local alt_c_command = os.getenv('FZF_ALT_C_COMMAND')
|
||||
if not alt_c_command then
|
||||
alt_c_command = 'dir /b /s /a:d-s $dir'
|
||||
end
|
||||
|
||||
alt_c_command = replace_dir(alt_c_command, line_state)
|
||||
|
||||
local temp_contents = rl_buffer:getbuffer()
|
||||
local r = io.popen(alt_c_command..' | '..get_fzf('FZF_ALT_C_OPTS')..' -i')
|
||||
if not r then
|
||||
rl_buffer:ding()
|
||||
return
|
||||
end
|
||||
|
||||
local str = r:read('*all')
|
||||
str = str and str:gsub('[\r\n]', '') or ''
|
||||
r:close()
|
||||
|
||||
if #str > 0 then
|
||||
rl_buffer:beginundogroup()
|
||||
rl_buffer:remove(0, -1)
|
||||
rl_buffer:insert('cd /d '..str)
|
||||
rl_buffer:endundogroup()
|
||||
rl_buffer:refreshline()
|
||||
rl.invokecommand('accept-line')
|
||||
return
|
||||
end
|
||||
|
||||
rl_buffer:refreshline()
|
||||
end
|
||||
|
||||
function fzf_bindings(rl_buffer)
|
||||
if not rl.getkeybindings then
|
||||
rl_buffer:beginoutput()
|
||||
print('fzf_bindings() in fzf.lua requires a newer version of Clink; please upgrade.')
|
||||
return
|
||||
end
|
||||
|
||||
local bindings = rl.getkeybindings()
|
||||
if #bindings <= 0 then
|
||||
rl_buffer:refreshline()
|
||||
return
|
||||
end
|
||||
|
||||
local line
|
||||
local r,w = io.popenrw(get_fzf('FZF_BINDINGS_OPTS')..' -i')
|
||||
if r and w then
|
||||
-- Write key bindings to the write pipe.
|
||||
for _,kb in ipairs(bindings) do
|
||||
w:write(kb.key..' : '..kb.binding..'\n')
|
||||
end
|
||||
w:close()
|
||||
|
||||
-- Read filtered matches.
|
||||
local ret = {}
|
||||
line = r:read('*line')
|
||||
r:close()
|
||||
end
|
||||
|
||||
rl_buffer:refreshline()
|
||||
|
||||
if line and #line > 0 then
|
||||
local binding = line:sub(#bindings[1].key + 3 + 1)
|
||||
rl.invokecommand(binding)
|
||||
end
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Match generator.
|
||||
|
||||
local function filter_matches(matches, completion_type, filename_completion_desired)
|
||||
if not fzf_complete_intercept then
|
||||
return
|
||||
end
|
||||
|
||||
-- Start fzf.
|
||||
local r,w = io.popenrw(get_fzf("FZF_COMPLETE_OPTS"))
|
||||
if not r or not w then
|
||||
return
|
||||
end
|
||||
|
||||
-- Write matches to the write pipe.
|
||||
for _,m in ipairs(matches) do
|
||||
w:write(m.match..'\n')
|
||||
end
|
||||
w:close()
|
||||
|
||||
-- Read filtered matches.
|
||||
local ret = {}
|
||||
while (true) do
|
||||
local line = r:read('*line')
|
||||
if not line then
|
||||
break
|
||||
end
|
||||
for _,m in ipairs(matches) do
|
||||
if m.match == line then
|
||||
table.insert(ret, m)
|
||||
end
|
||||
end
|
||||
end
|
||||
r:close()
|
||||
|
||||
-- Yay, successful; clear it to not ding.
|
||||
fzf_complete_intercept = false
|
||||
return ret
|
||||
end
|
||||
|
||||
local interceptor = clink.generator(0)
|
||||
function interceptor:generate(line_state, match_builder)
|
||||
if fzf_complete_intercept then
|
||||
clink.onfiltermatches(filter_matches)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
clink.onbeginedit(function ()
|
||||
fzf_complete_intercept = false
|
||||
end)
|
||||
|
992
git.lua
Normal file
992
git.lua
Normal file
@ -0,0 +1,992 @@
|
||||
-- preamble: common routines
|
||||
|
||||
local path = require('path')
|
||||
local git = require('gitutil')
|
||||
local matchers = require('matchers')
|
||||
local w = require('tables').wrap
|
||||
local clink_version = require('clink_version')
|
||||
local color = require('color')
|
||||
local parser = clink.arg.new_parser
|
||||
|
||||
if clink_version.supports_color_settings then
|
||||
settings.add('color.git.star', 'bright green', 'Color for preferred branch completions')
|
||||
end
|
||||
|
||||
---
|
||||
-- Lists remote branches based on packed-refs file from git directory
|
||||
-- @param string [dir] Directory where to search file for
|
||||
-- @return table List of remote branches
|
||||
local function list_packed_refs(dir)
|
||||
local result = w()
|
||||
local git_dir = dir or git.get_git_common_dir()
|
||||
if not git_dir then return result end
|
||||
|
||||
local packed_refs_file = io.open(git_dir..'/packed-refs')
|
||||
if packed_refs_file == nil then return {} end
|
||||
|
||||
for line in packed_refs_file:lines() do
|
||||
-- SHA is 40 char length + 1 char for space
|
||||
if #line > 41 then
|
||||
local match = line:sub(41):match('refs/remotes/(.*)')
|
||||
if match then table.insert(result, match) end
|
||||
end
|
||||
end
|
||||
|
||||
packed_refs_file:close()
|
||||
return result
|
||||
end
|
||||
|
||||
local function list_remote_branches(dir)
|
||||
local git_dir = dir or git.get_git_common_dir()
|
||||
if not git_dir then return w() end
|
||||
|
||||
return w(path.list_files(git_dir..'/refs/remotes', '/*',
|
||||
--[[recursive=]]true, --[[reverse_separator=]]true))
|
||||
:concat(list_packed_refs(git_dir))
|
||||
:sort():dedupe()
|
||||
end
|
||||
|
||||
---
|
||||
-- Lists local branches for git repo in git_dir directory.
|
||||
--
|
||||
-- @param string [dir] Git directory, where to search for remote branches
|
||||
-- @return table List of branches.
|
||||
local function list_local_branches(dir)
|
||||
local git_dir = dir or git.get_git_common_dir()
|
||||
if not git_dir then return w() end
|
||||
|
||||
local result = w(path.list_files(git_dir..'/refs/heads', '/*',
|
||||
--[[recursive=]]true, --[[reverse_separator=]]true))
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
local branches = function (token)
|
||||
local git_dir = git.get_git_common_dir()
|
||||
if not git_dir then return w() end
|
||||
|
||||
return list_local_branches(git_dir)
|
||||
:filter(function(branch)
|
||||
return clink.is_match(token, branch)
|
||||
end)
|
||||
end
|
||||
|
||||
local function alias(token)
|
||||
local res = w()
|
||||
|
||||
-- Try to resolve .git directory location
|
||||
local git_dir = git.get_git_dir()
|
||||
|
||||
if git_dir == nil then return res end
|
||||
|
||||
local f = io.popen("git config --get-regexp alias 2>nul")
|
||||
if f == nil then return {} end
|
||||
|
||||
for line in f:lines() do
|
||||
local s = line:find(" ", 1, true)
|
||||
local alias_name = line:sub(7, s - 1)
|
||||
local start = alias_name:find(token, 1, true)
|
||||
if start and start == 1 then
|
||||
table.insert(res, alias_name)
|
||||
end
|
||||
end
|
||||
|
||||
f:close()
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
local function remotes(token) -- luacheck: no unused args
|
||||
local result = w()
|
||||
local git_dir = git.get_git_common_dir()
|
||||
if not git_dir then return result end
|
||||
|
||||
local git_config = io.open(git_dir..'/config')
|
||||
-- if there is no gitconfig file (WAT?!), return empty list
|
||||
if git_config == nil then return result end
|
||||
|
||||
for line in git_config:lines() do
|
||||
local remote = line:match('%[remote "(.*)"%]')
|
||||
if (remote) then
|
||||
table.insert(result, remote)
|
||||
end
|
||||
end
|
||||
|
||||
git_config:close()
|
||||
return result
|
||||
end
|
||||
|
||||
local function local_or_remote_branches(token)
|
||||
-- Try to resolve .git directory location
|
||||
local git_dir = git.get_git_common_dir()
|
||||
if not git_dir then return w() end
|
||||
|
||||
return list_local_branches(git_dir)
|
||||
:concat(list_remote_branches(git_dir))
|
||||
:filter(function(branch)
|
||||
return clink.is_match(token, branch)
|
||||
end)
|
||||
end
|
||||
|
||||
local function checkout_spec_generator(token)
|
||||
local files = matchers.files(token)
|
||||
:filter(function(file)
|
||||
return path.is_real_dir(file)
|
||||
end)
|
||||
|
||||
local git_dir = git.get_git_common_dir()
|
||||
|
||||
local local_branches = branches(token)
|
||||
local remote_branches = list_remote_branches(git_dir)
|
||||
:filter(function(branch)
|
||||
return clink.is_match(token, branch)
|
||||
end)
|
||||
|
||||
local predicted_branches = list_remote_branches(git_dir)
|
||||
:map(function (remote_branch)
|
||||
return remote_branch:match('.-/(.+)')
|
||||
end)
|
||||
:filter(function(branch)
|
||||
return branch
|
||||
and clink.is_match(token, branch)
|
||||
-- Filter out those predictions which are already exists as local branches
|
||||
and not local_branches:contains(branch)
|
||||
end)
|
||||
|
||||
if (#local_branches + #remote_branches + #predicted_branches) == 0 then return files end
|
||||
|
||||
-- if there is any refspec that matches token then:
|
||||
-- * disable readline's filename completion, otherwise we'll get a list of these specs
|
||||
-- threaten as list of files (without 'path' part), ie. 'some_branch' instead of 'my_remote/some_branch'
|
||||
-- * create display filter for completion table to append path separator to each directory entry
|
||||
-- since it is not added automatically by readline (see previous point)
|
||||
clink.matches_are_files(0)
|
||||
clink.match_display_filter = function ()
|
||||
local star = '*'
|
||||
if clink_version.supports_query_rl_var and rl.isvariabletrue('colored-stats') then
|
||||
star = color.get_clink_color('color.git.star')..star..color.get_clink_color('color.filtered')
|
||||
end
|
||||
return files:map(function(file)
|
||||
return clink.is_dir(file) and file..'\\' or file
|
||||
end)
|
||||
:concat(local_branches)
|
||||
:concat(predicted_branches:map(function(branch) return star..branch end))
|
||||
:concat(remote_branches)
|
||||
end
|
||||
|
||||
return files
|
||||
:concat(local_branches)
|
||||
:concat(predicted_branches)
|
||||
:concat(remote_branches)
|
||||
end
|
||||
|
||||
local function push_branch_spec(token)
|
||||
local git_dir = git.get_git_common_dir()
|
||||
if not git_dir then return w() end
|
||||
|
||||
local plus_prefix = token:sub(0, 1) == '+'
|
||||
-- cut out leading '+' symbol as it is a part of branch spec
|
||||
local branch_spec = plus_prefix and token:sub(2) or token
|
||||
-- check if there a local/remote branch separator
|
||||
local s, e = branch_spec:find(':')
|
||||
|
||||
-- starting from here we have 2 options:
|
||||
-- * if there is no branch separator complete word with local branches
|
||||
if not s then
|
||||
local b = branches(branch_spec)
|
||||
|
||||
-- setup display filter to prevent display '+' symbol in completion list
|
||||
clink.match_display_filter = function ()
|
||||
return b
|
||||
end
|
||||
|
||||
return b:map(function(branch)
|
||||
-- append '+' to results if it was specified
|
||||
return plus_prefix and '+'..branch or branch
|
||||
end)
|
||||
else
|
||||
-- * if there is ':' separator then we need to complete remote branch
|
||||
local local_branch_spec = branch_spec:sub(1, s - 1)
|
||||
local remote_branch_spec = branch_spec:sub(e + 1)
|
||||
|
||||
-- TODO: show remote branches only for remote that has been specified as previous argument
|
||||
local b = w(clink.find_dirs(git_dir..'/refs/remotes/*'))
|
||||
:filter(function(remote) return path.is_real_dir(remote) end)
|
||||
:reduce({}, function(result, remote)
|
||||
return w(path.list_files(git_dir..'/refs/remotes/'..remote, '/*',
|
||||
--[[recursive=]]true, --[[reverse_separator=]]true))
|
||||
:filter(function(remote_branch)
|
||||
return clink.is_match(remote_branch_spec, remote_branch)
|
||||
end)
|
||||
:concat(result)
|
||||
end)
|
||||
|
||||
-- setup display filter to prevent display '+' symbol in completion list
|
||||
clink.match_display_filter = function ()
|
||||
return b
|
||||
end
|
||||
|
||||
return b:map(function(branch)
|
||||
return (plus_prefix and '+'..local_branch_spec or local_branch_spec)..':'..branch
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local stashes = function(token) -- luacheck: no unused args
|
||||
|
||||
local git_dir = git.get_git_dir()
|
||||
if not git_dir then return w() end
|
||||
|
||||
local stash_file = io.open(git_dir..'/logs/refs/stash')
|
||||
-- if there is no stash file, return empty list
|
||||
if stash_file == nil then return w() end
|
||||
|
||||
local stashes = {}
|
||||
-- make a dictionary of stash time and stash comment to
|
||||
-- be able to sort stashes by date/time created
|
||||
for stash in stash_file:lines() do
|
||||
local stash_time, stash_name = stash:match('(%d%d%d%d%d%d%d%d%d%d) [+-]%d%d%d%d%s+(.*)')
|
||||
if (stash_name and stash_name) then
|
||||
stashes[stash_time] = stash_name
|
||||
end
|
||||
end
|
||||
|
||||
stash_file:close()
|
||||
|
||||
-- get times for available stashes into separate table and sort it
|
||||
-- from newest to oldest. This is required because of stash@{0}
|
||||
-- represents _latest_ stash, not the last one in file
|
||||
local stash_times = {}
|
||||
for k in pairs(stashes) do
|
||||
table.insert(stash_times, k)
|
||||
end
|
||||
|
||||
table.sort(stash_times, function (a, b)
|
||||
return a > b
|
||||
end)
|
||||
|
||||
-- generate matches and match filter table
|
||||
local ret = {}
|
||||
local ret_filter = {}
|
||||
for i,v in ipairs(stash_times) do
|
||||
local match = "stash@{"..(i-1).."}"
|
||||
table.insert(ret, match)
|
||||
if clink_version.supports_display_filter_description then
|
||||
-- Clink now has a richer match interface. By returning a table,
|
||||
-- the script is able to provide the stash name separately from the
|
||||
-- description. If the script does so, then the popup completion
|
||||
-- window is able to show the stash name plus a dimmed description,
|
||||
-- but only insert the stash name.
|
||||
table.insert(ret_filter, { match=match, type="word", description=stashes[v] })
|
||||
else
|
||||
table.insert(ret_filter, match.." "..stashes[v])
|
||||
end
|
||||
end
|
||||
|
||||
local function filter()
|
||||
return ret_filter
|
||||
end
|
||||
|
||||
if clink_version.supports_display_filter_description then
|
||||
clink.ondisplaymatches(filter)
|
||||
else
|
||||
clink.match_display_filter = filter
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
local color_opts = parser({"true", "false", "always"})
|
||||
|
||||
local git_options = {
|
||||
"core.editor",
|
||||
"core.pager",
|
||||
"core.excludesfile",
|
||||
"core.autocrlf"..parser({"true", "false", "input"}),
|
||||
"core.trustctime"..parser({"true", "false"}),
|
||||
"core.whitespace"..parser({
|
||||
"cr-at-eol",
|
||||
"-cr-at-eol",
|
||||
"indent-with-non-tab",
|
||||
"-indent-with-non-tab",
|
||||
"space-before-tab",
|
||||
"-space-before-tab",
|
||||
"trailing-space",
|
||||
"-trailing-space"
|
||||
}),
|
||||
"commit.template",
|
||||
"color.ui"..color_opts, "color.*"..color_opts, "color.branch"..color_opts,
|
||||
"color.diff"..color_opts, "color.interactive"..color_opts, "color.status"..color_opts,
|
||||
"help.autocorrect",
|
||||
"merge.tool", "mergetool.*.cmd", "mergetool.trustExitCode"..parser({"true", "false"}), "diff.external",
|
||||
"user.name", "user.email", "user.signingkey",
|
||||
}
|
||||
|
||||
local config_parser = parser(
|
||||
"--system", "--global", "--local", "--file"..parser({matchers.files}),
|
||||
"--int", "--bool", "--path",
|
||||
"-z", "--null",
|
||||
"--add",
|
||||
"--replace-all",
|
||||
"--get", "--get-all", "--get-regexp", "--get-urlmatch",
|
||||
"--unset", "--unset-all",
|
||||
"--rename-section", "--remove-section",
|
||||
"-l", "--list",
|
||||
"--get-color", "--get-colorbool",
|
||||
"-e", "--edit",
|
||||
{git_options}
|
||||
)
|
||||
|
||||
local merge_recursive_options = parser({
|
||||
"ours",
|
||||
"theirs",
|
||||
"renormalize",
|
||||
"no-renormalize",
|
||||
"diff-algorithm="..parser({
|
||||
"patience",
|
||||
"minimal",
|
||||
"histogram",
|
||||
"myers"
|
||||
}),
|
||||
"patience",
|
||||
"ignore-space-change",
|
||||
"ignore-all-space",
|
||||
"ignore-space-at-eol",
|
||||
"rename-threshold=",
|
||||
-- "subtree="..parser(),
|
||||
"subtree"
|
||||
})
|
||||
|
||||
local merge_strategies = parser({
|
||||
"resolve",
|
||||
"recursive",
|
||||
"ours",
|
||||
"octopus",
|
||||
"subtree"
|
||||
})
|
||||
|
||||
local cleanup_options = parser({
|
||||
"strip",
|
||||
"whitespace",
|
||||
"verbatim",
|
||||
"scissors",
|
||||
"default"
|
||||
})
|
||||
|
||||
local git_parser = parser(
|
||||
{
|
||||
{alias},
|
||||
"add" .. parser({matchers.files},
|
||||
"-n", "--dry-run",
|
||||
"-v", "--verbose",
|
||||
"-f", "--force",
|
||||
"-i", "--interactive",
|
||||
"-p", "--patch",
|
||||
"-e", "--edit",
|
||||
"-u", "--update",
|
||||
"-A", "--all",
|
||||
"--no-all",
|
||||
"--ignore-removal",
|
||||
"--no-ignore-removal",
|
||||
"-N", "--intent-to-add",
|
||||
"--refresh",
|
||||
"--ignore-errors",
|
||||
"--ignore-missing"
|
||||
),
|
||||
"add--interactive",
|
||||
"am",
|
||||
"annotate" .. parser({matchers.files},
|
||||
"-b",
|
||||
"--root",
|
||||
"--show-stats",
|
||||
"-L",
|
||||
"-l",
|
||||
"-t",
|
||||
"-S",
|
||||
"--reverse",
|
||||
"-p",
|
||||
"--porcelain",
|
||||
"--line-porcelain",
|
||||
"--incremental",
|
||||
"--encoding=",
|
||||
"--contents",
|
||||
"--date",
|
||||
"-M",
|
||||
"-C",
|
||||
"-h"
|
||||
),
|
||||
"apply" .. parser(
|
||||
"--stat",
|
||||
"--numstat",
|
||||
"--summary",
|
||||
"--check",
|
||||
"--index",
|
||||
"--cached",
|
||||
"-3", "--3way",
|
||||
"--build-fake-ancestor=",
|
||||
"-R", "--reverse",
|
||||
"--reject",
|
||||
"-z",
|
||||
"-p",
|
||||
"-C",
|
||||
"--unidiff-zero",
|
||||
"--apply",
|
||||
"--no-add",
|
||||
"--allow-binary-replacement", "--binary",
|
||||
"--exclude=",
|
||||
"--include=",
|
||||
"--ignore-space-change", "--ignore-whitespace",
|
||||
"--whitespace=",
|
||||
"--inaccurate-eof",
|
||||
"-v", "--verbose",
|
||||
"--recount",
|
||||
"--directory="
|
||||
),
|
||||
"archive",
|
||||
"bisect",
|
||||
"bisect--helper",
|
||||
"blame",
|
||||
"branch" .. parser(
|
||||
"-v", "--verbose",
|
||||
"-q", "--quiet",
|
||||
"-t", "--track",
|
||||
"--set-upstream",
|
||||
"-u", "--set-upstream-to",
|
||||
"--unset-upstream",
|
||||
"--color",
|
||||
"-r", "--remotes",
|
||||
"--contains" ,
|
||||
"--abbrev",
|
||||
"-a", "--all",
|
||||
"-d" .. parser({branches}):loop(1),
|
||||
"--delete" .. parser({branches}):loop(1),
|
||||
"-D" .. parser({branches}):loop(1),
|
||||
"-m", "--move",
|
||||
"-M",
|
||||
"--list",
|
||||
"-l", "--create-reflog",
|
||||
"--edit-description",
|
||||
"-f", "--force",
|
||||
"--no-merged",
|
||||
"--merged",
|
||||
"--column"
|
||||
),
|
||||
"bundle",
|
||||
"cat-file",
|
||||
"check-attr",
|
||||
"check-ignore",
|
||||
"check-mailmap",
|
||||
"check-ref-format",
|
||||
"checkout" .. parser({checkout_spec_generator},
|
||||
"-q", "--quiet",
|
||||
"-b",
|
||||
"-B",
|
||||
"-l",
|
||||
"--detach",
|
||||
"-t", "--track",
|
||||
"--orphan",
|
||||
"-2", "--ours",
|
||||
"-3", "--theirs",
|
||||
"-f", "--force",
|
||||
"-m", "--merge",
|
||||
"--overwrite-ignore",
|
||||
"--conflict",
|
||||
"-p", "--patch",
|
||||
"--ignore-skip-worktree-bits"
|
||||
),
|
||||
"checkout-index",
|
||||
"cherry",
|
||||
"cherry-pick"..parser(
|
||||
"-e", "--edit",
|
||||
"-m", "--mainline ",
|
||||
"-n", "--no-commit",
|
||||
"-r",
|
||||
"-x",
|
||||
"--ff",
|
||||
"-s", "-S", "--gpg-sign",
|
||||
"--allow-empty",
|
||||
"--allow-empty-message",
|
||||
"--keep-redundant-commits",
|
||||
"--strategy"..parser({merge_strategies}),
|
||||
"-X"..parser({merge_recursive_options}),
|
||||
"--strategy-option"..parser({merge_recursive_options}),
|
||||
"--continue",
|
||||
"--quit",
|
||||
"--abort"
|
||||
),
|
||||
"citool",
|
||||
"clean",
|
||||
"clone" .. parser(
|
||||
"--template",
|
||||
"-l", "--local",
|
||||
"-s", "--shared",
|
||||
"--no-hardlinks",
|
||||
"-q", "--quiet",
|
||||
"-n", "--no-checkout",
|
||||
"--bare",
|
||||
"--mirror",
|
||||
"-o", "--origin",
|
||||
"-b", "--branch",
|
||||
"-u", "--upload-pack",
|
||||
"--reference",
|
||||
"--dissociate",
|
||||
"--separate-git-dir",
|
||||
"--depth",
|
||||
"--single-branch", "--no-single-branch",
|
||||
"--no-tags",
|
||||
"--recurse-submodules", "--shallow-submodules", "--no-shallow-submodules",
|
||||
"--jobs"
|
||||
),
|
||||
"column",
|
||||
"commit" .. parser(
|
||||
"-a", "--all",
|
||||
"-p", "--patch",
|
||||
"-C", "--reuse-message=",
|
||||
"-c", "--reedit-message=",
|
||||
"--fixup=",
|
||||
"--squash=",
|
||||
"--reset-author",
|
||||
"--short",
|
||||
"--branch",
|
||||
"--porcelain",
|
||||
"--long",
|
||||
"-z",
|
||||
"--null",
|
||||
"-F", "--file=",
|
||||
"--author=",
|
||||
"--date=",
|
||||
"-m", "--message=",
|
||||
"-t", "--template=",
|
||||
"-s", "--signoff",
|
||||
"-n", "--no-verify",
|
||||
"--allow-empty",
|
||||
"--allow-empty-message",
|
||||
"--cleanup"..cleanup_options,
|
||||
"-e", "--edit",
|
||||
"--no-edit",
|
||||
"--amend",
|
||||
"--no-post-rewrite",
|
||||
"-i", "--include",
|
||||
"-o", "--only",
|
||||
"-u", "--untracked-files", "--untracked-files=", -- .. parser({"no", "normal", "all"}),
|
||||
"-v", "--verbose",
|
||||
"-q", "--quiet",
|
||||
"--dry-run",
|
||||
"--status",
|
||||
"--no-status",
|
||||
"-S", "--gpg-sign", "--gpg-sign=",
|
||||
"--"
|
||||
),
|
||||
"commit-tree",
|
||||
"config"..config_parser,
|
||||
"count-objects",
|
||||
"credential",
|
||||
"credential-store",
|
||||
"credential-wincred",
|
||||
"daemon",
|
||||
"describe",
|
||||
"diff" .. parser({local_or_remote_branches, matchers.files}),
|
||||
"diff-files",
|
||||
"diff-index",
|
||||
"diff-tree",
|
||||
"difftool"..parser(
|
||||
"-d", "--dir-diff",
|
||||
"-y", "--no-prompt", "--prompt",
|
||||
"-t", "--tool=" -- TODO: complete tool (take from config)
|
||||
),
|
||||
"difftool--helper",
|
||||
"fast-export",
|
||||
"fast-import",
|
||||
"fetch" .. parser({remotes},
|
||||
"--all",
|
||||
"--prune",
|
||||
"--tags"
|
||||
),
|
||||
"fetch-pack",
|
||||
"filter-branch",
|
||||
"fmt-merge-msg",
|
||||
"for-each-ref",
|
||||
"format-patch",
|
||||
"fsck",
|
||||
"fsck-objects",
|
||||
"gc",
|
||||
"get-tar-commit-id",
|
||||
"grep",
|
||||
"gui",
|
||||
"gui--askpass",
|
||||
"gui--askyesno",
|
||||
"gui.tcl",
|
||||
"hash-object",
|
||||
"help",
|
||||
"http-backend",
|
||||
"http-fetch",
|
||||
"http-push",
|
||||
"imap-send",
|
||||
"index-pack",
|
||||
"init",
|
||||
"init-db",
|
||||
"log",
|
||||
"lost-found",
|
||||
"ls-files",
|
||||
"ls-remote",
|
||||
"ls-tree",
|
||||
"mailinfo",
|
||||
"mailsplit",
|
||||
"merge" .. parser({branches},
|
||||
"--commit", "--no-commit",
|
||||
"--edit", "-e", "--no-edit",
|
||||
"--ff", "--no-ff", "--ff-only",
|
||||
"--log", "--no-log",
|
||||
"--stat", "-n", "--no-stat",
|
||||
"--squash", "--no-squash",
|
||||
"-s" .. merge_strategies,
|
||||
"--strategy" .. merge_strategies,
|
||||
"-X" .. merge_recursive_options,
|
||||
"--strategy-option" .. merge_recursive_options,
|
||||
"--verify-signatures", "--no-verify-signatures",
|
||||
"-q", "--quiet", "-v", "--verbose",
|
||||
"--progress", "--no-progress",
|
||||
"-S", "--gpg-sign",
|
||||
"-m",
|
||||
"--rerere-autoupdate", "--no-rerere-autoupdate",
|
||||
"--abort"
|
||||
),
|
||||
"merge-base",
|
||||
"merge-file",
|
||||
"merge-index",
|
||||
"merge-octopus",
|
||||
"merge-one-file",
|
||||
"merge-ours",
|
||||
"merge-recursive",
|
||||
"merge-resolve",
|
||||
"merge-subtree",
|
||||
"merge-tree",
|
||||
"mergetool",
|
||||
"mergetool--lib",
|
||||
"mktag",
|
||||
"mktree",
|
||||
"mv",
|
||||
"name-rev",
|
||||
"notes",
|
||||
"p4",
|
||||
"pack-objects",
|
||||
"pack-redundant",
|
||||
"pack-refs",
|
||||
"parse-remote",
|
||||
"patch-id",
|
||||
"peek-remote",
|
||||
"prune",
|
||||
"prune-packed",
|
||||
"pull" .. parser(
|
||||
{remotes}, {branches},
|
||||
"-q", "--quiet",
|
||||
"-v", "--verbose",
|
||||
"--recurse-submodules", --[no-]recurse-submodules[=yes|on-demand|no]
|
||||
"--no-recurse-submodules",
|
||||
"--commit", "--no-commit",
|
||||
"-e", "--edit", "--no-edit",
|
||||
"--ff", "--no-ff", "--ff-only",
|
||||
"--log", "--no-log",
|
||||
"--stat", "-n", "--no-stat",
|
||||
"--squash", "--no-squash",
|
||||
"-s"..merge_strategies,
|
||||
"--strategy"..merge_strategies,
|
||||
"-X"..merge_recursive_options,
|
||||
"--strategy-option"..merge_recursive_options,
|
||||
"--verify-signatures", "--no-verify-signatures",
|
||||
"--summary", "--no-summary",
|
||||
"-r", "--rebase", "--no-rebase",
|
||||
"--all",
|
||||
"-a", "--append",
|
||||
"--depth", "--unshallow", "--update-shallow",
|
||||
"-f", "--force",
|
||||
"-k", "--keep",
|
||||
"--no-tags",
|
||||
"-u", "--update-head-ok",
|
||||
"--upload-pack",
|
||||
"--progress"
|
||||
),
|
||||
"push" .. parser(
|
||||
{remotes},
|
||||
{push_branch_spec},
|
||||
"-v", "--verbose",
|
||||
"-q", "--quiet",
|
||||
"--repo",
|
||||
"--all",
|
||||
"--mirror",
|
||||
"--delete",
|
||||
"--tags",
|
||||
"-n", "--dry-run",
|
||||
"--porcelain",
|
||||
"-f", "--force",
|
||||
"--force-with-lease",
|
||||
"--recurse-submodules",
|
||||
"--thin",
|
||||
"--receive-pack",
|
||||
"--exec",
|
||||
"-u", "--set-upstream",
|
||||
"--progress",
|
||||
"--prune",
|
||||
"--no-verify",
|
||||
"--follow-tags"
|
||||
),
|
||||
"quiltimport",
|
||||
"read-tree",
|
||||
"rebase" .. parser({local_or_remote_branches}, {branches},
|
||||
"-i", "--interactive",
|
||||
"--onto" .. parser({branches}),
|
||||
"--continue",
|
||||
"--abort",
|
||||
"--keep-empty",
|
||||
"--skip",
|
||||
"--edit-todo",
|
||||
"-m", "--merge",
|
||||
"-s" .. merge_strategies,
|
||||
"--strategy"..merge_strategies,
|
||||
"-X" .. merge_recursive_options,
|
||||
"--strategy-option"..merge_recursive_options,
|
||||
"-S", "--gpg-sign",
|
||||
"-q", "--quiet",
|
||||
"-v", "--verbose",
|
||||
"--stat", "-n", "--no-stat",
|
||||
"--no-verify", "--verify",
|
||||
"-C",
|
||||
"-f", "--force-rebase",
|
||||
"--fork-point", "--no-fork-point",
|
||||
"--ignore-whitespace", "--whitespace",
|
||||
"--committer-date-is-author-date", "--ignore-date",
|
||||
"-i", "--interactive",
|
||||
"-p", "--preserve-merges",
|
||||
"-x", "--exec",
|
||||
"--root",
|
||||
"--autosquash", "--no-autosquash",
|
||||
"--autostash", "--no-autostash",
|
||||
"--no-ff"
|
||||
),
|
||||
"receive-pack",
|
||||
"reflog",
|
||||
"remote"..parser({
|
||||
"add" ..parser(
|
||||
"-t"..parser({branches}),
|
||||
"-m",
|
||||
"-f",
|
||||
"--mirror",
|
||||
"--tags", "--no-tags"
|
||||
),
|
||||
"rename"..parser({remotes}),
|
||||
"remove"..parser({remotes}),
|
||||
"rm"..parser({remotes}),
|
||||
"set-head"..parser({remotes}, {branches},
|
||||
"-a", "--auto",
|
||||
"-d", "--delete"
|
||||
),
|
||||
"set-branches"..parser("--add", {remotes}, {branches}),
|
||||
"set-url"..parser(
|
||||
"--add"..parser("--push", {remotes}),
|
||||
"--delete"..parser("--push", {remotes})
|
||||
),
|
||||
"get-url"..parser({remotes}, "--push", "--all"),
|
||||
"show"..parser("-n", {remotes}),
|
||||
"prune"..parser("-n", "--dry-run", {remotes}),
|
||||
"update"..parser({remotes}, "-p", "--prune")
|
||||
}, "-v", "--verbose"),
|
||||
"remote-ext",
|
||||
"remote-fd",
|
||||
"remote-ftp",
|
||||
"remote-ftps",
|
||||
"remote-hg",
|
||||
"remote-http",
|
||||
"remote-https",
|
||||
"remote-testsvn",
|
||||
"repack",
|
||||
"replace",
|
||||
"repo-config",
|
||||
"request-pull",
|
||||
"rerere",
|
||||
-- TODO: Add commit completions
|
||||
"reset"..parser({local_or_remote_branches},
|
||||
"-q",
|
||||
"-p", "--patch",
|
||||
"--soft", "--mixed", "--hard",
|
||||
"--merge", "--keep"
|
||||
),
|
||||
"restore"..parser({matchers.files},
|
||||
"-s", "--source",
|
||||
"-p", "--patch",
|
||||
"-W", "--worktree",
|
||||
"-S", "--staged",
|
||||
"-q", "--quiet",
|
||||
"--progress", "--no-progress",
|
||||
"--ours", "--theirs",
|
||||
"-m", "--merge",
|
||||
"--conflict",
|
||||
"--ignore-unmerged",
|
||||
"--ignore-skip-worktree-bits",
|
||||
"--overlay", "--no-overlay"
|
||||
),
|
||||
"rev-list",
|
||||
"rev-parse",
|
||||
"revert"..parser(
|
||||
"-e", "--edit",
|
||||
"-m", "--mainline",
|
||||
"--no-edit",
|
||||
"--cleanup"..cleanup_options,
|
||||
"-n", "--no-commit",
|
||||
"-S", "--gpg-sign",
|
||||
"--no-gpg-sign",
|
||||
"-s", "--signoff",
|
||||
"--strategy"..merge_strategies,
|
||||
"-X"..merge_recursive_options,
|
||||
"--strategy-option"..merge_recursive_options,
|
||||
"--rerere-autoupdate",
|
||||
"--no-rerere-autoupdate",
|
||||
"--continue",
|
||||
"--skip",
|
||||
"--quit",
|
||||
"--abort"
|
||||
),
|
||||
"rm",
|
||||
"send-email",
|
||||
"send-pack",
|
||||
"sh-i18n",
|
||||
"sh-i18n--envsubst",
|
||||
"sh-setup",
|
||||
"shortlog",
|
||||
"show",
|
||||
"show-branch",
|
||||
"show-index",
|
||||
"show-ref",
|
||||
"stage",
|
||||
"stash"..parser({
|
||||
"list", -- TODO: The command takes options applicable to the git log
|
||||
-- command to control what is shown and how it's done
|
||||
"show"..parser({stashes}),
|
||||
"drop"..parser({stashes}, "-q", "--quiet"),
|
||||
"pop"..parser({stashes}, "--index", "-q", "--quiet"),
|
||||
"apply"..parser({stashes}, "--index", "-q", "--quiet"),
|
||||
"branch"..parser({branches}, {stashes}),
|
||||
"save"..parser(
|
||||
"-p", "--patch",
|
||||
"-k", "--no-keep-index", "--keep-index",
|
||||
"-q", "--quiet",
|
||||
"-u", "--include-untracked",
|
||||
"-a", "--all"
|
||||
),
|
||||
"clear"
|
||||
}),
|
||||
"status",
|
||||
"stripspace",
|
||||
"submodule"..parser({
|
||||
"add",
|
||||
"init",
|
||||
"deinit",
|
||||
"foreach",
|
||||
"status"..parser("--cached", "--recursive"),
|
||||
"summary",
|
||||
"sync",
|
||||
"update"
|
||||
}, '--quiet'),
|
||||
"subtree",
|
||||
"switch"..parser({local_or_remote_branches},
|
||||
"-c", "-C", "--create",
|
||||
"--force-create",
|
||||
"-d", "--detach",
|
||||
"--guess", "--no-guess",
|
||||
"-f", "--force", "--discard-changes",
|
||||
"-m", "--merge",
|
||||
"--conflict",
|
||||
"-q", "--quiet",
|
||||
"--progress", "--no-progress",
|
||||
"-t", "--track",
|
||||
"--no-track",
|
||||
"--orphan",
|
||||
"--ignore-other-worktrees",
|
||||
"--recurse-submodules", "--no-recurse-submodules"
|
||||
),
|
||||
"svn"..parser({
|
||||
"init"..parser("-T", "--trunk", "-t", "--tags", "-b", "--branches", "-s", "--stdlayout",
|
||||
"--no-metadata", "--use-svm-props", "--use-svnsync-props", "--rewrite-root",
|
||||
"--rewrite-uuid", "--username", "--prefix"..parser({"origin"}), "--ignore-paths",
|
||||
"--include-paths", "--no-minimize-url"),
|
||||
"fetch"..parser({remotes}, "--localtime", "--parent", "--ignore-paths", "--include-paths",
|
||||
"--log-window-size"),
|
||||
"clone"..parser("-T", "--trunk", "-t", "--tags", "-b", "--branches", "-s", "--stdlayout",
|
||||
"--no-metadata", "--use-svm-props", "--use-svnsync-props", "--rewrite-root",
|
||||
"--rewrite-uuid", "--username", "--prefix"..parser({"origin"}), "--ignore-paths",
|
||||
"--include-paths", "--no-minimize-url", "--preserve-empty-dirs",
|
||||
"--placeholder-filename"),
|
||||
"rebase"..parser({local_or_remote_branches}, {branches}),
|
||||
"dcommit"..parser("--no-rebase", "--commit-url", "--mergeinfo", "--interactive"),
|
||||
"branch"..parser("-m","--message","-t", "--tags", "-d", "--destination",
|
||||
"--username", "--commit-url", "--parents"),
|
||||
"log"..parser("-r", "--revision", "-v", "--verbose", "--limit",
|
||||
"--incremental", "--show-commit", "--oneline"),
|
||||
"find-rev"..parser("--before", "--after"),
|
||||
"reset"..parser("-r", "--revision", "-p", "--parent"),
|
||||
"tag",
|
||||
"blame",
|
||||
"set-tree",
|
||||
"create-ignore",
|
||||
"show-ignore",
|
||||
"mkdirs",
|
||||
"commit-diff",
|
||||
"info",
|
||||
"proplist",
|
||||
"propget",
|
||||
"show-externals",
|
||||
"gc"
|
||||
}),
|
||||
"symbolic-ref",
|
||||
"tag",
|
||||
"tar-tree",
|
||||
"unpack-file",
|
||||
"unpack-objects",
|
||||
"update-index",
|
||||
"update-ref",
|
||||
"update-server-info",
|
||||
"upload-archive",
|
||||
"upload-pack",
|
||||
"var",
|
||||
"verify-pack",
|
||||
"verify-tag",
|
||||
"web--browse",
|
||||
"whatchanged",
|
||||
"worktree"..parser({
|
||||
"add"..parser(
|
||||
{matchers.dirs},
|
||||
{branches},
|
||||
"-f", "--force",
|
||||
"--detach",
|
||||
"--checkout",
|
||||
"--lock",
|
||||
"-b"..parser({branches})
|
||||
),
|
||||
"list"..parser("--porcelain"),
|
||||
"lock"..parser("--reason"),
|
||||
"move",
|
||||
"prune"..parser(
|
||||
"-n", "--dry-run",
|
||||
"-v", "--verbose",
|
||||
"--expire"
|
||||
),
|
||||
"remove"..parser("-f"),
|
||||
"unlock"
|
||||
}),
|
||||
"write-tree",
|
||||
},
|
||||
"--version",
|
||||
"--help",
|
||||
"-c",
|
||||
"--exec-path",
|
||||
"--html-path",
|
||||
"--man-path",
|
||||
"--info-path",
|
||||
"-p", "--paginate", "--no-pager",
|
||||
"--no-replace-objects",
|
||||
"--bare",
|
||||
"--git-dir=",
|
||||
"--work-tree=",
|
||||
"--namespace="
|
||||
)
|
||||
|
||||
clink.arg.register_parser("git", git_parser)
|
1724
modules/JSON.lua
Normal file
1724
modules/JSON.lua
Normal file
File diff suppressed because it is too large
Load Diff
13
modules/clink_version.lua
Normal file
13
modules/clink_version.lua
Normal file
@ -0,0 +1,13 @@
|
||||
local exports = {}
|
||||
|
||||
-- Busted runs these modules scripts *outside* of Clink.
|
||||
-- So these Clink scripts have to work without any Clink APIs being available.
|
||||
clink = clink or {}
|
||||
|
||||
local clink_version_encoded = clink.version_encoded or 0
|
||||
|
||||
exports.supports_display_filter_description = (clink_version_encoded >= 10010012)
|
||||
exports.supports_color_settings = (clink_version_encoded >= 10010009)
|
||||
exports.supports_query_rl_var = (clink_version_encoded >= 10010009)
|
||||
|
||||
return exports
|
41
modules/color.lua
Normal file
41
modules/color.lua
Normal file
@ -0,0 +1,41 @@
|
||||
local clink_version = require('clink_version')
|
||||
|
||||
local exports = {}
|
||||
|
||||
exports.BLACK = 0
|
||||
exports.RED = 1
|
||||
exports.GREEN = 2
|
||||
exports.YELLOW = 3
|
||||
exports.BLUE = 4
|
||||
exports.MAGENTA = 5
|
||||
exports.CYAN = 6
|
||||
exports.WHITE = 7
|
||||
exports.DEFAULT = 9
|
||||
exports.BOLD = 1
|
||||
|
||||
exports.set_color = function (fore, back, bold)
|
||||
local err_message = "All arguments must be either nil or numbers between 0-9"
|
||||
assert(fore == nil or (type(fore) == "number" and fore >= 0 and fore <=9), err_message)
|
||||
assert(back == nil or (type(back) == "number" and back >= 0 and back <=9), err_message)
|
||||
|
||||
fore = fore or exports.DEFAULT
|
||||
back = back or exports.DEFAULT
|
||||
bold = bold and exports.BOLD or 22
|
||||
|
||||
return "\x1b[3"..fore..";"..bold..";".."4"..back.."m"
|
||||
end
|
||||
|
||||
exports.get_clink_color = function (setting_name)
|
||||
-- Clink's settings.get() returns SGR parameters for a CSI SGR escape code.
|
||||
local sgr = clink_version.supports_color_settings and settings.get(setting_name) or ""
|
||||
if sgr ~= "" then
|
||||
sgr = "\x1b["..sgr.."m"
|
||||
end
|
||||
return sgr
|
||||
end
|
||||
|
||||
exports.color_text = function (text, fore, back, bold)
|
||||
return exports.set_color(fore, back, bold)..text..exports.set_color()
|
||||
end
|
||||
|
||||
return exports
|
100
modules/funclib.lua
Normal file
100
modules/funclib.lua
Normal file
@ -0,0 +1,100 @@
|
||||
|
||||
local exports = {}
|
||||
|
||||
--- Implementation of table.filter function. Applies filter function to each
|
||||
-- element of table and returns a new table with values for which filter
|
||||
-- returns 'true'.
|
||||
--
|
||||
-- @param tbl a table to filter. Default is an empty table.
|
||||
-- @param filter function that accepts an element of table, specified in the
|
||||
-- first argument and returns either 'true' or 'false'. If not specified,
|
||||
-- then default function is used that returns its argument.
|
||||
--
|
||||
-- @return a new table with values that are not filtered out by 'filter' function.
|
||||
exports.filter = function (tbl, filter)
|
||||
if not tbl then return {} end
|
||||
if not filter then filter = function(v) return v end end
|
||||
local ret = {}
|
||||
for _,v in ipairs(tbl) do
|
||||
if filter(v) then table.insert(ret, v) end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
--- Implementation of table.map function. Applies filter function to each
|
||||
-- element of table and returns a new table with values returned by mapper
|
||||
-- function.
|
||||
--
|
||||
-- @param tbl a table to filter. Default is an empty table.
|
||||
-- @param map_func function that accepts an element of table, specified in the
|
||||
-- first argument and returns a new value for resultant table. If not
|
||||
-- specified, then 'map' function returns it input table.
|
||||
--
|
||||
-- @return a new table with values produced by 'map_func'.
|
||||
exports.map = function (tbl, map_func)
|
||||
assert(tbl == nil or type(tbl) == "table",
|
||||
"First argument must be either table or nil")
|
||||
|
||||
assert(map_func == nil or type(map_func) == "function",
|
||||
"Second argument must be either function or nil")
|
||||
|
||||
if tbl == nil then return {} end
|
||||
if not map_func then return tbl end
|
||||
local ret = {}
|
||||
for _,v in ipairs(tbl) do
|
||||
table.insert(ret, map_func(v))
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
--- Implementation of table.reduce function. Iterates through table and calls
|
||||
-- 'func' function passing an accumulator and an entry from the original
|
||||
-- table. The result of table is stored in accumulator and passed to next
|
||||
-- 'func' call.
|
||||
--
|
||||
-- @param accum an accumulator, initial value that will be passed to first
|
||||
-- 'func' call.
|
||||
-- @param tbl a table to reduce. Default is an empty table.
|
||||
-- @param func function that accepts two params: an accumulator and an element
|
||||
-- of table, specified in the first argument and returns a new value for
|
||||
-- accumulator.
|
||||
--
|
||||
-- @return a resultant accumulator value.
|
||||
exports.reduce = function (accum, tbl, func)
|
||||
assert(type(func) == "function",
|
||||
"Third argument must be a function")
|
||||
|
||||
if not tbl then return accum end
|
||||
for _,v in ipairs(tbl) do
|
||||
accum = func(accum, v)
|
||||
end
|
||||
return accum
|
||||
end
|
||||
|
||||
--- Concatenates any number of input values into one table. If input parameter is
|
||||
-- a table then its values is copied to the end of resultant table. If the
|
||||
-- parameter is single value, then it is appended to the resultant table. If
|
||||
-- the input value is 'nil', then it is omitted.
|
||||
--
|
||||
-- @return a result of concatenation. The result is always a table.
|
||||
exports.concat = function (...)
|
||||
local input = {...}
|
||||
local ret = {}
|
||||
local i = 1
|
||||
|
||||
while i <= #input do
|
||||
local arg = input[i]
|
||||
if type(arg) == 'table' then
|
||||
for _,v in ipairs(arg) do
|
||||
table.insert(ret, v)
|
||||
end
|
||||
elseif arg ~= nil then
|
||||
table.insert(ret, arg)
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
return exports
|
83
modules/gitutil.lua
Normal file
83
modules/gitutil.lua
Normal file
@ -0,0 +1,83 @@
|
||||
local path = require('path')
|
||||
|
||||
local exports = {}
|
||||
|
||||
---
|
||||
-- Resolves closest .git directory location.
|
||||
-- Navigates subsequently up one level and tries to find .git directory
|
||||
-- @param {string} path Path to directory will be checked. If not provided
|
||||
-- current directory will be used
|
||||
-- @return {string} Path to .git directory or nil if such dir not found
|
||||
exports.get_git_dir = function (start_dir)
|
||||
|
||||
-- Checks if provided directory contains '.git' directory
|
||||
-- and returns path to that directory
|
||||
local function has_git_dir(dir)
|
||||
return #clink.find_dirs(dir..'/.git') > 0 and dir..'/.git'
|
||||
end
|
||||
|
||||
-- checks if directory contains '.git' _file_ and if it does
|
||||
-- parses it and returns a path to git directory from that file
|
||||
local function has_git_file(dir)
|
||||
local gitfile = io.open(dir..'/.git')
|
||||
if not gitfile then return false end
|
||||
|
||||
local git_dir = gitfile:read():match('gitdir: (.*)')
|
||||
gitfile:close()
|
||||
|
||||
if not git_dir then return false end
|
||||
-- If found path is absolute don't prepend initial
|
||||
-- directory - return absolute path value
|
||||
return path.is_absolute(git_dir) and git_dir
|
||||
or dir..'/'..git_dir
|
||||
end
|
||||
|
||||
-- Set default path to current directory
|
||||
if not start_dir or start_dir == '.' then start_dir = clink.get_cwd() end
|
||||
|
||||
-- Calculate parent path now otherwise we won't be
|
||||
-- able to do that inside of logical operator
|
||||
local parent_path = path.pathname(start_dir)
|
||||
|
||||
return has_git_dir(start_dir)
|
||||
or has_git_file(start_dir)
|
||||
-- Otherwise go up one level and make a recursive call
|
||||
or (parent_path ~= start_dir and exports.get_git_dir(parent_path) or nil)
|
||||
end
|
||||
|
||||
exports.get_git_common_dir = function (start_dir)
|
||||
local git_dir = exports.get_git_dir(start_dir)
|
||||
if not git_dir then return git_dir end
|
||||
local commondirfile = io.open(git_dir..'/commondir')
|
||||
if commondirfile then
|
||||
-- If there's a commondir file, we're in a git worktree
|
||||
local commondir = commondirfile:read()
|
||||
commondirfile.close()
|
||||
return path.is_absolute(commondir) and commondir
|
||||
or git_dir..'/'..commondir
|
||||
end
|
||||
return git_dir
|
||||
end
|
||||
|
||||
---
|
||||
-- Find out current branch
|
||||
-- @return {nil|git branch name}
|
||||
---
|
||||
exports.get_git_branch = function (dir)
|
||||
local git_dir = dir or exports.get_git_dir()
|
||||
|
||||
-- If git directory not found then we're probably outside of repo
|
||||
-- or something went wrong. The same is when head_file is nil
|
||||
local head_file = git_dir and io.open(git_dir..'/HEAD')
|
||||
if not head_file then return end
|
||||
|
||||
local HEAD = head_file:read()
|
||||
head_file:close()
|
||||
|
||||
-- if HEAD matches branch expression, then we're on named branch
|
||||
-- otherwise it is a detached commit
|
||||
local branch_name = HEAD:match('ref: refs/heads/(.+)')
|
||||
return branch_name or 'HEAD detached at '..HEAD:sub(1, 7)
|
||||
end
|
||||
|
||||
return exports
|
80
modules/matchers.lua
Normal file
80
modules/matchers.lua
Normal file
@ -0,0 +1,80 @@
|
||||
|
||||
local exports = {}
|
||||
|
||||
local path = require('path')
|
||||
local w = require('tables').wrap
|
||||
|
||||
exports.dirs = function(word)
|
||||
-- Strip off any path components that may be on text.
|
||||
local prefix = ""
|
||||
local i = word:find("[\\/:][^\\/:]*$")
|
||||
if i then
|
||||
prefix = word:sub(1, i)
|
||||
end
|
||||
local include_dots = word:find("%.+$") ~= nil
|
||||
|
||||
-- Find matches.
|
||||
local matches = w(clink.find_dirs(word.."*", true))
|
||||
:filter(function (dir)
|
||||
return clink.is_match(word, prefix..dir) and
|
||||
(include_dots or path.is_real_dir(dir))
|
||||
end)
|
||||
:map(function(dir)
|
||||
return prefix..dir
|
||||
end)
|
||||
|
||||
-- If there was no matches but word is a dir then use it as the single match.
|
||||
-- Otherwise tell readline that matches are files and it will do magic.
|
||||
if #matches == 0 and clink.is_dir(rl_state.text) then
|
||||
return {rl_state.text}
|
||||
end
|
||||
|
||||
clink.matches_are_files()
|
||||
return matches
|
||||
end
|
||||
|
||||
exports.files = function (word)
|
||||
-- Strip off any path components that may be on text.
|
||||
local prefix = ""
|
||||
local i = word:find("[\\/:][^\\/:]*$")
|
||||
if i then
|
||||
prefix = word:sub(1, i)
|
||||
end
|
||||
|
||||
-- Find matches.
|
||||
local matches = w(clink.find_files(word.."*", true))
|
||||
:filter(function (file)
|
||||
return clink.is_match(word, prefix..file)
|
||||
end)
|
||||
:map(function(file)
|
||||
return prefix..file
|
||||
end)
|
||||
|
||||
-- Tell readline that matches are files and it will do magic.
|
||||
if #matches ~= 0 then
|
||||
clink.matches_are_files()
|
||||
end
|
||||
|
||||
return matches
|
||||
end
|
||||
|
||||
exports.create_dirs_matcher = function (dir_pattern, show_dotfiles)
|
||||
return function (token)
|
||||
return w(clink.find_dirs(dir_pattern))
|
||||
:filter(function(dir)
|
||||
return clink.is_match(token, dir) and (path.is_real_dir(dir) or show_dotfiles)
|
||||
end )
|
||||
end
|
||||
end
|
||||
|
||||
exports.create_files_matcher = function (file_pattern)
|
||||
return function (token)
|
||||
return w(clink.find_files(file_pattern))
|
||||
:filter(function(file)
|
||||
-- Filter out '.' and '..' entries as well
|
||||
return clink.is_match(token, file) and path.is_real_dir(file)
|
||||
end )
|
||||
end
|
||||
end
|
||||
|
||||
return exports
|
69
modules/path.lua
Normal file
69
modules/path.lua
Normal file
@ -0,0 +1,69 @@
|
||||
local exports = {}
|
||||
|
||||
local w = require('tables').wrap
|
||||
|
||||
exports.list_files = function (base_path, glob, recursive, reverse_separator)
|
||||
local mask = glob or '/*'
|
||||
|
||||
local entries = w(clink.find_files(base_path..mask))
|
||||
:filter(function(entry)
|
||||
return exports.is_real_dir(entry)
|
||||
end)
|
||||
|
||||
local files = entries:filter(function(entry)
|
||||
return not clink.is_dir(base_path..'/'..entry)
|
||||
end)
|
||||
|
||||
-- if 'recursive' flag is not set, we don't need to iterate
|
||||
-- through directories, so just return files found
|
||||
if not recursive then return files end
|
||||
|
||||
local sep = reverse_separator and '/' or '\\'
|
||||
|
||||
return entries
|
||||
:filter(function(entry)
|
||||
return clink.is_dir(base_path..'/'..entry)
|
||||
end)
|
||||
:reduce(files, function(accum, dir)
|
||||
-- iterate through directories and call list_files recursively
|
||||
return exports.list_files(base_path..'/'..dir, mask, recursive, reverse_separator)
|
||||
:map(function(entry)
|
||||
return dir..sep..entry
|
||||
end)
|
||||
:concat(accum)
|
||||
end)
|
||||
end
|
||||
|
||||
exports.basename = function (path)
|
||||
local prefix = path
|
||||
local i = path:find("[\\/:][^\\/:]*$")
|
||||
if i then
|
||||
prefix = path:sub(i + 1)
|
||||
end
|
||||
return prefix
|
||||
end
|
||||
|
||||
exports.pathname = function (path)
|
||||
local prefix = ""
|
||||
local i = path:find("[\\/:][^\\/:]*$")
|
||||
if i then
|
||||
prefix = path:sub(1, i-1)
|
||||
end
|
||||
return prefix
|
||||
end
|
||||
|
||||
exports.is_absolute = function (path)
|
||||
local drive = path:find("^%s?[%l%a]:[\\/]")
|
||||
if drive then return true else return false end
|
||||
end
|
||||
|
||||
exports.is_metadir = function (dirname)
|
||||
return exports.basename(dirname) == '.'
|
||||
or exports.basename(dirname) == '..'
|
||||
end
|
||||
|
||||
exports.is_real_dir = function (dirname)
|
||||
return not exports.is_metadir(dirname)
|
||||
end
|
||||
|
||||
return exports
|
74
modules/tables.lua
Normal file
74
modules/tables.lua
Normal file
@ -0,0 +1,74 @@
|
||||
local concat = require('funclib').concat
|
||||
local filter = require('funclib').filter
|
||||
local map = require('funclib').map
|
||||
local reduce = require('funclib').reduce
|
||||
|
||||
local exports = {}
|
||||
|
||||
local wrap_filter = function (tbl, filter_func)
|
||||
return exports.wrap(filter(tbl, filter_func))
|
||||
end
|
||||
|
||||
local wrap_map = function (tbl, map_func)
|
||||
return exports.wrap(map(tbl, map_func))
|
||||
end
|
||||
|
||||
local wrap_reduce = function (tbl, accum, reduce_func)
|
||||
local res = reduce(accum, tbl, reduce_func)
|
||||
return (type(res) == "table" and exports.wrap(res) or res)
|
||||
end
|
||||
|
||||
local wrap_concat = function (tbl, ...)
|
||||
return exports.wrap(concat(tbl, ...))
|
||||
end
|
||||
|
||||
local wrap_print = function (tbl)
|
||||
return exports.wrap(filter(tbl, function (item)
|
||||
print(item)
|
||||
return true
|
||||
end))
|
||||
end
|
||||
|
||||
exports.wrap = function (tbl)
|
||||
if tbl == nil then tbl = {} end
|
||||
if type(tbl) ~= "table" then tbl = {tbl} end
|
||||
|
||||
local mt = getmetatable(tbl) or {}
|
||||
mt.__index = mt.__index or {}
|
||||
mt.__index.filter = wrap_filter
|
||||
mt.__index.map = wrap_map
|
||||
mt.__index.reduce = wrap_reduce
|
||||
mt.__index.concat = wrap_concat
|
||||
mt.__index.print = wrap_print
|
||||
mt.__index.keys = function (arg)
|
||||
local res = {}
|
||||
for k,_ in pairs(arg) do
|
||||
table.insert(res, k)
|
||||
end
|
||||
return exports.wrap(res)
|
||||
end
|
||||
mt.__index.sort = function (arg)
|
||||
table.sort(arg)
|
||||
return arg
|
||||
end
|
||||
mt.__index.dedupe = function (arg)
|
||||
local res, hash = {}, {}
|
||||
for _,v in ipairs(arg) do
|
||||
if not hash[v] then
|
||||
hash[v] = true
|
||||
table.insert(res, v)
|
||||
end
|
||||
end
|
||||
return exports.wrap(res)
|
||||
end
|
||||
mt.__index.contains = function (arg, value)
|
||||
for _,v in ipairs(arg) do
|
||||
if v == value then return true, _ end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return setmetatable(tbl, mt)
|
||||
end
|
||||
|
||||
return exports
|
240
pip.lua
Normal file
240
pip.lua
Normal file
@ -0,0 +1,240 @@
|
||||
-- -*- coding: utf-8 -*-
|
||||
-- preamble: common routines
|
||||
|
||||
local matchers = require("matchers")
|
||||
local w = require("tables").wrap
|
||||
|
||||
local parser = clink.arg.new_parser
|
||||
|
||||
local function pip_libs_list(token)
|
||||
local handle = io.popen('python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"')
|
||||
local python_lib_path = handle:read("*a")
|
||||
handle:close()
|
||||
|
||||
-- trim spaces
|
||||
python_lib_path = python_lib_path:gsub("^%s*(.-)%s*$", "%1")
|
||||
|
||||
local finder = matchers.create_files_matcher(python_lib_path .. "\\*.dist-info")
|
||||
|
||||
local list = w(finder(token))
|
||||
|
||||
list =
|
||||
list:map(
|
||||
function(package)
|
||||
package = package:gsub("-[%d%.]+dist%-info$", "")
|
||||
return package
|
||||
end
|
||||
)
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
local pip_default_flags = {
|
||||
"--help",
|
||||
"-h",
|
||||
"--isolated",
|
||||
"--verbose",
|
||||
"-v",
|
||||
"--version",
|
||||
"-V",
|
||||
"--quiet",
|
||||
"-q",
|
||||
"--log",
|
||||
"--proxy",
|
||||
"--retries",
|
||||
"--timeout",
|
||||
"--exists-action",
|
||||
"--trusted-host",
|
||||
"--cert",
|
||||
"--client-cert",
|
||||
"--cache-dir",
|
||||
"--no-cache-dir",
|
||||
"--disable-pip-version-check",
|
||||
"--no-color"
|
||||
}
|
||||
|
||||
local pip_requirement_flags = {
|
||||
"--requirement" .. parser({clink.matches_are_files}),
|
||||
"-r" .. parser({clink.matches_are_files})
|
||||
}
|
||||
|
||||
local pip_index_flags = {
|
||||
"--index-url",
|
||||
"-i",
|
||||
"--extra-index-url",
|
||||
"--no-index",
|
||||
"--find-links",
|
||||
"-f"
|
||||
}
|
||||
|
||||
local pip_install_download_wheel_flags = {
|
||||
pip_requirement_flags,
|
||||
"--no-binary",
|
||||
"--only-binary",
|
||||
"--prefer-binary",
|
||||
"--no-build-isolation",
|
||||
"--use-pep517",
|
||||
"--constraint",
|
||||
"-c",
|
||||
"--src",
|
||||
"--no-deps",
|
||||
"--progress-bar" .. parser({"off", "on", "ascii", "pretty", "emoji"}),
|
||||
"--global-option",
|
||||
"--pre",
|
||||
"--no-clean",
|
||||
"--requires-hashes"
|
||||
}
|
||||
|
||||
local pip_install_download_flags = {
|
||||
pip_install_download_wheel_flags,
|
||||
"--platform",
|
||||
"--python-version",
|
||||
"--implementation" .. parser({"pp", "jy", "cp", "ip"}),
|
||||
"--abi"
|
||||
}
|
||||
|
||||
local pip_install_parser =
|
||||
parser(
|
||||
{},
|
||||
"--editable",
|
||||
"-e",
|
||||
"--target",
|
||||
"-t",
|
||||
"--user",
|
||||
"--root",
|
||||
"--prefix",
|
||||
"--build",
|
||||
"-b",
|
||||
"--upgrade",
|
||||
"-U",
|
||||
"--upgrade-strategy" .. parser({"eager", "only-if-needed"}),
|
||||
"--force-reinstall",
|
||||
"--ignore-installed",
|
||||
"-I",
|
||||
"--ignore-requires-python",
|
||||
"--install-option",
|
||||
"--compile",
|
||||
"--no-compile",
|
||||
"--no-warn-script-location",
|
||||
"--no-warn-conflicts"
|
||||
):loop(1)
|
||||
pip_install_parser:add_flags(pip_install_download_flags)
|
||||
pip_install_parser:add_flags(pip_index_flags)
|
||||
pip_install_parser:add_flags(pip_default_flags)
|
||||
|
||||
local pip_download_parser = parser({}, "--build", "-b", "--dest", "-d"):loop(1)
|
||||
pip_download_parser:add_flags(pip_install_download_flags)
|
||||
pip_download_parser:add_flags(pip_index_flags)
|
||||
pip_download_parser:add_flags(pip_default_flags)
|
||||
|
||||
local pip_uninstall_parser =
|
||||
parser({pip_libs_list}, "--yes", "-y"):add_flags(pip_default_flags, pip_requirement_flags):loop(1)
|
||||
|
||||
local pip_freeze_parser = parser({}, "--find-links", "--local", "-l", "--user", "--all", "--exclude-editable")
|
||||
pip_freeze_parser:add_flags(pip_default_flags, pip_requirement_flags)
|
||||
|
||||
local pip_list_parser =
|
||||
parser(
|
||||
{},
|
||||
"--outdated",
|
||||
"-o",
|
||||
"--uptodate",
|
||||
"-u",
|
||||
"--editable",
|
||||
"-e",
|
||||
"--local",
|
||||
"-l",
|
||||
"--user",
|
||||
"--pre",
|
||||
"--format" .. parser({"columns", "freeze", "json"}),
|
||||
"--not-required",
|
||||
"--exclude-editable",
|
||||
"--include-editable"
|
||||
)
|
||||
pip_list_parser:add_flags(pip_default_flags)
|
||||
|
||||
local pip_config_parser =
|
||||
parser(
|
||||
{
|
||||
"list",
|
||||
"edit",
|
||||
"get",
|
||||
"set",
|
||||
"unset"
|
||||
},
|
||||
"--editor",
|
||||
"--global",
|
||||
"--user",
|
||||
"--venv",
|
||||
pip_default_flags
|
||||
)
|
||||
pip_config_parser:add_flags(pip_default_flags)
|
||||
|
||||
local pip_search_parser = parser({}, "--index", "-i"):add_flags(pip_default_flags)
|
||||
|
||||
local pip_wheel_parser =
|
||||
parser(
|
||||
{},
|
||||
"--wheel-dir",
|
||||
"-w",
|
||||
"--build-option",
|
||||
"--editable",
|
||||
"-e",
|
||||
"--ignore-requires-python",
|
||||
"--build",
|
||||
"-b"
|
||||
):loop(1)
|
||||
pip_wheel_parser:add_flags(pip_install_download_flags)
|
||||
pip_wheel_parser:add_flags(pip_index_flags)
|
||||
pip_wheel_parser:add_flags(pip_default_flags)
|
||||
|
||||
local pip_hash_parser =
|
||||
parser(
|
||||
{},
|
||||
"--algorithm" .. parser({"sha256", "sha384", "sha512"}),
|
||||
"-a" .. parser({"sha256", "sha384", "sha512"}),
|
||||
pip_default_flags
|
||||
)
|
||||
pip_hash_parser:add_flags(pip_default_flags)
|
||||
|
||||
local pip_completion_parser = parser({}, "--bash", "-b", "--zsh", "-z", "--fish", "-f"):add_flags(pip_default_flags)
|
||||
|
||||
local pip_help_parser =
|
||||
parser(
|
||||
{
|
||||
"install",
|
||||
"download",
|
||||
"uninstall",
|
||||
"freeze",
|
||||
"list",
|
||||
"show",
|
||||
"config",
|
||||
"search",
|
||||
"wheel",
|
||||
"hash",
|
||||
"completion",
|
||||
"help"
|
||||
}
|
||||
)
|
||||
pip_help_parser:add_flags(pip_default_flags)
|
||||
|
||||
local pip_parser =
|
||||
parser(
|
||||
{
|
||||
"install" .. pip_install_parser,
|
||||
"download" .. pip_download_parser,
|
||||
"uninstall" .. pip_uninstall_parser,
|
||||
"freeze" .. pip_freeze_parser,
|
||||
"list" .. pip_list_parser,
|
||||
"show" .. parser({pip_libs_list}, pip_default_flags),
|
||||
"config" .. pip_config_parser,
|
||||
"search" .. pip_search_parser,
|
||||
"wheel" .. pip_wheel_parser,
|
||||
"hash" .. pip_hash_parser,
|
||||
"completion" .. pip_completion_parser,
|
||||
"help" .. pip_help_parser
|
||||
}
|
||||
)
|
||||
pip_parser:add_flags(pip_default_flags)
|
||||
|
||||
clink.arg.register_parser("pip", pip_parser)
|
343
scoop.lua
Normal file
343
scoop.lua
Normal file
@ -0,0 +1,343 @@
|
||||
-- -*- coding: utf-8 -*-
|
||||
-- preamble: common routines
|
||||
|
||||
local JSON = require("JSON")
|
||||
|
||||
local matchers = require("matchers")
|
||||
local path = require("path")
|
||||
local w = require("tables").wrap
|
||||
local concat = require("funclib").concat
|
||||
|
||||
local parser = clink.arg.new_parser
|
||||
local profile = os.getenv("home") or os.getenv("USERPROFILE")
|
||||
|
||||
local function scoop_folder()
|
||||
local folder = os.getenv("SCOOP")
|
||||
|
||||
if not folder then
|
||||
folder = profile .. "\\scoop"
|
||||
end
|
||||
|
||||
return folder
|
||||
end
|
||||
|
||||
local function scoop_global_folder()
|
||||
local folder = os.getenv("SCOOP_GLOBAL")
|
||||
|
||||
if not folder then
|
||||
folder = os.getenv("ProgramData") .. "\\scoop"
|
||||
end
|
||||
|
||||
return folder
|
||||
end
|
||||
|
||||
local function scoop_load_config() -- luacheck: no unused args
|
||||
local file = io.open(profile .. "\\.config\\scoop\\config.json")
|
||||
-- If there is no such file, then close handle and return
|
||||
if file == nil then
|
||||
return w()
|
||||
end
|
||||
|
||||
-- Read the whole file contents
|
||||
local contents = file:read("*a")
|
||||
file:close()
|
||||
|
||||
-- strip UTF-8-BOM
|
||||
local utf8_len = contents:len()
|
||||
local pat_start, _ = string.find(contents, "{")
|
||||
contents = contents:sub(pat_start, utf8_len)
|
||||
|
||||
local data = JSON:decode(contents)
|
||||
|
||||
if data == nil then
|
||||
return w()
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
local function scoop_alias_list(token) -- luacheck: no unused args
|
||||
local data = scoop_load_config()
|
||||
|
||||
return w(data.alias):keys()
|
||||
end
|
||||
|
||||
local function scoop_config_list(token) -- luacheck: no unused args
|
||||
local data = scoop_load_config()
|
||||
|
||||
return w(data):keys()
|
||||
end
|
||||
|
||||
local function scoop_bucket_known_list(token) -- luacheck: no unused args
|
||||
local file = io.open(scoop_folder() .. "\\apps\\scoop\\current\\buckets.json")
|
||||
-- If there is no such file, then close handle and return
|
||||
if file == nil then
|
||||
return w()
|
||||
end
|
||||
|
||||
-- Read the whole file contents
|
||||
local contents = file:read("*a")
|
||||
file:close()
|
||||
|
||||
local data = JSON:decode(contents)
|
||||
|
||||
return w(data):keys()
|
||||
end
|
||||
|
||||
local function scoop_bucket_list(token)
|
||||
local finder = matchers.create_files_matcher(scoop_folder() .. "\\buckets\\*")
|
||||
|
||||
local list = finder(token)
|
||||
|
||||
return list:filter(path.is_real_dir)
|
||||
end
|
||||
|
||||
local function scoop_apps_list(token)
|
||||
local folders = {scoop_folder(), scoop_global_folder()}
|
||||
|
||||
local list = w()
|
||||
for _, folder in pairs(folders) do
|
||||
local finder = matchers.create_files_matcher(folder .. "\\apps\\*")
|
||||
|
||||
local new_list = finder(token)
|
||||
list = w(concat(list, new_list))
|
||||
end
|
||||
|
||||
return list:filter(path.is_real_dir)
|
||||
end
|
||||
|
||||
local function scoop_available_apps_list(token)
|
||||
-- search in default bucket
|
||||
local finder = matchers.create_files_matcher(scoop_folder() .. "\\apps\\scoop\\current\\bucket\\*.json")
|
||||
local list = finder(token)
|
||||
|
||||
-- search in each installed bucket
|
||||
local buckets = scoop_bucket_list("")
|
||||
for _, bucket in pairs(buckets) do
|
||||
local bucket_folder = scoop_folder() .. "\\buckets\\" .. bucket
|
||||
|
||||
-- check the bucket folder exists
|
||||
if clink.is_dir(bucket_folder .. "\\bucket") then
|
||||
bucket_folder = bucket_folder .. "\\bucket"
|
||||
end
|
||||
|
||||
local b_finder = matchers.create_files_matcher(bucket_folder .. "\\*.json")
|
||||
local b_list = b_finder(token)
|
||||
list = w(concat(list, b_list))
|
||||
end
|
||||
|
||||
-- remove ".json" of file name
|
||||
for k, v in pairs(list) do
|
||||
list[k] = v:gsub(".json", "")
|
||||
end
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
local function scoop_cache_apps_list(token)
|
||||
local cache_folder = os.getenv("SCOOP_CACHE")
|
||||
if not cache_folder then
|
||||
cache_folder = scoop_folder() .. "\\cache"
|
||||
end
|
||||
|
||||
local finder = matchers.create_files_matcher(cache_folder .. "\\*")
|
||||
|
||||
local list = finder(token)
|
||||
list = w(list:filter(path.is_real_dir))
|
||||
|
||||
-- get name before "#" from cache list (name#version#url)
|
||||
for k, v in pairs(list) do
|
||||
list[k] = v:gsub("#.*$", "")
|
||||
end
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
local scoop_default_flags = {
|
||||
"--help",
|
||||
"-h"
|
||||
}
|
||||
|
||||
local scoop_alias_parser =
|
||||
parser(
|
||||
{
|
||||
"add",
|
||||
"list" .. parser("-v", "--verbose"),
|
||||
"rm" .. parser({scoop_alias_list})
|
||||
}
|
||||
)
|
||||
|
||||
local scoop_bucket_parser =
|
||||
parser(
|
||||
{
|
||||
"add" .. parser({scoop_bucket_known_list}),
|
||||
"list",
|
||||
"known",
|
||||
"rm" .. parser({scoop_bucket_list})
|
||||
}
|
||||
)
|
||||
|
||||
local scoop_cache_parser =
|
||||
parser(
|
||||
{
|
||||
"show" .. parser({scoop_cache_apps_list, scoop_apps_list, "*"}),
|
||||
"rm" .. parser({scoop_cache_apps_list, "*"})
|
||||
}
|
||||
)
|
||||
|
||||
local scoop_cleanup_parser =
|
||||
parser(
|
||||
{
|
||||
scoop_apps_list,
|
||||
"*"
|
||||
},
|
||||
"--global",
|
||||
"-g",
|
||||
"--cache",
|
||||
"-k"
|
||||
):loop(1)
|
||||
|
||||
local scoop_config_parser =
|
||||
parser(
|
||||
{
|
||||
"rm" .. parser({scoop_config_list}),
|
||||
scoop_config_list,
|
||||
"aria2-enabled" .. parser({"true", "false"}),
|
||||
"aria2-max-connection-per-server",
|
||||
"aria2-min-split-size",
|
||||
"aria2-options",
|
||||
"aria2-retry-wait",
|
||||
"aria2-split",
|
||||
"debug" .. parser({"true", "false"}),
|
||||
"proxy",
|
||||
"show_update_log" .. parser({"true", "false"}),
|
||||
"virustotal_api_key"
|
||||
}
|
||||
)
|
||||
|
||||
local scoop_uninstall_parser =
|
||||
parser(
|
||||
{
|
||||
scoop_apps_list
|
||||
},
|
||||
"--global",
|
||||
"-g",
|
||||
"--purge",
|
||||
"-p"
|
||||
):loop(1)
|
||||
|
||||
local scoop_update_parser =
|
||||
parser(
|
||||
{
|
||||
scoop_apps_list,
|
||||
"*"
|
||||
},
|
||||
"--force",
|
||||
"-f",
|
||||
"--global",
|
||||
"-g",
|
||||
"--independent",
|
||||
"-i",
|
||||
"--no-cache",
|
||||
"-k",
|
||||
"--skip",
|
||||
"-s",
|
||||
"--quiet",
|
||||
"-q"
|
||||
):loop(1)
|
||||
|
||||
local scoop_install_parser =
|
||||
parser(
|
||||
{scoop_available_apps_list},
|
||||
"--global",
|
||||
"-g",
|
||||
"--independent",
|
||||
"-i",
|
||||
"--no-cache",
|
||||
"-k",
|
||||
"--skip",
|
||||
"-s",
|
||||
"--arch" .. parser({"32bit", "64bit"}),
|
||||
"-a" .. parser({"32bit", "64bit"})
|
||||
):loop(1)
|
||||
|
||||
local scoop_help_parser =
|
||||
parser(
|
||||
{
|
||||
"alias",
|
||||
"bucket",
|
||||
"cache",
|
||||
"checkup",
|
||||
"cleanup",
|
||||
"config",
|
||||
"create",
|
||||
"depends",
|
||||
"export",
|
||||
"help",
|
||||
"home",
|
||||
"hold",
|
||||
"info",
|
||||
"install",
|
||||
"list",
|
||||
"prefix",
|
||||
"reset",
|
||||
"search",
|
||||
"status",
|
||||
"unhold",
|
||||
"uninstall",
|
||||
"update",
|
||||
"virustotal",
|
||||
"which"
|
||||
},
|
||||
"/?",
|
||||
"--help",
|
||||
"-h",
|
||||
"--version"
|
||||
)
|
||||
|
||||
local scoop_parser = parser()
|
||||
scoop_parser:set_flags(scoop_default_flags)
|
||||
scoop_parser:set_arguments(
|
||||
{
|
||||
scoop_alias_list,
|
||||
"alias" .. scoop_alias_parser,
|
||||
"bucket" .. scoop_bucket_parser,
|
||||
"cache" .. scoop_cache_parser,
|
||||
"checkup",
|
||||
"cleanup" .. scoop_cleanup_parser,
|
||||
"config" .. scoop_config_parser,
|
||||
"create",
|
||||
"depends" ..
|
||||
parser(
|
||||
{scoop_available_apps_list, scoop_apps_list},
|
||||
"--arch" .. parser({"32bit", "64bit"}),
|
||||
"-a" .. parser({"32bit", "64bit"})
|
||||
),
|
||||
"export",
|
||||
"help" .. scoop_help_parser,
|
||||
"hold" .. parser({scoop_apps_list}),
|
||||
"home" .. parser({scoop_available_apps_list, scoop_apps_list}),
|
||||
"info" .. parser({scoop_available_apps_list, scoop_apps_list}),
|
||||
"install" .. scoop_install_parser,
|
||||
"list",
|
||||
"prefix" .. parser({scoop_apps_list}),
|
||||
"reset" .. parser({scoop_apps_list}):loop(1),
|
||||
"search",
|
||||
"status",
|
||||
"unhold" .. parser({scoop_apps_list}),
|
||||
"uninstall" .. scoop_uninstall_parser,
|
||||
"update" .. scoop_update_parser,
|
||||
"virustotal" ..
|
||||
parser(
|
||||
{scoop_apps_list, "*"},
|
||||
"--arch" .. parser({"32bit", "64bit"}),
|
||||
"-a" .. parser({"32bit", "64bit"}),
|
||||
"--scan",
|
||||
"-s",
|
||||
"--no-depends",
|
||||
"-n"
|
||||
):loop(1),
|
||||
"which"
|
||||
}
|
||||
)
|
||||
clink.arg.register_parser("scoop", scoop_parser)
|
39
ssh.lua
Normal file
39
ssh.lua
Normal file
@ -0,0 +1,39 @@
|
||||
local w = require('tables').wrap
|
||||
local parser = clink.arg.new_parser
|
||||
|
||||
local function read_lines (filename)
|
||||
local lines = w({})
|
||||
local f = io.open(filename)
|
||||
if not f then return lines end
|
||||
|
||||
for line in f:lines() do table.insert(lines, line) end
|
||||
|
||||
f:close()
|
||||
return lines
|
||||
end
|
||||
|
||||
-- read all Host entries in the user's ssh config file
|
||||
local function list_ssh_hosts()
|
||||
return read_lines(clink.get_env("userprofile") .. "/.ssh/config")
|
||||
:map(function (line)
|
||||
return line:match('^Host%s+(.*)$')
|
||||
end)
|
||||
:filter()
|
||||
end
|
||||
|
||||
local function list_known_hosts()
|
||||
return read_lines(clink.get_env("userprofile") .. "/.ssh/known_hosts")
|
||||
:map(function (line)
|
||||
return line:match('^([%w-.]*).*')
|
||||
end)
|
||||
:filter()
|
||||
end
|
||||
|
||||
local hosts = function (token) -- luacheck: no unused args
|
||||
return list_ssh_hosts()
|
||||
:concat(list_known_hosts())
|
||||
end
|
||||
|
||||
local ssh_hosts_parser = parser({hosts})
|
||||
|
||||
clink.arg.register_parser("ssh", ssh_hosts_parser)
|
Loading…
x
Reference in New Issue
Block a user