├── test.bat ├── completions ├── procdump.lua ├── procdump64.lua ├── rustup.lua ├── cargo.lua ├── rustc.lua ├── grep.lua ├── sed.lua ├── curl.lua ├── xcopy.lua ├── colortool.lua ├── findstr.lua ├── cmdkey.lua ├── wt.lua ├── cf.lua ├── less.lua ├── doskey.lua ├── attrib.lua ├── nuke.lua ├── ping.lua ├── make.lua ├── premake5.lua ├── robocopy.lua ├── sudo.lua ├── code.lua ├── spicetify.lua ├── fastboot.lua ├── gsudo.lua ├── delta.lua ├── where.lua ├── bat.lua └── eza.lua ├── .busted ├── .gitignore ├── .luacov ├── .luacheckrc ├── .vscode └── tasks.json ├── .github └── workflows │ └── code-check.yml ├── !init.lua ├── .init.lua ├── LICENSE ├── spec ├── path_spec.lua ├── color_spec.lua └── funclib_spec.lua ├── nvm.lua ├── modules ├── color.lua ├── clink_version.lua ├── tables.lua ├── path.lua ├── funclib.lua ├── gitutil.lua ├── matchers.lua ├── pid_complete.lua ├── procdump_shared.lua └── multicharflags.lua ├── kubectl.lua ├── net.lua ├── coho.lua ├── cordova.lua ├── vagrant.lua ├── pipenv.lua ├── angular-cli.lua ├── chocolatey.lua ├── pip.lua └── README.md /test.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | chcp 65001 1>nul 3 | luacheck . && call busted 4 | -------------------------------------------------------------------------------- /completions/procdump.lua: -------------------------------------------------------------------------------- 1 | local shared = require("procdump_shared") 2 | shared.init_procdump() 3 | -------------------------------------------------------------------------------- /completions/procdump64.lua: -------------------------------------------------------------------------------- 1 | local shared = require("procdump_shared") 2 | shared.init_procdump() 3 | -------------------------------------------------------------------------------- /.busted: -------------------------------------------------------------------------------- 1 | return { 2 | default = { 3 | coverage = true, 4 | verbose = true, 5 | lpath = "./modules/?.lua" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .history 2 | *.sublime-* 3 | settings 4 | 5 | # testing stuff 6 | lua_modules 7 | luacov.*.out 8 | 9 | #local lua 10 | .lua 11 | -------------------------------------------------------------------------------- /completions/rustup.lua: -------------------------------------------------------------------------------- 1 | -- Rustup argmatcher for Rust. 2 | local rh = require("rust_helper") 3 | if rh then 4 | rh.make_rust_argmatcher("rustup.exe") 5 | end 6 | -------------------------------------------------------------------------------- /.luacov: -------------------------------------------------------------------------------- 1 | return { 2 | include = { "modules/*" }, 3 | exclude = { "lua_modules/*", ".lua/*", "modules/clink_version.lua" }, 4 | deletestats = false, 5 | runreport = true 6 | } 7 | -------------------------------------------------------------------------------- /completions/cargo.lua: -------------------------------------------------------------------------------- 1 | -- Cargo argmatcher for Rust. 2 | local rh = require("rust_helper") 3 | if rh then 4 | local cargo = rh.make_rust_argmatcher("cargo.exe") 5 | cargo.rust_data.dashdashlist = true 6 | end 7 | -------------------------------------------------------------------------------- /completions/rustc.lua: -------------------------------------------------------------------------------- 1 | -- Rustc argmatcher for Rust. 2 | local rh = require("rust_helper") 3 | if rh then 4 | local rustc = rh.make_rust_argmatcher("rustc.exe") 5 | rustc.rust_data.help_commands[rustc] = "--help" 6 | end 7 | -------------------------------------------------------------------------------- /completions/grep.lua: -------------------------------------------------------------------------------- 1 | local clink_version = require('clink_version') 2 | if not clink_version.supports_argmatcher_delayinit then 3 | log.info("grep.lua argmatcher requires a newer version of Clink; please upgrade.") 4 | return 5 | end 6 | 7 | require('help_parser').make('grep', '--help', 'gnu') 8 | -------------------------------------------------------------------------------- /completions/sed.lua: -------------------------------------------------------------------------------- 1 | local clink_version = require('clink_version') 2 | if not clink_version.supports_argmatcher_delayinit then 3 | log.info("sed.lua argmatcher requires a newer version of Clink; please upgrade.") 4 | return 5 | end 6 | 7 | require('help_parser').make('sed', '--help', 'gnu') 8 | -------------------------------------------------------------------------------- /completions/curl.lua: -------------------------------------------------------------------------------- 1 | local clink_version = require('clink_version') 2 | if not clink_version.supports_argmatcher_delayinit then 3 | log.info("curl.lua argmatcher requires a newer version of Clink; please upgrade.") 4 | return 5 | end 6 | 7 | require('help_parser').make('curl', '--help all', 'curl') 8 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | return { 2 | exclude_files = { ".install", ".lua", ".luarocks", "modules/JSON.lua", "lua_modules" }, 3 | files = { 4 | spec = { std = "+busted" }, 5 | }, 6 | globals = { 7 | "clink", 8 | "console", 9 | "error", 10 | "log", 11 | "os", 12 | "path", 13 | "pause", 14 | "rl", 15 | "rl_state", 16 | "settings", 17 | "string.comparematches", 18 | "string.equalsi", 19 | "string.explode", 20 | "string.matchlen", 21 | "unicode", 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "command": "cmd", 6 | "args": [ 7 | "/c" 8 | ], 9 | "tasks": [ 10 | { 11 | "label": "test", 12 | "type": "shell", 13 | "command": "cmd", 14 | "args": [ 15 | "/c", 16 | "test" 17 | ], 18 | "problemMatcher": [], 19 | "group": { 20 | "_id": "test", 21 | "isDefault": false 22 | } 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/code-check.yml: -------------------------------------------------------------------------------- 1 | name: Code check 2 | on: 3 | # Allows you to run this workflow manually from the Actions tab 4 | workflow_dispatch: 5 | push: 6 | pull_request: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: leafo/gh-actions-lua@v9.1.0 14 | with: 15 | luaVersion: "5.2.4" 16 | - uses: leafo/gh-actions-luarocks@v4.3.0 17 | - name: Install dependencies 18 | run: | 19 | luarocks install busted 20 | luarocks install cluacov 21 | luarocks install luacheck 22 | - name: Run checks 23 | run: | 24 | luacheck . 25 | busted 26 | - uses: codecov/codecov-action@v3.1.1 27 | -------------------------------------------------------------------------------- /completions/xcopy.lua: -------------------------------------------------------------------------------- 1 | local clink_version = require('clink_version') 2 | if not clink_version.supports_argmatcher_delayinit then 3 | log.info("xcopy.lua argmatcher requires a newer version of Clink; please upgrade.") 4 | return 5 | end 6 | 7 | local function closure(parser) 8 | -- This is a dirty hack. I don't want to invest in a reusable clean 9 | -- mechanism right now. 10 | if parser._flags and parser._flags._args and parser._flags._args[1] then 11 | local tbl = { concat_one_letter_flags=true } 12 | if parser._flags._args[1]._links["/d:"] then 13 | table.insert(tbl, { hide=true, "/d" }) 14 | end 15 | if parser._flags._args[1]._links["/D:"] then 16 | local desc = parser._descriptions["/D:"] 17 | table.insert(tbl, { "/D", desc[#desc] }) 18 | end 19 | if tbl[1] then 20 | parser:_addexflags(tbl) 21 | end 22 | end 23 | end 24 | 25 | require('help_parser').make('xcopy', '/?', nil, {concat=true}, closure) 26 | -------------------------------------------------------------------------------- /!init.lua: -------------------------------------------------------------------------------- 1 | -- Note: This happens in both .init.lua and !init.lua because older Cmder 2 | -- versions don't know about !init.lua. 3 | 4 | -- Get the parent path of this script. 5 | local parent_path = debug.getinfo(1, "S").source:match[[^@?(.*[\/])[^\/]-$]] 6 | 7 | -- Extend package.path with modules directory, if not already present, to allow 8 | -- using require() with them. 9 | local modules_path = parent_path.."modules/?.lua" 10 | if not package.path:find(modules_path, 1, true--[[plain]]) then 11 | package.path = modules_path..";"..package.path 12 | end 13 | 14 | -- Explicitly set the completions dir, in case something (such as Cmder) 15 | -- manually loads completion scripts with them being in a Clink script path. 16 | if os.setenv then 17 | local completions_path = parent_path.."completions" 18 | local env = os.getenv("CLINK_COMPLETIONS_DIR") or "" 19 | if not env:find(completions_path, 1, true--[[plain]]) then 20 | os.setenv("CLINK_COMPLETIONS_DIR", env .. (#env > 0 and ";" or "") .. completions_path) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /.init.lua: -------------------------------------------------------------------------------- 1 | -- Note: This happens in both .init.lua and !init.lua because older Cmder 2 | -- versions don't know about !init.lua. 3 | 4 | -- Get the parent path of this script. 5 | local parent_path = debug.getinfo(1, "S").source:match[[^@?(.*[\/])[^\/]-$]] 6 | 7 | -- Extend package.path with modules directory, if not already present, to allow 8 | -- using require() with them. 9 | local modules_path = parent_path.."modules/?.lua" 10 | if not package.path:find(modules_path, 1, true--[[plain]]) then 11 | package.path = modules_path..";"..package.path 12 | end 13 | 14 | -- Explicitly set the completions dir, in case something (such as Cmder) 15 | -- manually loads completion scripts with them being in a Clink script path. 16 | if os.setenv then 17 | local completions_path = parent_path.."completions" 18 | local env = os.getenv("CLINK_COMPLETIONS_DIR") or "" 19 | if not env:find(completions_path, 1, true--[[plain]]) then 20 | os.setenv("CLINK_COMPLETIONS_DIR", env .. (#env > 0 and ";" or "") .. completions_path) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Vladimir Kotikov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/path_spec.lua: -------------------------------------------------------------------------------- 1 | local path = require('path') 2 | 3 | describe("path module", function() 4 | describe("is_absolute", function () 5 | it("should return true for absolute paths", function () 6 | assert.is_true(path.is_absolute("c:/foo.bar")) 7 | assert.is_true(path.is_absolute("c:/foo.bar/baz")) 8 | assert.is_true(path.is_absolute("c:\\foo.bar")) 9 | assert.is_true(path.is_absolute("c:\\foo.bar\\baz")) 10 | assert.is_true(path.is_absolute("z:/baz\\foo.bar")) 11 | assert.is_true(path.is_absolute("z:\\baz/foo.bar")) 12 | assert.is_true(path.is_absolute("c:/quux/..\\baz/foo.bar")) 13 | end) 14 | 15 | it("should return false for relative paths", function () 16 | assert.is_false(path.is_absolute("./foo.bar")) 17 | assert.is_false(path.is_absolute(".\\baz")) 18 | assert.is_false(path.is_absolute("foo.bar")) 19 | assert.is_false(path.is_absolute(".\\foo.bar\\baz")) 20 | assert.is_false(path.is_absolute("./baz\\foo.bar")) 21 | assert.is_false(path.is_absolute("..\\baz/foo.bar")) 22 | end) 23 | end) 24 | end) 25 | -------------------------------------------------------------------------------- /nvm.lua: -------------------------------------------------------------------------------- 1 | local path = require('path') 2 | local w = require('tables').wrap 3 | local parser = clink.arg.new_parser 4 | 5 | local NVM_ROOT 6 | 7 | local function get_nvm_root() 8 | if NVM_ROOT then return NVM_ROOT end 9 | 10 | local proc = io.popen("2>nul nvm root") 11 | if not proc then 12 | NVM_ROOT = "" 13 | return NVM_ROOT 14 | end 15 | 16 | local lines = proc:read('*all') 17 | NVM_ROOT = lines:match("Current Root:%s(.*)%s*\n$") or "" 18 | proc:close() 19 | 20 | return NVM_ROOT 21 | end 22 | 23 | local installed = function () 24 | return w(clink.find_dirs(get_nvm_root().."/*")) 25 | :filter(path.is_real_dir) 26 | :map(function (dir) 27 | return dir:match("v(.*)") 28 | end) 29 | end 30 | 31 | local archs = parser({"64", "32"}) 32 | 33 | local nvm_parser = parser({ 34 | "arch"..archs, 35 | "install"..parser({"latest"}, archs), 36 | "list"..parser({installed, "available"}), 37 | "ls"..parser({installed, "available"}), 38 | "on", "off", 39 | "proxy"..parser({"none"}), 40 | "uninstall"..parser({installed}), 41 | "use"..parser({installed}, archs), 42 | "root", 43 | "version", "v" 44 | }, "-h", "--help", "-v", "--version") 45 | 46 | clink.arg.register_parser("nvm", nvm_parser) 47 | -------------------------------------------------------------------------------- /completions/colortool.lua: -------------------------------------------------------------------------------- 1 | local clink_version = require('clink_version') 2 | if not clink_version.supports_argmatcher_delayinit then 3 | log.info("colortool.lua argmatcher requires a newer version of Clink; please upgrade.") 4 | return 5 | end 6 | 7 | local function init_themes(argmatcher, argindex) -- luacheck: no unused 8 | local matches = {} 9 | local r = io.popen('2>nul colortool -s') 10 | if not r then 11 | return 12 | end 13 | 14 | for line in r:lines() do 15 | local name = line:match('^(.-)\x1b') 16 | if name then 17 | local color = line:match('^[^\x1b]*(\x1b.*)$') 18 | name = name:gsub(' +$', '') 19 | color = color and '\x1b[m'..color..'\x1b[m' or '' 20 | table.insert(matches, {match=name, description=color}) 21 | end 22 | end 23 | 24 | r:close() 25 | return matches 26 | end 27 | 28 | local function forcequoting(_, _, _, builder) 29 | if builder.setforcequoting then 30 | builder:setforcequoting() 31 | end 32 | return {} 33 | end 34 | 35 | local function closure(argmatcher) 36 | argmatcher:addarg({delayinit=init_themes, forcequoting}) 37 | argmatcher:nofiles() 38 | end 39 | 40 | local help_parser = require('help_parser') 41 | help_parser.make('colortool', '-?', 'curl', nil, closure) 42 | -------------------------------------------------------------------------------- /modules/color.lua: -------------------------------------------------------------------------------- 1 | local clink_version = require('clink_version') 2 | 3 | local exports = {} 4 | 5 | exports.BLACK = 0 6 | exports.RED = 1 7 | exports.GREEN = 2 8 | exports.YELLOW = 3 9 | exports.BLUE = 4 10 | exports.MAGENTA = 5 11 | exports.CYAN = 6 12 | exports.WHITE = 7 13 | exports.DEFAULT = 9 14 | exports.BOLD = 1 15 | 16 | exports.set_color = function (fore, back, bold) 17 | local err_message = "All arguments must be either nil or numbers between 0-9" 18 | assert(fore == nil or (type(fore) == "number" and fore >= 0 and fore <=9), err_message) 19 | assert(back == nil or (type(back) == "number" and back >= 0 and back <=9), err_message) 20 | 21 | fore = fore or exports.DEFAULT 22 | back = back or exports.DEFAULT 23 | bold = bold and exports.BOLD or 22 24 | 25 | return "\x1b[3"..fore..";"..bold..";".."4"..back.."m" 26 | end 27 | 28 | exports.get_clink_color = function (setting_name) 29 | -- Clink's settings.get() returns SGR parameters for a CSI SGR escape code. 30 | local sgr = clink_version.supports_color_settings and settings.get(setting_name) or "" 31 | if sgr ~= "" then 32 | sgr = "\x1b["..sgr.."m" 33 | end 34 | return sgr 35 | end 36 | 37 | exports.color_text = function (text, fore, back, bold) 38 | return exports.set_color(fore, back, bold)..text..exports.set_color() 39 | end 40 | 41 | return exports 42 | -------------------------------------------------------------------------------- /modules/clink_version.lua: -------------------------------------------------------------------------------- 1 | local exports = {} 2 | 3 | -- Busted runs these modules scripts *outside* of Clink. 4 | -- So these Clink scripts have to work without any Clink APIs being available. 5 | clink = clink or {} 6 | 7 | local clink_version_encoded = clink.version_encoded or 0 8 | 9 | -- Version check for the new v1.0.0 API redesign. 10 | exports.new_api = (settings and settings.add and true or false) 11 | 12 | -- Version checks for specific features. 13 | exports.supports_display_filter_description = (clink_version_encoded >= 10010012) 14 | exports.supports_color_settings = (clink_version_encoded >= 10010009) 15 | exports.supports_query_rl_var = (clink_version_encoded >= 10010009) 16 | exports.supports_path_toparent = (clink_version_encoded >= 10010020) 17 | exports.supports_argmatcher_nosort = (clink_version_encoded >= 10030003) 18 | exports.supports_argmatcher_hideflags = (clink_version_encoded >= 10030003) 19 | exports.supports_argmatcher_delayinit = (clink_version_encoded >= 10030010) 20 | exports.supports_argmatcher_chaincommand = (clink_version_encoded >= 10030013) 21 | exports.has_volatile_matches_fix = (clink_version_encoded >= 10040013) 22 | exports.supports_argmatcher_onlink = (clink_version_encoded >= 10050014) 23 | exports.has_linked_setdelayinit_fix = (clink_version_encoded >= 10050016) 24 | exports.supports_argmatcher_nowordbreakchars = (clink_version_encoded >= 10050017) 25 | exports.supports_onalias = (clink_version_encoded >= 10060018) 26 | 27 | return exports 28 | -------------------------------------------------------------------------------- /modules/tables.lua: -------------------------------------------------------------------------------- 1 | local concat = require('funclib').concat 2 | local filter = require('funclib').filter 3 | local map = require('funclib').map 4 | local reduce = require('funclib').reduce 5 | 6 | local exports = {} 7 | 8 | local wrap_filter = function (tbl, filter_func) 9 | return exports.wrap(filter(tbl, filter_func)) 10 | end 11 | 12 | local wrap_map = function (tbl, map_func) 13 | return exports.wrap(map(tbl, map_func)) 14 | end 15 | 16 | local wrap_reduce = function (tbl, accum, reduce_func) 17 | local res = reduce(accum, tbl, reduce_func) 18 | return (type(res) == "table" and exports.wrap(res) or res) 19 | end 20 | 21 | local wrap_concat = function (tbl, ...) 22 | return exports.wrap(concat(tbl, ...)) 23 | end 24 | 25 | local wrap_print = function (tbl) 26 | return exports.wrap(filter(tbl, function (item) 27 | print(item) 28 | return true 29 | end)) 30 | end 31 | 32 | exports.wrap = function (tbl) 33 | if tbl == nil then tbl = {} end 34 | if type(tbl) ~= "table" then tbl = {tbl} end 35 | 36 | local mt = getmetatable(tbl) or {} 37 | mt.__index = mt.__index or {} 38 | mt.__index.filter = wrap_filter 39 | mt.__index.map = wrap_map 40 | mt.__index.reduce = wrap_reduce 41 | mt.__index.concat = wrap_concat 42 | mt.__index.print = wrap_print 43 | mt.__index.keys = function (arg) 44 | local res = {} 45 | for k,_ in pairs(arg) do 46 | table.insert(res, k) 47 | end 48 | return exports.wrap(res) 49 | end 50 | mt.__index.sort = function (arg) 51 | table.sort(arg) 52 | return arg 53 | end 54 | mt.__index.dedupe = function (arg) 55 | local res, hash = {}, {} 56 | for _,v in ipairs(arg) do 57 | if not hash[v] then 58 | hash[v] = true 59 | table.insert(res, v) 60 | end 61 | end 62 | return exports.wrap(res) 63 | end 64 | mt.__index.contains = function (arg, value) 65 | for _,v in ipairs(arg) do 66 | if v == value then return true, _ end 67 | end 68 | return false 69 | end 70 | 71 | return setmetatable(tbl, mt) 72 | end 73 | 74 | return exports 75 | -------------------------------------------------------------------------------- /kubectl.lua: -------------------------------------------------------------------------------- 1 | local w = require('tables').wrap 2 | local parser = clink.arg.new_parser 3 | 4 | local function exec_kubectl(arguments, template) 5 | local f = io.popen("2>nul kubectl "..arguments.." -o template --template=\""..template.."\"") 6 | if not f then return w({}) end 7 | local output = f:read('*all') 8 | f:close() 9 | local res = w({}) 10 | for element in output:gmatch("%S+") do table.insert(res, element) end 11 | return res 12 | end 13 | 14 | local function get_config(config) 15 | return exec_kubectl("config view", "{{ range ."..config.." }}{{ .name }} {{ end }}") 16 | end 17 | 18 | local function get_config_func(config) 19 | return function() 20 | return get_config(config) 21 | end 22 | end 23 | 24 | local function get_resources(noun) 25 | return exec_kubectl("get "..noun, "{{ range .items }}{{ .metadata.name }} {{ end }}") 26 | end 27 | 28 | local function get_resources_func(noun) 29 | return function() 30 | return get_resources(noun) 31 | end 32 | end 33 | 34 | local resource_parser = parser( 35 | { 36 | "all" .. parser({get_resources_func("all")}), 37 | "node" .. parser({get_resources_func("node")}), 38 | "service" .. parser({get_resources_func("service")}), 39 | "pod" .. parser({get_resources_func("pod")}), 40 | "deployment" .. parser({get_resources_func("deployment")}) 41 | } 42 | ) 43 | 44 | local scale_parser = parser( 45 | { 46 | "deployment" .. parser({get_resources_func("deployment")}, parser({"--replicas"})) 47 | } 48 | ) 49 | 50 | local config_parser = parser( 51 | { 52 | "current-context", 53 | "delete-cluster", 54 | "delete-context", 55 | "get-clusters", 56 | "get-contexts", 57 | "rename-context", 58 | "set", 59 | "set-cluster", 60 | "set-context", 61 | "set-credentials", 62 | "unset", 63 | "use-context" .. parser({get_config_func("contexts")}), 64 | "view" 65 | } 66 | ) 67 | 68 | local kubectl_parser = parser( 69 | { 70 | "apply", 71 | "exec" .. parser({get_resources_func("pod")}, parser({ "-it"})), 72 | "get" .. resource_parser, 73 | "describe" .. resource_parser, 74 | "logs" .. parser({get_resources_func("pod")}), 75 | "port-forward" .. parser({get_resources_func("pod")}), 76 | "scale" .. scale_parser, 77 | "config" .. config_parser 78 | } 79 | ) 80 | 81 | clink.arg.register_parser("kubectl", kubectl_parser) -------------------------------------------------------------------------------- /completions/findstr.lua: -------------------------------------------------------------------------------- 1 | require('arghelper') 2 | 3 | local dir_matcher = clink.argmatcher():addarg(clink.dirmatches) 4 | local file_matcher = clink.argmatcher():addarg({ 5 | { match="/", display="/ (console)" }, 6 | clink.filematches 7 | }) 8 | 9 | local a_parser = clink.argmatcher():addarg({fromhistory=true}) 10 | local c_parser = clink.argmatcher():addarg("search_string") 11 | 12 | local flag_def_table = { 13 | {"/b", "Matches pattern if at the beginning of a line"}, 14 | {"/e", "Matches pattern if at the end of a line"}, 15 | {"/l", "Uses search strings literally"}, 16 | {"/r", "Uses search strings as regular expressions (default)"}, 17 | {"/s", "Search in subdirectories also"}, 18 | {"/i", "Case insensitive search"}, 19 | {"/x", "Prints lines that match exactly"}, 20 | {"/v", "Prints only lines that do not contain a match"}, 21 | {"/n", "Prints the line number before each line that matches"}, 22 | {"/m", "Prints only the filename if a file contains a match"}, 23 | {"/o", "Prints character offset before each matching line"}, 24 | {"/p", "Skips files with non-printable characters"}, 25 | {"/offline", "Do not skip files with offline attribute set"}, 26 | {"/a:", a_parser, "hexattr", "Specifies color attribute with two hex digits"}, 27 | {"/f:", file_matcher, "file", "Reads file list from the specified file (/ stands for console)"}, 28 | {"/c:", c_parser, "string", "Uses specified string as literal search string"}, 29 | {"/g:", file_matcher, "file", "Gets search strings from the specified file (/ stands for console)"}, 30 | {"/d:", dir_matcher, "dir[;dir...]", "Search a semicolon delimited list of directories"}, 31 | } 32 | 33 | local flags = { concat_one_letter_flags=true } 34 | for _,f in ipairs(flag_def_table) do 35 | if f[3] then 36 | table.insert(flags, { f[1]..f[2], f[3], f[4] }) 37 | table.insert(flags, { hide=true, f[1]:upper()..f[2], f[3], f[4] }) 38 | else 39 | table.insert(flags, { f[1], f[2] }) 40 | table.insert(flags, { hide=true, f[1]:upper(), f[2] }) 41 | end 42 | end 43 | 44 | -- luacheck: no max line length 45 | clink.argmatcher("findstr") 46 | :setflagsanywhere(false) 47 | :_addexflags(flags) 48 | -------------------------------------------------------------------------------- /completions/cmdkey.lua: -------------------------------------------------------------------------------- 1 | require('arghelper') 2 | 3 | local deadend = clink.argmatcher():nofiles() 4 | local placeholder = clink.argmatcher():addarg() 5 | local users = clink.argmatcher():addarg({fromhistory=true}) 6 | 7 | local add_flags = { 8 | {"/user:"..users, "username", "Specify username"}, 9 | {"/pass:"..placeholder, "password", "Specify password"}, 10 | {"/pass", "Prompt for password"}, 11 | {"/smartcard", "Retrieve credential from a smart card"}, 12 | } 13 | 14 | local function existing_targets(_, _, _, builder) 15 | local targets = {} 16 | local pending 17 | local f = io.popen("2>nul cmdkey.exe /list") 18 | if f then 19 | for line in f:lines() do 20 | local a,b = line:match("^ +Target: ([^=:]+):target=(.*)$") 21 | if a and b then 22 | pending = {type=a, target=b} 23 | elseif pending then 24 | local u = line:match("^ +User: (.+)$") 25 | if u then 26 | table.insert(targets, {match=pending.target, type="arg", description=u.." ("..pending.type..")"}) 27 | pending = nil 28 | end 29 | end 30 | end 31 | f:close() 32 | if builder.setforcequoting then 33 | builder:setforcequoting() 34 | end 35 | end 36 | return targets 37 | end 38 | 39 | local list_targets = clink.argmatcher():addarg({fromhistory=true}):nofiles() 40 | local domain_targets = clink.argmatcher():addarg({fromhistory=true}):_addexflags(add_flags):nofiles() 41 | local generic_targets = clink.argmatcher():addarg({fromhistory=true}):_addexflags(add_flags):nofiles() 42 | local delete_targets = clink.argmatcher():addarg({existing_targets}):nofiles() 43 | 44 | clink.argmatcher("cmdkey") 45 | :_addexflags({ 46 | {"/list"..deadend, "List available credentials"}, 47 | {"/list:"..list_targets, "targetname", "List available credentials for targetname"}, 48 | {"/add:"..domain_targets, "targetname", "Create domain credentials"}, 49 | {"/generic:"..generic_targets, "targetname", "Create generic credentials"}, 50 | {"/delete /ras"..deadend, "Delete RAS credentials"}, 51 | {"/delete:"..delete_targets, "targetname", "Delete existing credentials"}, 52 | {"/?", "Show help"}, 53 | {hide=true, "/delete"}, 54 | {hide=true, "/ras"}, 55 | {hide=true, "/user:"..users}, 56 | {hide=true, "/pass:"..placeholder}, 57 | {hide=true, "/pass"}, 58 | {hide=true, "/smartcard"}, 59 | }) 60 | :nofiles() 61 | 62 | -------------------------------------------------------------------------------- /net.lua: -------------------------------------------------------------------------------- 1 | local clink_version = require('clink_version') 2 | 3 | local function args(...) 4 | if clink_version.new_api then 5 | return clink.argmatcher() 6 | :addflags("/?") 7 | :addarg(...) 8 | :addarg() 9 | :nofiles() 10 | :loop() 11 | else 12 | return clink.arg.new_parser({...}, "/?") 13 | end 14 | end 15 | 16 | local function flags(...) 17 | if clink_version.new_api then 18 | return clink.argmatcher() 19 | :addflags("/?", ...) 20 | :addarg({}) 21 | :loop() 22 | else 23 | return clink.arg.new_parser("/?", ...) 24 | end 25 | end 26 | 27 | local function flagsnofiles(...) 28 | if clink_version.new_api then 29 | return flags(...):nofiles() 30 | else 31 | return clink.arg.new_parser({}, "/?", ...) 32 | end 33 | end 34 | 35 | local helpflag = flagsnofiles() 36 | 37 | local time_parser 38 | if clink_version.new_api then 39 | time_parser = clink.argmatcher() 40 | :addflags("/domain", "/domain:", "/rtsdomain", "/rtsdomain:", "/set", "/?") 41 | :addarg({}) 42 | :loop() 43 | else 44 | time_parser = flags("/domain", "/rtsdomain", "/set", "/?") 45 | end 46 | 47 | local use_flags = flags("/user:", "/u:", "/smartcard", "/savecred", "/delete", "/d", 48 | "/persistent:yes", "/p:yes", "/persistent:no", "/p:no") 49 | if use_flags.hideflags then 50 | use_flags:hideflags("/u:", "/d", "/p:yes", "/p:no") 51 | end 52 | 53 | local net_table = 54 | { 55 | "accounts" .. flags("/forcelogoff:", "/forcelogoff:no", "/domain", 56 | "/maxpwage:", "/maxpwage:unlimited", "/minpwage:", 57 | "/minpwlen:","/uniquepw:"), 58 | "computer" .. args("*" .. flagsnofiles("/add", "/del")), 59 | "config" .. args("server", "workstation"), 60 | "continue" .. helpflag, 61 | "file" .. helpflag, 62 | "group" .. helpflag, 63 | "helpmsg" .. helpflag, 64 | "localgroup" .. helpflag, 65 | "pause" .. helpflag, 66 | "session" .. flags("/delete", "/list"), 67 | "share" .. helpflag, 68 | "start" .. helpflag, 69 | "statistics" .. args("server", "workstation"), 70 | "stop" .. helpflag, 71 | "time" .. time_parser, 72 | "use" .. use_flags, 73 | "user" .. helpflag, 74 | "view" .. flags("/cache", "/all", "/domain") 75 | } 76 | 77 | local help_table = 78 | { 79 | "help" .. args(net_table, "names", "services", "syntax") 80 | } 81 | 82 | if clink_version.new_api then 83 | clink.argmatcher("net") 84 | :addflags("/?") 85 | :addarg(net_table, help_table) 86 | else 87 | clink.arg.register_parser("net", clink.arg.new_parser(net_table), "/?") 88 | clink.arg.register_parser("net", clink.arg.new_parser(help_table)) 89 | end 90 | -------------------------------------------------------------------------------- /completions/wt.lua: -------------------------------------------------------------------------------- 1 | require("arghelper") 2 | local clink_version = require("clink_version") 3 | 4 | local pos_args = clink.argmatcher():addarg({fromhistory=true, nowordbreakchars=","}) 5 | local size_args = clink.argmatcher():addarg({fromhistory=true, nowordbreakchars=","}) 6 | local window_args = clink.argmatcher():addarg({fromhistory=true}) 7 | 8 | if not clink_version.supports_argmatcher_nowordbreakchars then 9 | pos_args:addarg({fromhistory=true}) 10 | size_args:addarg({fromhistory=true}) 11 | end 12 | 13 | local subcommands = { 14 | ["nt"]=true, ["new-tab"]=true, 15 | ["sp"]=true, ["split-pane"]=true, 16 | ["ft"]=true, ["focus-tab"]=true, 17 | ["mf"]=true, ["move-focus"]=true, 18 | ["mp"]=true, ["move-pane"]=true, 19 | ["swap-pane"]=true, 20 | ["fp"]=true, ["focus-pane"]=true, 21 | } 22 | 23 | local function classify_but_never_generate(arg_index, word) 24 | if arg_index == 1 and not subcommands[word] then 25 | return 1 -- Ignore this arg index. 26 | end 27 | end 28 | 29 | local single_char_flags = { "-?", "-h", "-v", "-M", "-F", "-f" } 30 | 31 | local wt = clink.argmatcher("wt") 32 | :addflags(single_char_flags, "-w"..window_args) 33 | :hideflags(single_char_flags, "-w") 34 | :_addexflags({ 35 | { "--help", "Show command line help" }, 36 | { "--version", "Display the application version" }, 37 | { "--maximized", "Launch the window maximized" }, 38 | { "--fullscreen", "Launch the window in fullscreen mode" }, 39 | { "--focus", "Launch the window in focus mode" }, 40 | { "--pos"..pos_args, " x,y", "Specify the position for the terminal" }, 41 | { "--size"..size_args, " cols,rows", "Specify the number of columns and rows for the terminal" }, 42 | { "--window"..window_args, " text", "Specify a terminal window to run the commandline in ('0' for current)" }, 43 | }) 44 | 45 | if (clink.version_encoded or 0) >= 10050014 then 46 | wt:_addexarg({ 47 | onadvance=classify_but_never_generate, 48 | { hide=true, "nt" }, 49 | { "new-tab", "Create a new tab" }, 50 | { hide=true, "sp" }, 51 | { "split-pane", "Create a new split pane" }, 52 | { hide=true, "ft" }, 53 | { "focus-tab", "Move focus to another tab" }, 54 | { hide=true, "mf" }, 55 | { "move-focus", "Move focus to the adjacent pane in the specified direction" }, 56 | { hide=true, "mp" }, 57 | { "move-pane", "Move focused pane to another tab" }, 58 | -- No alias for swap-pane. 59 | { "swap-pane", "Swap the focused pane with the adjacent pane in the specified direction" }, 60 | { hide=true, "fp" }, 61 | { "focus-pane", "Move focus to another pane" }, 62 | }) 63 | end 64 | 65 | if wt.chaincommand then 66 | wt:chaincommand("run") 67 | end 68 | -------------------------------------------------------------------------------- /completions/cf.lua: -------------------------------------------------------------------------------- 1 | -- Clink script to generate completions for Cloud Foundry CLI. 2 | -- https://github.com/cloudfoundry/cli 3 | 4 | local fullname = ... 5 | 6 | local function get_completions(matchtype, word, word_index, line_state, match_builder, user_data) -- luacheck: no unused 7 | -- Collect the command arguments. 8 | local args = "" 9 | for i = 1, line_state:getwordcount() do 10 | local info = line_state:getwordinfo(i) 11 | if info and not info.redir then 12 | if args == "" then 13 | -- Skip first non-redir word; it's the program name. 14 | args = " " 15 | else 16 | local w = line_state:getword(i) 17 | if w:sub(-1) == "\\" then 18 | -- Compensate for \" command line parsing. 19 | w = w.."\\" 20 | end 21 | args = args..' "'..w..'"' 22 | end 23 | end 24 | end 25 | 26 | -- Get completions. 27 | local command = string.format('2>nul set GO_FLAGS_COMPLETION=verbose& 2>nul "%s" %s', fullname, args) 28 | local f = io.popen(command) 29 | if f then 30 | -- Add completions to the match builder. 31 | for line in f:lines() do 32 | local mt = matchtype 33 | local w, d = line:match("^([^ ]+) +# (.*)$") 34 | w = w or line 35 | if w:sub(-1) == "\\" then 36 | mt = "dir" 37 | end 38 | if w and d then 39 | -- Include the description when available. 40 | match_builder:addmatch({ match=w, description=d }, mt) 41 | else 42 | match_builder:addmatch(w, mt) 43 | end 44 | end 45 | f:close() 46 | end 47 | 48 | return true 49 | end 50 | 51 | local function get_flag_completions(...) 52 | return get_completions("flag", ...) 53 | end 54 | 55 | local function get_word_completions(...) 56 | return get_completions("word", ...) 57 | end 58 | 59 | local name = path.getname(fullname):lower() 60 | if name == "cf.exe" or name == "cf8.exe" then 61 | local f = io.popen(string.format('2>nul findstr /m /c:code.cloudfoundry.org "%s"', fullname)) 62 | if f then 63 | local t = f:read("*a") or "" 64 | if t:lower():find("cf.exe") then 65 | -- It really is Cloud Foundry, so set up an argmatcher. 66 | local ext = path.getextension(fullname) 67 | local cf 68 | if (clink.version_encoded or 0) >= 10060017 then 69 | cf = clink.argmatcher(fullname, fullname:sub(1, #fullname - #ext)) 70 | else 71 | -- Can't use exact lookup in Clink v1.6.16 and earlier because 72 | -- typing "cf" looks for "cf.EXE" but internally argmatchers are 73 | -- always registered as lower case names. 74 | cf = clink.argmatcher("cf") 75 | end 76 | cf:setflagprefix("-") 77 | :addflags(get_flag_completions) 78 | :addarg(get_word_completions) 79 | :loop() 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /modules/path.lua: -------------------------------------------------------------------------------- 1 | local exports = {} 2 | 3 | local w = require('tables').wrap 4 | local clink_version = require('clink_version') 5 | 6 | local function isdir(name) 7 | return clink_version.new_api and os.isdir(name) or clink.is_dir(name) 8 | end 9 | 10 | exports.list_files = function (base_path, glob, recursive, reverse_separator) 11 | local mask = glob or '/*' 12 | 13 | local entries = w(clink.find_files(base_path..mask)) 14 | :filter(function(entry) 15 | return exports.is_real_dir(entry) 16 | end) 17 | 18 | local files = entries:filter(function(entry) 19 | return not isdir(base_path..'/'..entry) 20 | end) 21 | 22 | -- if 'recursive' flag is not set, we don't need to iterate 23 | -- through directories, so just return files found 24 | if not recursive then return files end 25 | 26 | local sep = reverse_separator and '/' or '\\' 27 | 28 | return entries 29 | :filter(function(entry) 30 | return isdir(base_path..'/'..entry) 31 | end) 32 | :reduce(files, function(accum, dir) 33 | -- iterate through directories and call list_files recursively 34 | return exports.list_files(base_path..'/'..dir, mask, recursive, reverse_separator) 35 | :map(function(entry) 36 | return dir..sep..entry 37 | end) 38 | :concat(accum) 39 | end) 40 | end 41 | 42 | exports.basename = function (pathname) 43 | local prefix = pathname 44 | if clink_version.supports_path_toparent then 45 | prefix = path.getname(pathname) 46 | else 47 | local i = pathname:find("[\\/:][^\\/:]*$") 48 | if i then 49 | prefix = pathname:sub(i + 1) 50 | end 51 | end 52 | return prefix 53 | end 54 | 55 | exports.pathname = function (pathname) 56 | local prefix = "" 57 | if clink_version.supports_path_toparent then 58 | -- Clink v1.1.20 and higher provide an API to do this right. 59 | local child 60 | prefix,child = path.toparent(pathname) 61 | if child == "" then 62 | -- This means it can't go up further. The old implementation 63 | -- returned "" in that case, though no callers stopped when an 64 | -- empty path was returned; they only stopped when the 65 | -- returned path equaled the input path. 66 | prefix = "" 67 | end 68 | else 69 | -- This approach has several bugs. For example, "c:/" yields "c". 70 | -- Walking up looking for .git tries "c:/.git" and then "c/.git". 71 | local i = pathname:find("[\\/:][^\\/:]*$") 72 | if i then 73 | prefix = pathname:sub(1, i-1) 74 | end 75 | end 76 | return prefix 77 | end 78 | 79 | exports.is_absolute = function (pathname) 80 | local drive = pathname:find("^%s?[%l%a]:[\\/]") 81 | if drive then return true else return false end 82 | end 83 | 84 | exports.is_metadir = function (dirname) 85 | return exports.basename(dirname) == '.' 86 | or exports.basename(dirname) == '..' 87 | end 88 | 89 | exports.is_real_dir = function (dirname) 90 | return not exports.is_metadir(dirname) 91 | end 92 | 93 | return exports 94 | -------------------------------------------------------------------------------- /coho.lua: -------------------------------------------------------------------------------- 1 | local parser = clink.arg.new_parser 2 | 3 | local repos = { 4 | -- repos names 5 | -- platforms 6 | "android", "ios", "blackberry", "windows", "wp8", "firefoxos", "osx","ubuntu", 7 | "amazon-fireos", "bada", "bada-wac", "webos", "qt", "tizen", 8 | -- plugins 9 | "plugin-battery-status", "plugin-camera", "plugin-console", "plugin-contacts", 10 | "plugin-device-motion", "plugin-device-orientation", "plugin-device", 11 | "plugin-dialogs", "plugin-file-transfer", "plugin-file", "plugin-geolocation", 12 | "plugin-globalization", "plugin-inappbrowser", "plugin-media", 13 | "plugin-media-capture", "plugin-network-information", "plugin-splashscreen", 14 | "plugin-vibration", "plugin-statusbar", "cordova-plugins", 15 | --tools 16 | "docs", "mobile-spec", "js","app-hello-world", "cli", "plugman", "lib", "common", 17 | "coho", "medic", "app-harness", "labs", "registry-web", "registry", 18 | "dist", "dist/dev", "private-pmc", "website", 19 | --repos groups 20 | "active-platform", "all", "auto", "cadence", "platform", "plugins", 21 | "release-repos", "tools" 22 | } 23 | 24 | local coho_parser = parser( 25 | { 26 | "repo-clone" .. parser( 27 | "-r" .. parser(repos), 28 | "--repo" .. parser(repos), 29 | "--chdir", "--no-chdir", 30 | "--depth" 31 | ), 32 | "repo-update" .. parser( 33 | "--chdir", "--no-chdir", 34 | "-b", "--branch", 35 | "-r" .. parser(repos), 36 | "--repo" .. parser(repos), 37 | "--fetch", 38 | "--depth", 39 | "-h", "--help" 40 | ), 41 | "repo-reset" .. parser( 42 | "--chdir", 43 | "-b", "--branch", 44 | "-r" .. parser(repos), 45 | "--repo" .. parser(repos), 46 | "-h", "--help" 47 | ), 48 | "repo-status" .. parser( 49 | "--chdir", 50 | "-b", "--branch", 51 | "-r" .. parser(repos), 52 | "--repo" .. parser(repos), 53 | "--branch2", 54 | "--diff", 55 | "-h", "--help" 56 | ), 57 | "repo-push", 58 | "list-repos", 59 | -- release management 60 | "prepare-release-branch", 61 | "tag-release", 62 | "audit-license-headers", 63 | "check-license", 64 | "create-archive"..parser( 65 | '-r'..parser(repos), 66 | '--repo'..parser(repos), 67 | '--dest' 68 | ), 69 | "verify-archive", 70 | "print-tags"..parser( 71 | '-r'..parser(repos), 72 | '--repo'..parser(repos), 73 | '--tag' -- TODO: get tags based on dir and functionality from git.lua 74 | ), 75 | "verify-tags", 76 | "list-release-urls", 77 | "nightly", 78 | "npm-publish-tag", 79 | "update-release-notes", 80 | "npm-unpublish-nightly", 81 | -- other commands 82 | "list-pulls", 83 | "last-week", 84 | "shortlog", 85 | "for-each", 86 | "npm-link", 87 | "create-pr", 88 | "merge-pr"..parser("--pr") 89 | }, 90 | "--chdir", 91 | "--no-chdir", 92 | "-h" 93 | ) 94 | 95 | clink.arg.register_parser("coho", coho_parser) 96 | -------------------------------------------------------------------------------- /cordova.lua: -------------------------------------------------------------------------------- 1 | --preamble: common routines 2 | 3 | local matchers = require('matchers') 4 | 5 | local platforms = matchers.create_dirs_matcher('platforms/*') 6 | local plugins = matchers.create_dirs_matcher('plugins/*') 7 | 8 | -- end preamble 9 | 10 | local parser = clink.arg.new_parser 11 | 12 | local platform_add_parser = parser({ 13 | "wp8", 14 | "windows", 15 | "android", 16 | "blackberry10", 17 | "firefoxos", 18 | matchers.dirs 19 | }, "--usegit", "--save", "--link"):loop(1) 20 | 21 | local plugin_add_parser = parser({matchers.dirs, 22 | "cordova-plugin-battery-status", 23 | "cordova-plugin-camera", 24 | "cordova-plugin-contacts", 25 | "cordova-plugin-device", 26 | "cordova-plugin-device-motion", 27 | "cordova-plugin-device-orientation", 28 | "cordova-plugin-dialogs", 29 | "cordova-plugin-file", 30 | "cordova-plugin-file-transfer", 31 | "cordova-plugin-geolocation", 32 | "cordova-plugin-globalization", 33 | "cordova-plugin-inappbrowser", 34 | "cordova-plugin-media", 35 | "cordova-plugin-media-capture", 36 | "cordova-plugin-network-information", 37 | "cordova-plugin-splashscreen", 38 | "cordova-plugin-statusbar", 39 | "cordova-plugin-test-framework", 40 | "cordova-plugin-vibration" 41 | }, 42 | "--searchpath" ..parser({matchers.dirs}), 43 | "--noregistry", 44 | "--link", 45 | "--save", 46 | "--shrinkwrap" 47 | ):loop(1) 48 | 49 | local platform_rm_parser = parser({platforms}, "--save"):loop(1) 50 | local plugin_rm_parser = parser({plugins}, "-f", "--force", "--save"):loop(1) 51 | 52 | local cordova_parser = parser( 53 | { 54 | -- common commands 55 | "create" .. parser( 56 | "--copy-from", "--src", 57 | "--link-to" 58 | ), 59 | "help", 60 | -- project-level commands 61 | "info", 62 | "platform" .. parser({ 63 | "add" .. platform_add_parser, 64 | "remove" .. platform_rm_parser, 65 | "rm" .. platform_rm_parser, 66 | "list", "ls", 67 | "up" .. parser({platforms}):loop(1), 68 | "update" .. parser({platforms}, "--usegit", "--save"):loop(1), 69 | "check" 70 | }), 71 | "plugin" .. parser({ 72 | "add" .. plugin_add_parser, 73 | "remove" .. plugin_rm_parser, 74 | "rm" .. plugin_rm_parser, 75 | "list", "ls", 76 | "search" 77 | }, "--browserify"), 78 | "prepare" .. parser({platforms}, "--browserify"):loop(1), 79 | "compile" .. parser({platforms}, 80 | "--browserify", 81 | "--debug", "--release", 82 | "--device", "--emulator", "--target="):loop(1), 83 | "build" .. parser({platforms}, 84 | "--browserify", 85 | "--debug", "--release", 86 | "--device", "--emulator", "--target="):loop(1), 87 | "run" .. parser({platforms}, 88 | "--browserify", 89 | "--nobuild", 90 | "--debug", "--release", 91 | "--device", "--emulator", "--target="), 92 | "emulate" .. parser({platforms}), 93 | "serve", 94 | }, "-h", 95 | "-v", "--version", 96 | "-d", "--verbose") 97 | 98 | clink.arg.register_parser("cordova", cordova_parser) 99 | clink.arg.register_parser("cordova-dev", cordova_parser) 100 | -------------------------------------------------------------------------------- /spec/color_spec.lua: -------------------------------------------------------------------------------- 1 | 2 | local color = require('color') 3 | 4 | describe("color module", function() 5 | 6 | it("should define color constants", function() 7 | assert.are.equals(color.BLACK, 0) 8 | assert.are.equals(color.RED, 1) 9 | assert.are.equals(color.GREEN, 2) 10 | assert.are.equals(color.YELLOW, 3) 11 | assert.are.equals(color.BLUE, 4) 12 | assert.are.equals(color.MAGENTA, 5) 13 | assert.are.equals(color.CYAN, 6) 14 | assert.are.equals(color.WHITE, 7) 15 | assert.are.equals(color.DEFAULT, 9) 16 | assert.are.equals(color.BOLD, 1) 17 | end) 18 | 19 | it("should export methods", function() 20 | assert.are.equal(type(color.set_color), 'function') 21 | assert.are.equal(type(color.get_clink_color), 'function') 22 | assert.are.equal(type(color.color_text), 'function') 23 | end) 24 | 25 | describe("'set_color' method", function () 26 | 27 | local VALID_COLOR_STRING = "^\x1b%[(3%d);(%d%d?);(4%d)m$" 28 | 29 | it("should accept numeric arguments and return string", function () 30 | assert.are.equal(type(color.set_color(1, 2, 3)), "string") 31 | end) 32 | 33 | it("should accept nil arguments and still return string", function () 34 | assert.are.equal(type(color.set_color()), "string") 35 | end) 36 | 37 | it("should throw if first two arguments are not a numbers or nil", function () 38 | assert.has.error(function() color.set_color('a', 2, 3) end) 39 | assert.has.error(function() color.set_color(1, 'a', 3) end) 40 | end) 41 | 42 | it("should throw if arguments is out of range", function () 43 | assert.has.error(function() color.set_color(100, 1, 1) end) 44 | assert.has.error(function() color.set_color(1, 200, 1) end) 45 | end) 46 | 47 | it("should return valid ANSI color code", function () 48 | -- TODO: either find appropriate assert or invent custom one 49 | -- assert_match(VALID_COLOR_STRING, color.set_color(3, 2, 1)) 50 | end) 51 | 52 | it("should set color to DEFAULT if no corresponding argument was passed", function () 53 | local _, _, fore, bold, back = string.find(color.set_color(), VALID_COLOR_STRING) 54 | assert.are.equals(fore, "39"); 55 | assert.are.equals(back, "49"); 56 | assert.are.equals(bold, "22"); 57 | end) 58 | end) 59 | 60 | describe("'get_clink_color' method", function () 61 | 62 | it("should do nothing since Clink support is only available in actual Clink", function () 63 | assert.are.equals(color.get_clink_color('color.git.star'), "") 64 | end) 65 | end) 66 | 67 | describe("'color_text' method", function () 68 | 69 | local TEST_STRING = "abc" 70 | local VALID_COLOR_STRING = "\x1b%[3%d;%d%d?;4%dm" 71 | 72 | it("should wrap string into valid ANSI codes", function () 73 | -- TODO: either find appropriate assert or invent custom one 74 | -- assert_match("^"..VALID_COLOR_STRING..TEST_STRING..VALID_COLOR_STRING.."$", 75 | -- color.color_text(TEST_STRING, 1, 2, 3)) 76 | end) 77 | 78 | it("should reset color to default", function () 79 | local _,_, color_suffix = string.find(color.color_text(TEST_STRING, 1, 2, 3), 80 | "^"..VALID_COLOR_STRING..TEST_STRING.."("..VALID_COLOR_STRING..")$") 81 | assert.are.equals(color_suffix, color.set_color()) 82 | end) 83 | end) 84 | end) 85 | -------------------------------------------------------------------------------- /completions/less.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Clink argmatcher for LESS 3 | -- 4 | -- Info: http://www.greenwoodsoftware.com/less 5 | -- Repo: https://github.com/gwsw/less.git 6 | 7 | local clink_version = require('clink_version') 8 | if not clink_version.supports_argmatcher_delayinit then 9 | log.info("less.lua argmatcher requires a newer version of Clink; please upgrade.") 10 | return 11 | end 12 | 13 | local inited 14 | 15 | local function init(argmatcher) 16 | if inited then 17 | return 18 | end 19 | inited = true 20 | 21 | local file = io.popen('2>nul less --help') 22 | if not file then 23 | return 24 | end 25 | 26 | local section = 'header' 27 | local flags = {} 28 | local descriptions = {} 29 | local pending 30 | 31 | for line in file:lines() do 32 | line = line:gsub('.\x08', '') 33 | if section == 'header' then 34 | if line:match('^ +OPTIONS') then 35 | section = 'options' 36 | end 37 | elseif section == 'options' then 38 | if line:find('^ *%-%-%-%-%-%-%-%-%-%-') then 39 | section = 'done' 40 | elseif pending then 41 | local desc = line:match('^[ \t]+([^ \t].*)$') 42 | if desc then 43 | local args 44 | desc = desc:gsub('%.+$', '') 45 | for _,f in ipairs(pending) do 46 | local display = f:match('%=(.+)$') or f:match('( .+)$') 47 | if display then 48 | if not args then 49 | if display:find('file') then 50 | args = clink.argmatcher():addarg(clink.filematches) 51 | else 52 | args = clink.argmatcher():addarg({fromhistory=true}) 53 | end 54 | end 55 | f = f:sub(1, #f - #display) 56 | if args then 57 | table.insert(flags, f .. args) 58 | else 59 | table.insert(flags, f) 60 | end 61 | descriptions[f] = { display, desc } 62 | else 63 | table.insert(flags, f) 64 | descriptions[f] = { desc } 65 | end 66 | end 67 | end 68 | pending = nil 69 | elseif line:match('^ +%-') then 70 | line = line:gsub('^ +', '') 71 | while true do 72 | local f = line:match('^(%-.-) ') or line:match('^(%-%-[^ ]+)$') 73 | if not f then 74 | break 75 | end 76 | line = line:sub(#f + 1):gsub('^[ .]+', '') 77 | 78 | pending = pending or {} 79 | f = f:gsub(' +$', '') 80 | table.insert(pending, f) 81 | end 82 | end 83 | else -- luacheck: ignore 542 84 | -- Nothing to do. 85 | end 86 | end 87 | 88 | file:close() 89 | 90 | argmatcher:addflags(flags) 91 | argmatcher:adddescriptions(descriptions) 92 | end 93 | 94 | local a = clink.argmatcher('less') 95 | if a.setdelayinit then 96 | a:setdelayinit(init) 97 | else 98 | init(a) 99 | end 100 | -------------------------------------------------------------------------------- /completions/doskey.lua: -------------------------------------------------------------------------------- 1 | require('arghelper') 2 | local clink_version = require('clink_version') 3 | 4 | local function exe_matches_all(word, word_index, line_state, match_builder) -- luacheck: no unused args 5 | match_builder:addmatch({ match="all", display="\x1b[1mALL" }) 6 | match_builder:addmatch({ match="cmd.exe", display="\x1b[1mCMD.EXE" }) 7 | match_builder:addmatches(clink.filematches("")) 8 | end 9 | 10 | local function exe_matches(word, word_index, line_state, match_builder) -- luacheck: no unused args 11 | match_builder:addmatch({ match="cmd.exe", display="\x1b[1mCMD.EXE" }) 12 | match_builder:addmatches(clink.filematches("")) 13 | end 14 | 15 | local function has_equal_sign(arg_index, word_index, line_state) 16 | if arg_index == 1 then 17 | local x = line_state:getwordinfo(word_index) 18 | local y = line_state:getwordinfo(word_index + 1) 19 | if x and y then 20 | local line = line_state:getline() 21 | local s = line:sub(x.offset + x.length, y.offset - 1) 22 | return s:find("=") and true 23 | end 24 | end 25 | end 26 | 27 | local onlink_parsers = {} 28 | local function chain_if_equal_sign(_, arg_index, _, word_index, line_state) 29 | if has_equal_sign(arg_index, word_index, line_state) then 30 | if not onlink_parsers.chain then 31 | onlink_parsers.chain = clink.argmatcher():chaincommand() 32 | end 33 | return onlink_parsers.chain 34 | else 35 | if not onlink_parsers.nofiles then 36 | onlink_parsers.nofiles = clink.argmatcher():nofiles() 37 | end 38 | return onlink_parsers.nofiles 39 | end 40 | end 41 | 42 | -- luacheck: no max line length 43 | local doskey = clink.argmatcher("doskey") 44 | :_addexflags({ 45 | {"/reinstall", "Installs a new copy of Doskey"}, 46 | {"/macros", "Display all Doskey macros for the current executable"}, 47 | {"/macros:"..clink.argmatcher():addarg(exe_matches_all), "Display all Doskey macros for the named executable ('ALL' for all executables)"}, 48 | {"/exename="..clink.argmatcher():addarg(exe_matches), "Specifies the executable"}, 49 | {"/macrofile=", "Specifies a file of macros to install"}, 50 | }) 51 | :addarg({onlink=chain_if_equal_sign}) 52 | 53 | if not clink_version.supports_argmatcher_onlink then 54 | 55 | local function require_equal_sign(arg_index, _, word_index, line_state, classifications) 56 | if arg_index == 1 then 57 | local x = line_state:getwordinfo(word_index) 58 | local y = line_state:getwordinfo(word_index + 1) 59 | if x and y then 60 | local line = line_state:getline() 61 | local s = line:sub(x.offset + x.length, y.offset - 1) 62 | if not s:find("=") then 63 | local color = settings.get("color.unexpected") or "" 64 | local delta = s:find("[ \t]") 65 | delta = delta and (delta - 1) or #s 66 | local lastinfo = line_state:getwordinfo(line_state:getwordcount()) 67 | local endoffset = lastinfo.offset + lastinfo.length 68 | local tailoffset = x.offset + x.length + delta 69 | if endoffset > tailoffset then 70 | local tail = line:sub(endoffset):match("^([^&|]+)[&|]?.*$") or "" 71 | endoffset = endoffset + #tail 72 | end 73 | classifications:applycolor(tailoffset, endoffset - tailoffset, color, true) 74 | end 75 | end 76 | end 77 | end 78 | 79 | doskey:chaincommand() 80 | doskey:setclassifier(require_equal_sign) 81 | 82 | end 83 | 84 | -------------------------------------------------------------------------------- /modules/funclib.lua: -------------------------------------------------------------------------------- 1 | 2 | local exports = {} 3 | 4 | --- Implementation of table.filter function. Applies filter function to each 5 | -- element of table and returns a new table with values for which filter 6 | -- returns 'true'. 7 | -- 8 | -- @param tbl a table to filter. Default is an empty table. 9 | -- @param filter function that accepts an element of table, specified in the 10 | -- first argument and returns either 'true' or 'false'. If not specified, 11 | -- then default function is used that returns its argument. 12 | -- 13 | -- @return a new table with values that are not filtered out by 'filter' function. 14 | exports.filter = function (tbl, filter) 15 | if not tbl then return {} end 16 | if not filter then filter = function(v) return v end end 17 | local ret = {} 18 | for _,v in ipairs(tbl) do 19 | if filter(v) then table.insert(ret, v) end 20 | end 21 | return ret 22 | end 23 | 24 | --- Implementation of table.map function. Applies filter function to each 25 | -- element of table and returns a new table with values returned by mapper 26 | -- function. 27 | -- 28 | -- @param tbl a table to filter. Default is an empty table. 29 | -- @param map_func function that accepts an element of table, specified in the 30 | -- first argument and returns a new value for resultant table. If not 31 | -- specified, then 'map' function returns it input table. 32 | -- 33 | -- @return a new table with values produced by 'map_func'. 34 | exports.map = function (tbl, map_func) 35 | assert(tbl == nil or type(tbl) == "table", 36 | "First argument must be either table or nil") 37 | 38 | assert(map_func == nil or type(map_func) == "function", 39 | "Second argument must be either function or nil") 40 | 41 | if tbl == nil then return {} end 42 | if not map_func then return tbl end 43 | local ret = {} 44 | for _,v in ipairs(tbl) do 45 | table.insert(ret, map_func(v)) 46 | end 47 | return ret 48 | end 49 | 50 | --- Implementation of table.reduce function. Iterates through table and calls 51 | -- 'func' function passing an accumulator and an entry from the original 52 | -- table. The result of table is stored in accumulator and passed to next 53 | -- 'func' call. 54 | -- 55 | -- @param accum an accumulator, initial value that will be passed to first 56 | -- 'func' call. 57 | -- @param tbl a table to reduce. Default is an empty table. 58 | -- @param func function that accepts two params: an accumulator and an element 59 | -- of table, specified in the first argument and returns a new value for 60 | -- accumulator. 61 | -- 62 | -- @return a resultant accumulator value. 63 | exports.reduce = function (accum, tbl, func) 64 | assert(type(func) == "function", 65 | "Third argument must be a function") 66 | 67 | if not tbl then return accum end 68 | for _,v in ipairs(tbl) do 69 | accum = func(accum, v) 70 | end 71 | return accum 72 | end 73 | 74 | --- Concatenates any number of input values into one table. If input parameter is 75 | -- a table then its values is copied to the end of resultant table. If the 76 | -- parameter is single value, then it is appended to the resultant table. If 77 | -- the input value is 'nil', then it is omitted. 78 | -- 79 | -- @return a result of concatenation. The result is always a table. 80 | exports.concat = function (...) 81 | local input = {...} 82 | local ret = {} 83 | local i = 1 84 | 85 | while i <= #input do 86 | local arg = input[i] 87 | if type(arg) == 'table' then 88 | for _,v in ipairs(arg) do 89 | table.insert(ret, v) 90 | end 91 | elseif arg ~= nil then 92 | table.insert(ret, arg) 93 | end 94 | i = i + 1 95 | end 96 | 97 | return ret 98 | end 99 | 100 | return exports 101 | -------------------------------------------------------------------------------- /completions/attrib.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Usage: 3 | -- 4 | -- Argmatcher for ATTRIB. Uses delayinit to support localized help text. 5 | 6 | -------------------------------------------------------------------------------- 7 | local clink_version = require('clink_version') 8 | if not clink_version.supports_argmatcher_delayinit then 9 | log.info("attrib.lua argmatcher requires a newer version of Clink; please upgrade.") 10 | return 11 | end 12 | 13 | -------------------------------------------------------------------------------- 14 | local function add_pending(pending, flags, descriptions, hideflags) 15 | if pending then 16 | table.insert(flags, pending.flag) 17 | table.insert(hideflags, pending.flag:lower()) 18 | local desc = pending.desc:gsub('[ .]+$', '') 19 | descriptions[pending.flag] = { desc } 20 | end 21 | end 22 | 23 | -------------------------------------------------------------------------------- 24 | local function make_desc(lhs, rhs) 25 | if rhs:match('^[A-Z][a-z ]') then 26 | rhs = rhs:sub(1, 1):lower() .. rhs:sub(2) 27 | end 28 | return lhs .. rhs:gsub('[ .]+$', '') 29 | end 30 | 31 | -------------------------------------------------------------------------------- 32 | local inited 33 | 34 | -------------------------------------------------------------------------------- 35 | local function delayinit(argmatcher) 36 | if inited then 37 | return 38 | end 39 | inited = true 40 | 41 | local f = io.popen('2>nul attrib /?') 42 | if not f then 43 | return 44 | end 45 | 46 | local flags = {} 47 | local descriptions = {} 48 | local hideflags = {} 49 | local pending 50 | 51 | local section = 'header' 52 | for line in f:lines() do 53 | if unicode.fromcodepage then -- luacheck: no global 54 | line = unicode.fromcodepage(line) -- luacheck: no global 55 | end 56 | if section == 'attrs' then 57 | local attr, desc = line:match('^ +([A-Z]) +([^ ].+)$') 58 | if attr then 59 | table.insert(flags, '+'..attr) 60 | table.insert(flags, '-'..attr) 61 | table.insert(hideflags, '+'..attr:lower()) 62 | table.insert(hideflags, '-'..attr:lower()) 63 | descriptions['+'..attr] = { make_desc('Set ', desc) } 64 | descriptions['-'..attr] = { make_desc('Clear ', desc) } 65 | elseif line:match('^ +%[') then 66 | section = 'flags' 67 | end 68 | elseif section == 'flags' then 69 | local indent, flag, pad, desc = line:match('^( +)(/[^ ]+)( +)([^ ].*)$') 70 | if flag then 71 | add_pending(pending, flags, descriptions, hideflags) 72 | pending = {} 73 | pending.indent = #indent + #flag + #pad 74 | pending.flag = flag 75 | pending.desc = desc:gsub(' +$', '') 76 | elseif pending then 77 | indent, desc = line:match('^( +)([^ ].*)$') 78 | if indent and #indent == (pending.indent or 0) then 79 | pending.desc = pending.desc .. ' ' .. desc:gsub(' +$', '') 80 | else 81 | add_pending(pending, flags, descriptions, hideflags) 82 | pending = nil 83 | end 84 | else 85 | add_pending(pending, flags, descriptions, hideflags) 86 | pending = nil 87 | end 88 | elseif section == 'header' then 89 | if line:match('^ +%+ +') then 90 | section = 'attrs' 91 | end 92 | end 93 | end 94 | add_pending(pending, flags, descriptions, hideflags) 95 | 96 | f:close() 97 | 98 | argmatcher:addflags(flags) 99 | argmatcher:addflags(hideflags) 100 | argmatcher:adddescriptions(descriptions) 101 | argmatcher:hideflags(hideflags) 102 | end 103 | 104 | -------------------------------------------------------------------------------- 105 | clink.argmatcher('attrib'):setdelayinit(delayinit) 106 | -------------------------------------------------------------------------------- /completions/nuke.lua: -------------------------------------------------------------------------------- 1 | -- This script generates completions for Nuke.Build. 2 | -- 3 | -- However, this does not apply input line coloring. There isn't a good way to 4 | -- support input line coloring, because there's no documentation about 5 | -- Nuke.Build's command line interface. The ":complete" command lists 6 | -- completions based on the entire input line before that point, but reveals 7 | -- nothing about why those are the completions for that specific argument 8 | -- position in that specific command line. 9 | -- 10 | -- The ":complete" command is designed to be easy to hook into bash, zsh, and 11 | -- fish but the side effect is it doesn't support features like input line 12 | -- coloring or descriptions for matches. 13 | 14 | if (clink.version_encoded or 0) < 10030037 then 15 | print("nuke.lua requires a newer version of Clink; please upgrade.") 16 | return 17 | end 18 | 19 | -- Prepare the input line for passing safely as an argument to "nuke :complete". 20 | local function sanitize_line(line_state) 21 | local text = "" 22 | 23 | local function sanitize_word(index, info) 24 | local end_offset = info.offset + info.length - 1 25 | if end_offset < info.offset and index == line_state:getwordcount() then 26 | end_offset = line_state:getcursor() - 1 27 | end 28 | 29 | local word = line_state:getline():sub(info.offset, end_offset) 30 | word = word:gsub('"', '\\"') 31 | return word 32 | end 33 | 34 | for i = 1, line_state:getwordcount() do 35 | local info = line_state:getwordinfo(i) 36 | local word 37 | if info.alias then 38 | word = "nuke" 39 | elseif not info.redir then 40 | word = sanitize_word(i, info) 41 | end 42 | if word then 43 | if #text > 0 then 44 | text = text .. " " 45 | end 46 | text = text .. word 47 | end 48 | end 49 | 50 | return text 51 | end 52 | 53 | -- Run "nuke :complete" with the sanitized input line to get completions. The 54 | -- 'filter' argument here enables differentiating between flags vs arguments in 55 | -- the nuke command. 56 | local function nuke_complete(line_state, builder, filter) 57 | local matches = {} 58 | 59 | -- Run 'nuke :complete' to get completions. 60 | local nuke = '"nuke"' 61 | local commandline = sanitize_line(line_state) 62 | local command = '2>nul '..nuke..' :complete "'..commandline..'"' 63 | local f = io.popen(command) 64 | if f then 65 | for line in f:lines() do 66 | line = line:gsub('"', '') 67 | if line ~= "" and line:find(filter) then 68 | -- Add non-blank words to the list of completion matches. 69 | table.insert(matches, line) 70 | end 71 | end 72 | f:close() 73 | end 74 | 75 | -- Mark the matches volatile even when generation was skipped due to 76 | -- running in a coroutine. Otherwise it'll never run it in the main 77 | -- coroutine, either. 78 | builder:setvolatile() 79 | 80 | -- Enable quoting. 81 | if builder.setforcequoting then 82 | builder:setforcequoting() 83 | end 84 | 85 | return matches 86 | end 87 | 88 | local function nuke_complete_flags(word, index, line_state, builder) -- luacheck: no unused args 89 | -- Filter completions to only include flags. 90 | return nuke_complete(line_state, builder, '^%-') 91 | end 92 | 93 | local function nuke_complete_nonflags(word, index, line_state, builder) -- luacheck: no unused args 94 | -- Filter completions to exclude flags. 95 | return nuke_complete(line_state, builder, '^[^-]') 96 | end 97 | 98 | clink.argmatcher("nuke") 99 | :addflags(nuke_complete_flags) 100 | :addarg(nuke_complete_nonflags) 101 | :setflagprefix("-") 102 | :loop() 103 | 104 | -- Apply the flag color to every word that starts with "--". This doesn't 105 | -- produce accurate input line coloring, but at least it makes the input line a 106 | -- little more readable. 107 | local clf = clink.classifier(1) 108 | function clf:classify(commands) -- luacheck: no unused 109 | for _, c in ipairs(commands) do 110 | local ls = c.line_state 111 | for i = 1, ls:getwordcount() do 112 | local word = ls:getword(i) 113 | if word:sub(1, 2) == "--" then 114 | c.classifications:classifyword(i, 'f') 115 | end 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /completions/ping.lua: -------------------------------------------------------------------------------- 1 | require('arghelper') 2 | local w = require('tables').wrap 3 | local clink_version = require('clink_version') 4 | 5 | -- Hosts from the .ssh/config file use `color.alias`. 6 | -- Hosts from the .ssh/known_hosts use `color.cmd`. 7 | -- Hosts from the hosts file use default color. 8 | 9 | local arg = clink.argmatcher():addarg() 10 | local host_list = clink.argmatcher():addarg({fromhistory=true}) 11 | local src_addr = clink.argmatcher():addarg({fromhistory=true}) 12 | 13 | local function read_lines (filename) 14 | local lines = w({}) 15 | local f = io.open(filename) 16 | if not f then 17 | return lines 18 | end 19 | 20 | for line in f:lines() do 21 | table.insert(lines, line) 22 | end 23 | 24 | f:close() 25 | return lines 26 | end 27 | 28 | local function extract_address(pattern, match_type, portflag) 29 | if not pattern then 30 | return nil 31 | end 32 | 33 | local addr, port = pattern:match('%[([^%]]+)%]:(%d+)') 34 | if not addr then 35 | addr = pattern:match('%[([^%]]+)%]') 36 | end 37 | if not addr then 38 | addr = pattern 39 | end 40 | 41 | local match 42 | if portflag and port then 43 | match = addr .. portflag .. port 44 | else 45 | match = addr 46 | end 47 | if clink_version.supports_display_filter_description then 48 | return { match=match, type=match_type } 49 | else 50 | return match 51 | end 52 | end 53 | 54 | -- read all Host entries in the user's ssh config file 55 | local function list_ssh_hosts(portflag) 56 | local matches = w({}) 57 | local lines = read_lines(clink.get_env("userprofile") .. "/.ssh/config") 58 | for _, line in ipairs(lines) do 59 | line = line:gsub('(#.*)$', '') 60 | local host = line:match('^Host%s+(.*)$') 61 | if host then 62 | for pattern in host:gmatch('([^%s]+)') do 63 | if not pattern:match('[%*%?/!]') then 64 | table.insert(matches, extract_address(pattern, 'alias', portflag)) 65 | end 66 | end 67 | end 68 | end 69 | return matches:filter() 70 | end 71 | 72 | local function list_known_hosts(portflag) 73 | return read_lines(clink.get_env("userprofile") .. "/.ssh/known_hosts") 74 | :map(function (line) 75 | line = line:gsub('(#.*)$', '') 76 | return extract_address(line:match('^([^%s,]*).*'), 'cmd', portflag) 77 | end) 78 | :filter() 79 | end 80 | 81 | local function list_hosts_file() 82 | local t = w({}) 83 | local lines = read_lines(os.getenv("systemroot") .. "/system32/drivers/etc/hosts") 84 | for _, line in ipairs(lines) do 85 | line = line:gsub('(#.*)$', '') 86 | local ip, hosts = line:match('^%s*([0-9.:]+)%s(.*)$') 87 | if ip then 88 | table.insert(t, ip) 89 | for _, host in ipairs(string.explode(hosts)) do 90 | table.insert(t, host) 91 | end 92 | end 93 | end 94 | return t:filter() 95 | end 96 | 97 | local function hosts(token) -- luacheck: no unused args 98 | return list_ssh_hosts() 99 | :concat(list_known_hosts()) 100 | :concat(list_hosts_file()) 101 | end 102 | 103 | -- luacheck: no max line length 104 | clink.argmatcher("ping") 105 | :addarg({hosts}) 106 | :_addexflags({ 107 | {"-t", "Ping the specified host until stopped"}, 108 | {"-a", "Resolve addresses to hostnames"}, 109 | {"-n"..arg, " count", "Number of echo requests to send"}, 110 | {"-l"..arg, " size", "Send buffer size"}, 111 | {"-f", "Set Don't Fragment flag in packet (IPv4-only)"}, 112 | {"-i"..arg, " TTL", "Time To Live"}, 113 | {"-v"..arg, " TOS", "Deprecated; Type of Service (IPv4-only)"}, 114 | {"-r"..arg, " count", "Record route for count hops (IPv4-only)"}, 115 | {"-s"..arg, " count", "Timestamp for count hops (IPv4-only)"}, 116 | {"-j"..host_list, " host-list", "Loose source route along host-list (IPv4-only)"}, 117 | {"-k"..host_list, " host-list", "Strict source route along host-list (IPv4-only)"}, 118 | {"-w"..arg, " timeout", "Timeout in milliseconds to wait for each reply"}, 119 | {"-R", "Deprecated; Use routing header to test reverse route also (IPv4-only)"}, 120 | {"-S"..src_addr, " srcaddr", "Source address to use"}, 121 | {"-c"..arg, " compartment", "Routing compartment identifier"}, 122 | {"-p", "Ping a Hyper-V Network Virtualization provider address"}, 123 | {"-4", "Force using IPv4"}, 124 | {"-6", "Force using IPv6"}, 125 | }) 126 | 127 | -------------------------------------------------------------------------------- /vagrant.lua: -------------------------------------------------------------------------------- 1 | local matchers = require('matchers') 2 | local path = require('path') 3 | local parser = clink.arg.new_parser 4 | 5 | local boxes = matchers.create_dirs_matcher(clink.get_env("userprofile") .. "/.vagrant.d/boxes/*") 6 | 7 | local function is_empty(s) 8 | return s == nil or s == '' 9 | end 10 | 11 | local function find_vagrantfile(start_dir) 12 | local vagrantfile_name = clink.get_env("VAGRANT_VAGRANTFILE") 13 | if is_empty(vagrantfile_name) then vagrantfile_name = "Vagrantfile" end 14 | 15 | local function has_vagrantfile(dir) 16 | return #clink.find_files(dir .. "./" .. vagrantfile_name .. "*") > 0 17 | end 18 | 19 | if not start_dir or start_dir == '.' then start_dir = clink.get_cwd() end 20 | 21 | if has_vagrantfile(start_dir) then return io.open(start_dir.."\\"..vagrantfile_name) end 22 | 23 | local parent_path = path.pathname(start_dir) 24 | if parent_path ~= start_dir then return find_vagrantfile(parent_path) end 25 | end 26 | 27 | local function get_vagrantfile() 28 | local vagrant_cwd = clink.get_env("VAGRANT_CWD") 29 | if not is_empty(vagrant_cwd) then 30 | return find_vagrantfile(vagrant_cwd) 31 | else 32 | return find_vagrantfile() 33 | end 34 | end 35 | 36 | local function delete_ruby_comment(line) 37 | if line == nil then return nil end 38 | local index = string.find(line, '#') 39 | if index and index > 0 then 40 | return string.sub(line, 0, index-1) 41 | end 42 | return line 43 | end 44 | 45 | local get_provisions = function () 46 | local vagrant_file = get_vagrantfile() 47 | if vagrant_file == nil then return {} end 48 | 49 | local provisions = {} 50 | for line in vagrant_file:lines() do 51 | line = delete_ruby_comment(line) 52 | if not is_empty(line) then 53 | local provision_name = line:match('.vm.provision[ \r\t]+[\"|\']([A-z]+[A-z0-9|-]*)[\"|\']') 54 | 55 | if not is_empty(provision_name) then 56 | table.insert(provisions, provision_name) 57 | end 58 | end 59 | end 60 | vagrant_file:close() 61 | return provisions 62 | end 63 | 64 | local vagrant_parser = parser({ 65 | "box" .. parser({ 66 | "add" .. parser( 67 | "--checksum", 68 | "--checksum-type" .. parser({"md5", "sha1", "sha256"}), 69 | "-c", "--clean", 70 | "-f", "--force", 71 | "--insecure", 72 | "--cacert", 73 | "--cert", 74 | "--provider" 75 | ), 76 | "list" .. parser("-i", "--box-info"), 77 | "outdated"..parser("--global", "-h", "--help"), 78 | "remove" .. parser({boxes}), 79 | "repackage" .. parser({boxes}), 80 | "update" 81 | }), 82 | "connect", 83 | "destroy" .. parser("-f", "--force"), 84 | "global-status", 85 | "halt" .. parser("-f", "--force"), 86 | "init" .. parser({boxes}, {}, "--output"), 87 | "package" .. parser("--base", "--output", "--include", "--vagrantfile"), 88 | "plugin" .. parser({ 89 | "install" .. parser( 90 | "--entry-point", 91 | "--plugin-prerelease", 92 | "--plugin-source", 93 | "--plugin-version" 94 | ), 95 | "license", 96 | "list", 97 | "uninstall", 98 | "update" .. parser( 99 | "--entry-point", 100 | "--plugin-prerelease", 101 | "--plugin-source", 102 | "--plugin-version" 103 | ) 104 | }), 105 | "provision" .. parser("--provision-with" .. parser({get_provisions}), "--no-parallel", "--parallel"), 106 | "reload" .. parser("--provision-with" .. parser({get_provisions}), "--no-parallel", "--parallel"), 107 | "resume", 108 | "ssh" .. parser("-c", "--command", "-p", "--plain") , 109 | "ssh-config", 110 | "snapshot" .. parser({ 111 | "push", 112 | "pop" .. parser( 113 | "--provision", 114 | "--no-provision", 115 | "--no-delete"), 116 | "save", 117 | "restore" .. parser( 118 | "--provision", 119 | "--no-provision"), 120 | "list", 121 | "delete"}), 122 | "status", 123 | "suspend", 124 | "up" .. parser( 125 | "--provision", 126 | "--no-provision", 127 | "--provision-with" .. parser({get_provisions}), 128 | "--destroy-on-error", 129 | "--no-destroy-on-error", 130 | "--parallel", 131 | "--no-parallel", 132 | "--provider" 133 | ) 134 | }, "-h", "--help", "-v", "--version") 135 | 136 | local help_parser = parser({ 137 | "help" .. parser(vagrant_parser:flatten_argument(1)) 138 | }) 139 | 140 | clink.arg.register_parser("vagrant", vagrant_parser) 141 | clink.arg.register_parser("vagrant", help_parser) -------------------------------------------------------------------------------- /pipenv.lua: -------------------------------------------------------------------------------- 1 | local matchers = require("matchers") 2 | local w = require("tables").wrap 3 | 4 | local parser = clink.arg.new_parser 5 | 6 | local function pipenv_libs_list(token) 7 | local result = "" 8 | local handle = io.popen('2>nul python -c "import sys; print(\\";\\".join(sys.path))"') 9 | if handle then 10 | result = handle:read("*a") 11 | handle:close() 12 | end 13 | 14 | -- trim spaces 15 | result = clink.get_cwd() .. result:gsub("^%s*(.-)%s*$", "%1") 16 | 17 | local lib_paths = clink.split(result, ";") 18 | 19 | local list = w() 20 | for _,lib_path in ipairs(lib_paths) do 21 | lib_path = lib_path .. "\\" 22 | local finder = matchers.create_files_matcher(lib_path .. "*") 23 | local libs = finder(token) 24 | for _,v in ipairs(libs) do 25 | local ext = path.getextension(v):lower() 26 | if ext == ".py" then 27 | v = v:sub(1, #v - #ext) 28 | table.insert(list, v) 29 | elseif ext == ".dist-info" then 30 | if clink.is_dir(lib_path .. "/" .. v) then 31 | local tmp = v:sub(1, #v - #ext) 32 | if tmp:match("%-%d[%d%.]+$") then 33 | v = tmp:gsub("%-%d[%d%.]+$", "") 34 | table.insert(list, v) 35 | end 36 | end 37 | end 38 | end 39 | end 40 | 41 | return list 42 | end 43 | 44 | local pipenv_default_flags = { 45 | "--python", 46 | "--three", 47 | "--two", 48 | "--clear", 49 | "--verbose", 50 | "-v", 51 | "--pypi-mirror", 52 | "--help", 53 | "-h" 54 | } 55 | 56 | local pipenv_check_parser = parser():add_flags(pipenv_default_flags, "--unused", "--ignore", "-i", "--system"):loop(1) 57 | 58 | local pipenv_clean_parser = parser():add_flags(pipenv_default_flags, "--bare", "--dry-run") 59 | 60 | local pipenv_graph_parser = parser():add_flags(pipenv_default_flags, "--bare", "--json", "--json-tree", "--reverse") 61 | 62 | local pipenv_install_parser = 63 | parser():add_flags( 64 | pipenv_default_flags, 65 | "--system", 66 | "--code", 67 | "-c", 68 | "--deploy", 69 | "--skip-lock", 70 | "--editable", 71 | "-e", 72 | "--ignore-pipfile", 73 | "--selective-upgrade", 74 | "--pre", 75 | "--requirements" .. parser({clink.matches_are_files}), 76 | "-r" .. parser({clink.matches_are_files}), 77 | "--extra-index-url", 78 | "--index", 79 | "-i", 80 | "--sequential", 81 | "--keep-outdated", 82 | "--dev", 83 | "-d" 84 | ):loop(1) 85 | 86 | local pipenv_lock_parser = 87 | parser():add_flags(pipenv_default_flags, "--requirements", "-r", "--keep-outdated", "--pre", "--dev", "-d") 88 | 89 | local pipenv_open_parser = parser({pipenv_libs_list}):add_flags(pipenv_default_flags) 90 | 91 | local pipenv_run_parser = parser():add_flags(pipenv_default_flags) 92 | 93 | local pipenv_shell_parser = parser():add_flags("--fancy", "--anyway", pipenv_default_flags) 94 | 95 | local pipenv_sync_parser = 96 | parser():add_flags("--bare", "--sequential", "--keep-outdated", "--pre", "--dev", "-d", pipenv_default_flags) 97 | 98 | local pipenv_uninstall_parser = 99 | parser():add_flags( 100 | "--skip-lock", 101 | "--lock", 102 | "--all-dev", 103 | "--all", 104 | "--editable", 105 | "-e", 106 | "--keep-outdated", 107 | "--pre", 108 | "--dev", 109 | "-d", 110 | pipenv_default_flags 111 | ) 112 | 113 | local pipenv_update_parser = 114 | parser():add_flags( 115 | "--bare", 116 | "--outdated", 117 | "--dry-run", 118 | "--editable", 119 | "-e", 120 | "--ignore-pipfile", 121 | "--selective-upgrade", 122 | "--pre", 123 | "--requirements", 124 | "-r", 125 | "--extra-index-url", 126 | "--index", 127 | "-i", 128 | "--sequential", 129 | "--keep-outdated", 130 | "--dev", 131 | "-d", 132 | pipenv_default_flags 133 | ) 134 | 135 | local pipenv_parser = 136 | parser( 137 | { 138 | "check" .. pipenv_check_parser, 139 | "clean" .. pipenv_clean_parser, 140 | "graph" .. pipenv_graph_parser, 141 | "install" .. pipenv_install_parser, 142 | "lock" .. pipenv_lock_parser, 143 | "open" .. pipenv_open_parser, 144 | "run" .. pipenv_run_parser, 145 | "shell" .. pipenv_shell_parser, 146 | "sync" .. pipenv_sync_parser, 147 | "uninstall" .. pipenv_uninstall_parser, 148 | "update" .. pipenv_update_parser 149 | } 150 | ):add_flags( 151 | pipenv_default_flags, 152 | "--where", 153 | "--venv", 154 | "--py", 155 | "--envs", 156 | "--rm", 157 | "--bare", 158 | "--completion", 159 | "--man", 160 | "--support", 161 | "--site-packages", 162 | "--version" 163 | ) 164 | 165 | clink.arg.register_parser("pipenv", pipenv_parser) 166 | -------------------------------------------------------------------------------- /modules/gitutil.lua: -------------------------------------------------------------------------------- 1 | local path = require('path') 2 | 3 | local exports = {} 4 | 5 | local function can_take_optional_locks(command) -- luacheck: no unused 6 | local var = string.lower(os.getenv("GITUTIL_TAKE_OPTIONAL_LOCKS") or "") 7 | if var == "" then 8 | return false 9 | end 10 | 11 | if var == "true" or var == "1" then 12 | return true 13 | end 14 | 15 | var = " " .. var:gsub("[ ;,]", " ") .. " " 16 | 17 | local words = string.explode(string.lower(command)) 18 | if not words or not words[1] then 19 | return false 20 | end 21 | 22 | local commands = string.explode(var) 23 | for _, c in ipairs(commands) do 24 | if words[1] == c then 25 | return true 26 | end 27 | end 28 | 29 | return false 30 | end 31 | 32 | --- 33 | -- Return a command line string to run the specified git command. It will 34 | -- include relevant global flags such as "--no-optional-locks", and also 35 | -- "2>nul" to suppress stderr. 36 | -- 37 | -- Currently it is just "git", but this function makes it possible in the 38 | -- future to specify "git.exe" (bypass any git.bat or git.cmd scripts) and/or 39 | -- add a fully qualified path. 40 | exports.make_command = function(command, dont_suppress_stderr) 41 | command = command or "" 42 | 43 | if not can_take_optional_locks(command) then 44 | command = "--no-optional-locks " .. command 45 | end 46 | 47 | command = "git " .. command 48 | 49 | if not dont_suppress_stderr then 50 | command = "2>nul " .. command 51 | end 52 | 53 | return command 54 | end 55 | 56 | --- 57 | -- Resolves closest .git directory location. 58 | -- Navigates subsequently up one level and tries to find .git directory 59 | -- @param {string} path Path to directory will be checked. If not provided 60 | -- current directory will be used 61 | -- @return {string} Path to .git directory or nil if such dir not found 62 | exports.get_git_dir = function (start_dir) 63 | 64 | -- Checks if provided directory contains '.git' directory 65 | -- and returns path to that directory 66 | local function has_git_dir(dir) 67 | return #clink.find_dirs(dir..'/.git') > 0 and dir..'/.git' 68 | end 69 | 70 | -- checks if directory contains '.git' _file_ and if it does 71 | -- parses it and returns a path to git directory from that file 72 | local function has_git_file(dir) 73 | local gitfile = io.open(dir..'/.git') 74 | if not gitfile then return false end 75 | 76 | local git_dir = gitfile:read():match('gitdir: (.*)') 77 | gitfile:close() 78 | 79 | if not git_dir then return false end 80 | -- If found path is absolute don't prepend initial 81 | -- directory - return absolute path value 82 | return path.is_absolute(git_dir) and git_dir 83 | or dir..'/'..git_dir 84 | end 85 | 86 | -- Set default path to current directory 87 | if not start_dir or start_dir == '.' then start_dir = clink.get_cwd() end 88 | 89 | -- Calculate parent path now otherwise we won't be 90 | -- able to do that inside of logical operator 91 | local parent_path = path.pathname(start_dir) 92 | 93 | return has_git_dir(start_dir) 94 | or has_git_file(start_dir) 95 | -- Otherwise go up one level and make a recursive call 96 | or (parent_path ~= '' and parent_path ~= start_dir and exports.get_git_dir(parent_path) or nil) 97 | end 98 | 99 | exports.get_git_common_dir = function (start_dir) 100 | local git_dir = exports.get_git_dir(start_dir) 101 | if not git_dir then return git_dir end 102 | local commondirfile = io.open(git_dir..'/commondir') 103 | if commondirfile then 104 | -- If there's a commondir file, we're in a git worktree 105 | local commondir = commondirfile:read() 106 | commondirfile.close() 107 | return path.is_absolute(commondir) and commondir 108 | or git_dir..'/'..commondir 109 | end 110 | return git_dir 111 | end 112 | 113 | --- 114 | -- Find out current branch 115 | -- WARNING: IS NOT COMPATIBLE WITH GIT REFTABLE! 116 | -- @return {nil|git branch name} 117 | --- 118 | exports.get_git_branch = function (dir) 119 | local git_dir = dir or exports.get_git_dir() 120 | 121 | -- If git directory not found then we're probably outside of repo 122 | -- or something went wrong. The same is when head_file is nil 123 | local head_file = git_dir and io.open(git_dir..'/HEAD') 124 | if not head_file then return end 125 | 126 | local HEAD = head_file:read() 127 | head_file:close() 128 | 129 | -- if HEAD matches branch expression, then we're on named branch 130 | -- otherwise it is a detached commit 131 | local branch_name = HEAD:match('ref: refs/heads/(.+)') 132 | return branch_name or 'HEAD detached at '..HEAD:sub(1, 7) 133 | end 134 | 135 | return exports 136 | -------------------------------------------------------------------------------- /completions/make.lua: -------------------------------------------------------------------------------- 1 | -- By default, this omits targets with / or \ in them. To include such targets, 2 | -- set %INCLUDE_PATHLIKE_MAKEFILE_TARGETS% to any non-empty string. 3 | 4 | local clink_version = require('clink_version') 5 | if not clink_version.supports_argmatcher_delayinit then 6 | log.info("make.lua argmatcher requires a newer version of Clink; please upgrade.") 7 | return 8 | end 9 | 10 | require('arghelper') 11 | 12 | -- Table of special targets to always ignore. 13 | local special_targets = { 14 | ['.PHONY'] = true, 15 | ['.SUFFIXES'] = true, 16 | ['.DEFAULT'] = true, 17 | ['.PRECIOUS'] = true, 18 | ['.INTERMEDIATE'] = true, 19 | ['.SECONDARY'] = true, 20 | ['.SECONDEXPANSION'] = true, 21 | ['.DELETE_ON_ERROR'] = true, 22 | ['.IGNORE'] = true, 23 | ['.LOW_RESOLUTION_TIME'] = true, 24 | ['.SILENT'] = true, 25 | ['.EXPORT_ALL_VARIABLES'] = true, 26 | ['.NOTPARALLEL'] = true, 27 | ['.ONESHELL'] = true, 28 | ['.POSIX'] = true, 29 | ['.NOEXPORT'] = true, 30 | ['.MAKE'] = true, 31 | } 32 | 33 | -- Function to parse a line of make output, and add any extracted target to the 34 | -- specified targets table. 35 | local function extract_target(line, last_line, targets, include_pathlike) 36 | -- Strip comments. 37 | line = line:gsub('(#.*)$', '') 38 | 39 | -- Ignore when not a target (is this only for GNU make?). 40 | if last_line:find('# Not a target') then 41 | return 42 | end 43 | 44 | -- Extract possible target. 45 | local p = ( 46 | line:match('^([^%s]+):$') or -- When target has no deps. 47 | line:match('^([^%s]+): ')) -- When target has deps. 48 | if not p then 49 | return 50 | end 51 | 52 | -- Ignore pattern rule targets. 53 | if p:find('%%') then 54 | return 55 | end 56 | 57 | -- Ignore special targets. 58 | if special_targets[p] then 59 | return 60 | end 61 | 62 | -- Maybe ignore path-like targets. 63 | local mt 64 | if include_pathlike then 65 | if not p:find('[/\\]') then 66 | mt = 'alias' 67 | end 68 | else 69 | if p:find('[/\\]') then 70 | return 71 | end 72 | end 73 | 74 | -- Add target. 75 | table.insert(targets, {match=p, type=mt}) 76 | end 77 | 78 | -- Sort comparator to sort pathlike targets last. 79 | local function comp_target_sort(a, b) 80 | local a_alias = (a.type == 'alias') 81 | local b_alias = (b.type == 'alias') 82 | if a_alias ~= b_alias then 83 | return a_alias 84 | else 85 | return string.comparematches(a.match, b.match) 86 | end 87 | end 88 | 89 | -- Function to collect available targets. 90 | local function get_targets(word, word_index, line_state, builder, user_data) -- luacheck: no unused 91 | local command = '"'..line_state:getword(line_state:getcommandwordindex())..'" -p -q -r' 92 | if user_data and user_data.makefile then 93 | command = command..' -f "'..user_data.makefile..'"' 94 | end 95 | 96 | local file = io.popen('2>nul '..command) 97 | if not file then 98 | return 99 | end 100 | 101 | local targets = {} 102 | local last_line = '' 103 | 104 | -- Extract targets to be included. 105 | local include_pathlike = os.getenv('INCLUDE_PATHLIKE_MAKEFILE_TARGETS') and true 106 | for line in file:lines() do 107 | extract_target(line, last_line, targets, include_pathlike) 108 | last_line = line 109 | end 110 | 111 | file:close() 112 | 113 | -- When including pathlike targets, sort them last. 114 | if include_pathlike and string.comparematches then 115 | table.sort(targets, comp_target_sort) 116 | if builder.setnosort then 117 | builder:setnosort() 118 | end 119 | end 120 | 121 | return targets 122 | end 123 | 124 | -- Function to detect flags for overriding the default makefile. 125 | local function onarg_flags(arg_index, word, word_index, line_state, user_data) -- luacheck: no unused 126 | if word == '-f' or word == '--file=' or word == '--makefile=' then 127 | user_data.makefile = line_state:getword(word_index + 1) 128 | elseif word:match('^-f.+') then 129 | -- Not sure if this is a bug or not. 130 | -- But if a path containing : is used, it is split in two words 131 | local possible_makefile = word:sub(3) 132 | if possible_makefile:match(':$') then 133 | possible_makefile = possible_makefile..line_state:getword(word_index + 1) 134 | end 135 | user_data.makefile = possible_makefile 136 | end 137 | end 138 | 139 | -- Add onarg function to detect when the user overrides the default makefile. 140 | local flags_table = {} 141 | flags_table.onarg = onarg_flags 142 | 143 | -- Create an argmatcher for make. 144 | local make_parser = clink.argmatcher("make") 145 | :_addexflags(flags_table) 146 | :addarg({get_targets}) 147 | :loop() 148 | 149 | require('help_parser').run(make_parser, 'gnu', 'make --help', nil) 150 | -------------------------------------------------------------------------------- /completions/premake5.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Usage: 3 | -- 4 | -- Clink argmatcher for Premake5. Generates completions for Premake5 by getting 5 | -- a list of available commands and flags from Premake5. 6 | -- 7 | -- Uses argmatcher:setdelayinit() to dynamically (re-)initialize the argmatcher 8 | -- based on the current directory. 9 | -- 10 | -- https://premake.github.io/ 11 | 12 | if not clink then 13 | -- Probably getting loaded and run inside of Premake5 itself; bail out. 14 | return 15 | end 16 | 17 | local clink_version = require('clink_version') 18 | if not clink_version.supports_argmatcher_delayinit then 19 | log.info("premake5.lua argmatcher requires a newer version of Clink; please upgrade.") 20 | return 21 | end 22 | 23 | local prev_cwd = "" 24 | 25 | local function delayinit(argmatcher) 26 | -- If the current directory is the same, the argmatcher is already 27 | -- initialized. 28 | local cwd = os.getcwd() 29 | if prev_cwd == cwd then 30 | return 31 | end 32 | 33 | -- Reset the argmatcher and update the current directory. 34 | argmatcher:reset() 35 | prev_cwd = cwd 36 | 37 | -- Invoke 'premake5 --help' and parse its output to collect the available 38 | -- flags and arguments. 39 | local actions 40 | local flags = {} 41 | local descriptions = {} 42 | local pending_link 43 | local values 44 | local placeholder 45 | local r = io.popen('2>nul premake5.exe --help') 46 | 47 | -- The output from premake5 follows this layout: 48 | -- 49 | -- ... ignore lines until ... 50 | -- OPTIONS ... 51 | -- --flag Description 52 | -- --etc ... 53 | -- ACTIONS 54 | -- action Description 55 | -- etc ... 56 | -- ... stop once a non-indented line is reached. 57 | -- 58 | -- Additionally, some flags follow this layout: 59 | -- 60 | -- --flag=value Description of flag 61 | -- value Description of value 62 | -- etc ... 63 | -- ... until another --flag line, or ACTIONS. 64 | -- 65 | for line in r:lines() do 66 | if actions then 67 | -- A non-blank, non-indented line ends the actions. 68 | if #line > 0 and line:sub(1,1) ~= ' ' then 69 | break 70 | end 71 | -- Parsing an action. 72 | local action, description = line:match('^ ([^ ]+) +(.+)$') 73 | if action then 74 | table.insert(actions, { match=action, type="arg", description=description }) 75 | end 76 | elseif line:find('^ACTIONS') then 77 | -- An 'ACTIONS' line starts the actions section. 78 | actions = {} 79 | elseif values and line:match('^ ') then 80 | -- Add a value to the values table for the pending_link. 81 | local value, description = line:match('^ ([^ ]+) +(.+)$') 82 | if value then 83 | table.insert(values, { match=value, type="arg", description=description }) 84 | end 85 | else 86 | -- Not a value line, so if there's a pending_link then it's 87 | -- finished; add the pending values to it. 88 | if pending_link then 89 | pending_link:addarg(#values > 0 and values or placeholder) 90 | pending_link = nil 91 | values = nil 92 | placeholder = nil 93 | end 94 | -- Parse a flag line. 95 | local flag, value, description 96 | flag, description = line:match('^[ ]+(%-%-[^ =]+) +(.+)$') 97 | if not flag then 98 | flag, value, description = line:match('^[ ]+(%-%-[^ =]+=)([^ ]+) +(.+)$') 99 | end 100 | -- If the line defines a flag, process the flag. 101 | if flag then 102 | if description then 103 | description = description:gsub('; one of:$', '') 104 | end 105 | -- Add the flag. 106 | if value then 107 | pending_link = clink.argmatcher() 108 | table.insert(flags, flag..pending_link) 109 | descriptions[flag] = { value, description } 110 | -- Prepare placeholder value. 111 | values = {} 112 | placeholder = { match=value, type="arg", description=description } 113 | else 114 | descriptions[flag] = description 115 | table.insert(flags, flag) 116 | end 117 | end 118 | end 119 | end 120 | 121 | r:close() 122 | 123 | argmatcher:addarg(actions or {}) 124 | argmatcher:addflags(flags) 125 | argmatcher:adddescriptions(descriptions) 126 | end 127 | 128 | local matcher = clink.argmatcher('premake5') 129 | if matcher.setdelayinit then 130 | matcher:setdelayinit(delayinit) 131 | end 132 | -------------------------------------------------------------------------------- /modules/matchers.lua: -------------------------------------------------------------------------------- 1 | local clink_version = require('clink_version') 2 | 3 | local exports = {} 4 | 5 | local path = require('path') 6 | local w = require('tables').wrap 7 | 8 | -- A function to generate directory matches. 9 | -- 10 | -- local matchers = require("matchers") 11 | -- clink.argmatcher():addarg(matchers.dirs) 12 | exports.dirs = function(word) 13 | if clink_version.supports_display_filter_description then 14 | local matches = w(clink.dirmatches(word)) 15 | return matches 16 | end 17 | 18 | -- Strip off any path components that may be on text. 19 | local prefix = "" 20 | local i = word:find("[\\/:][^\\/:]*$") 21 | if i then 22 | prefix = word:sub(1, i) 23 | end 24 | local include_dots = word:find("%.+$") ~= nil 25 | 26 | -- Find matches. 27 | local matches = w(clink.find_dirs(word.."*", true)) 28 | :filter(function (dir) 29 | return clink.is_match(word, prefix..dir) and 30 | (include_dots or path.is_real_dir(dir)) 31 | end) 32 | :map(function(dir) 33 | return prefix..dir 34 | end) 35 | 36 | -- If there was no matches but word is a dir then use it as the single match. 37 | -- Otherwise tell readline that matches are files and it will do magic. 38 | if #matches == 0 and clink.is_dir(rl_state.text) then 39 | return {rl_state.text} 40 | end 41 | 42 | clink.matches_are_files() 43 | return matches 44 | end 45 | 46 | -- A function to generate file matches. 47 | -- 48 | -- local matchers = require("matchers") 49 | -- clink.argmatcher():addarg(matchers.files) 50 | exports.files = function (word) 51 | if clink_version.supports_display_filter_description then 52 | local matches = w(clink.filematches(word)) 53 | return matches 54 | end 55 | 56 | -- Strip off any path components that may be on text. 57 | local prefix = "" 58 | local i = word:find("[\\/:][^\\/:]*$") 59 | if i then 60 | prefix = word:sub(1, i) 61 | end 62 | 63 | -- Find matches. 64 | local matches = w(clink.find_files(word.."*", true)) 65 | :filter(function (file) 66 | return clink.is_match(word, prefix..file) 67 | end) 68 | :map(function(file) 69 | return prefix..file 70 | end) 71 | 72 | -- Tell readline that matches are files and it will do magic. 73 | if #matches ~= 0 then 74 | clink.matches_are_files() 75 | end 76 | 77 | return matches 78 | end 79 | 80 | -- Returns a function that generates matches for the specified wildcards. 81 | -- 82 | -- local matchers = require("matchers") 83 | -- clink.argmatcher():addarg(matchers.ext_files("*.json")) 84 | exports.ext_files = function (...) 85 | local wildcards = {...} 86 | 87 | if clink.argmatcher then 88 | return function (word) 89 | local matches = clink.dirmatches(word.."*") 90 | for _, wild in ipairs(wildcards) do 91 | for _, m in ipairs(clink.filematches(word..wild)) do 92 | table.insert(matches, m) 93 | end 94 | end 95 | return matches 96 | end 97 | end 98 | 99 | return function (word) 100 | 101 | -- Strip off any path components that may be on text. 102 | local prefix = "" 103 | local i = word:find("[\\/:][^\\/:]*$") 104 | if i then 105 | prefix = word:sub(1, i) 106 | end 107 | 108 | -- Find directories. 109 | local matches = w(clink.find_dirs(word.."*", true)) 110 | :filter(function (dir) 111 | return clink.is_match(word, prefix..dir) and path.is_real_dir(dir) 112 | end) 113 | :map(function(dir) 114 | return prefix..dir 115 | end) 116 | 117 | -- Find wildcard matches (e.g. *.dll). 118 | for _, wild in ipairs(wildcards) do 119 | local filematches = w(clink.find_files(word..wild, true)) 120 | :filter(function (file) 121 | return clink.is_match(word, prefix..file) 122 | end) 123 | :map(function(file) 124 | return prefix..file 125 | end) 126 | matches = matches:concat(filematches) 127 | end 128 | 129 | -- Tell readline that matches are files and it will do magic. 130 | if #matches ~= 0 then 131 | clink.matches_are_files() 132 | end 133 | 134 | return matches 135 | end 136 | end 137 | 138 | exports.create_dirs_matcher = function (dir_pattern, show_dotfiles) 139 | return function (token) 140 | return w(clink.find_dirs(dir_pattern)) 141 | :filter(function(dir) 142 | return clink.is_match(token, dir) and (path.is_real_dir(dir) or show_dotfiles) 143 | end ) 144 | end 145 | end 146 | 147 | exports.create_files_matcher = function (file_pattern) 148 | return function (token) 149 | return w(clink.find_files(file_pattern)) 150 | :filter(function(file) 151 | -- Filter out '.' and '..' entries as well 152 | return clink.is_match(token, file) and path.is_real_dir(file) 153 | end ) 154 | end 155 | end 156 | 157 | return exports 158 | -------------------------------------------------------------------------------- /angular-cli.lua: -------------------------------------------------------------------------------- 1 | local parser = clink.arg.new_parser 2 | 3 | local addon_parser = parser({ 4 | "--dry-run", "-d", 5 | "--verbose", "-v", 6 | "--blueprint", "-b", 7 | "--skip-npm", "-sn", 8 | "--skip-bower", "-sb", 9 | "--skip-git", "-sg", 10 | "--directory", "-dir" 11 | }) 12 | 13 | local asset_sizes_parser = parser({ 14 | "--output-path", "-o" 15 | }) 16 | 17 | local build_parser = parser({ 18 | "--environment=", "-e", 19 | "--environment=dev", "-dev", 20 | "--environment=prod", "-prod", 21 | "--output-path", "-o", 22 | "--watch", "-w", 23 | "--watcher", 24 | "--suppress-sizes", 25 | "--target", "-t", 26 | "--target=development", "-dev", 27 | "--target=production", "-prod", 28 | "--base-href", "-bh", 29 | "--aot" 30 | }) 31 | 32 | local destroy_parser = parser({ 33 | "--dry-run", "-d", 34 | "--verbose", "-v", 35 | "--pod", "-p", 36 | "--classic", "-c", 37 | "--dummy", "-dum", "-id", 38 | "--in-repo-addon", "--in-repo", "-ir" 39 | }) 40 | 41 | local generate_parser = parser({ 42 | "class", "cl", 43 | "component", "c", 44 | "directive", "d", 45 | "enum", "e", 46 | "module", "m", 47 | "pipe", "p", 48 | "route", "r", 49 | "service", "s" 50 | },{ 51 | "--dry-run", "-d", 52 | "--verbose", "-v", 53 | "--pod", "-p", 54 | "--classic", "-c", 55 | "--dummy", "-dum", "-id", 56 | "--in-repo-addon", "--in-repo", "-ir" 57 | }) 58 | 59 | local help_parser = parser({ 60 | "--verbose", "-v", 61 | "--json" 62 | }) 63 | 64 | local init_parser = parser({ 65 | "--dry-run", "-d", 66 | "--verbose", "-v", 67 | "--blueprint", "-b", 68 | "--skip-npm", "-sn", 69 | "--skip-bower", "-sb", 70 | "--name", "-n", 71 | "--link-cli", "-lc", 72 | "--source-dir", "-sd", 73 | "--style", "--style=sass", "--style=scss", "--style=less", "--style=stylus", 74 | "--prefix", "-p", 75 | "--mobile", 76 | "--routing", 77 | "--inline-style", "-is", 78 | "--inline-template", "-it" 79 | }) 80 | 81 | local new_parser = parser({ 82 | "--dry-run", "-d", 83 | "--verbose", "-v", 84 | "--blueprint", "-b", 85 | "--skip-npm", "-sn", 86 | "--skip-git", "-sg", 87 | "--directory", "-dir", 88 | "--link-cli", "-lc", 89 | "--source-dir", "-sd", 90 | "--style", 91 | "--prefix", "-p", 92 | "--mobile", 93 | "--routing", 94 | "--inline-style", "-is", 95 | "--inline-template", "-it" 96 | }) 97 | 98 | local serve_parser = parser({ 99 | "--port", "-p", 100 | "--host", "-H", 101 | "--proxy", "-pr", "-pxy", 102 | "--proxy-config", "-pc", 103 | "--insecure-proxy", "--inspr", 104 | "--watcher", "-w", 105 | "--live-reload", "-lr", 106 | "--live-reload-host", "-lrh", 107 | "--live-reload-base-url", "-lrbu", 108 | "--live-reload-port", "-lrp", 109 | "--live-reload-live-css", 110 | "--environment", "-e", 111 | "--environment=development", "-dev", 112 | "--environment=production", "-prod", 113 | "--output-path", "-op", "-out", 114 | "--ssl", 115 | "--ssl-key", 116 | "--ssl-cert", 117 | "--target", "-t", 118 | "--target=development", "-dev", 119 | "--target=production", "-prod", 120 | "--aot", 121 | "--open", "-o" 122 | }) 123 | 124 | local get_parser = parser({ 125 | "--global" 126 | }) 127 | 128 | local set_parser = parser({ 129 | "--global", "-g" 130 | }) 131 | 132 | local github_pages_parser = parser({ 133 | "--message", 134 | "--environment", 135 | "--branch", 136 | "--skip-build", 137 | "--gh-token", 138 | "--gh-username", 139 | "--user-page" 140 | }) 141 | 142 | local test_parser = parser({ 143 | "--environment", "-e", 144 | "--config-file", "-c", "-cf", 145 | "--server", "-s", 146 | "--host", "-H", 147 | "--test-port", "-tp", 148 | "--filter", "-f", 149 | "--module", "-m", 150 | "--watch", "--watcher", "-w", 151 | "--launch", 152 | "--reporter", "-r", 153 | "--silent", 154 | "--test-page", 155 | "--page", 156 | "--query", 157 | "--code-coverage", "-cc", 158 | "--lint", "-l", 159 | "--browsers", 160 | "--colors", 161 | "--log-levevl", 162 | "--port", 163 | "--reporters", 164 | "--build" 165 | }) 166 | 167 | local version_parser = parser({ 168 | "--verbose" 169 | }) 170 | 171 | local ng_parser = parser({ 172 | "addon"..addon_parser, 173 | "asset-sizes"..asset_sizes_parser, 174 | "build"..build_parser, "b"..build_parser, 175 | "destroy"..destroy_parser, "d"..destroy_parser, 176 | "generate"..generate_parser, "g"..generate_parser, 177 | "help"..help_parser, "h"..help_parser, "--help"..help_parser, "-h"..help_parser, 178 | "init"..init_parser, 179 | "install", "i", 180 | "new"..new_parser, 181 | "serve"..serve_parser, "server"..serve_parser, "s"..serve_parser, 182 | "test"..test_parser, "t"..test_parser, 183 | "e2e", 184 | "lint", 185 | "version"..version_parser, "v"..version_parser, "--version"..version_parser, "-v"..version_parser, 186 | "completion", 187 | "doc", 188 | "make-this-awesome", 189 | "set"..set_parser, 190 | "get"..get_parser, 191 | "github-pages:deploy"..github_pages_parser 192 | }) 193 | 194 | clink.arg.register_parser("ng", ng_parser) -------------------------------------------------------------------------------- /completions/robocopy.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Clink argmatcher for Robocopy. 3 | -- Uses delayinit to parse the Robocopy help text. 4 | 5 | local clink_version = require('clink_version') 6 | if not clink_version.supports_argmatcher_delayinit then 7 | log.info("robocopy.lua argmatcher requires a newer version of Clink; please upgrade.") 8 | return 9 | end 10 | 11 | require('arghelper') 12 | local mcf = require('multicharflags') 13 | 14 | local function sentence_casing(text) 15 | if unicode.iter then -- luacheck: no global 16 | for str in unicode.iter(text) do -- luacheck: ignore 512, no global 17 | return clink.upper(str) .. text:sub(#str + 1) 18 | end 19 | return text 20 | else 21 | return clink.upper(text:sub(1,1)) .. text:sub(2) 22 | end 23 | end 24 | 25 | local function delayinit(argmatcher) 26 | local r = io.popen('2>nul robocopy.exe /???') 27 | if not r then 28 | return 29 | end 30 | 31 | local flags = {} 32 | local hideflags = {} 33 | local descriptions = {} 34 | 35 | local function add_match(flag, disp, desc, linked) 36 | local altflag = flag:lower() 37 | if flag == altflag then 38 | altflag = nil 39 | end 40 | desc = sentence_casing(desc) 41 | if linked then 42 | table.insert(flags, flag..linked) 43 | if altflag then 44 | table.insert(flags, altflag..linked) 45 | table.insert(hideflags, altflag) 46 | end 47 | else 48 | table.insert(flags, flag) 49 | if altflag then 50 | table.insert(flags, altflag) 51 | table.insert(hideflags, altflag) 52 | end 53 | end 54 | if disp then 55 | descriptions[flag] = { disp, desc } 56 | else 57 | descriptions[flag] = { desc } 58 | end 59 | end 60 | 61 | local rashcnet_chars = { 62 | nosort=true, 63 | caseless=true, 64 | { 'R', 'Read-only' }, 65 | { 'A', 'Archive' }, 66 | { 'S', 'System' }, 67 | { 'H', 'Hidden' }, 68 | { 'C', 'Compressed' }, 69 | { 'N', 'Not content indexed' }, 70 | { 'E', 'Encrypted' }, 71 | { 'T', 'Temporary' }, 72 | } 73 | local rashcneto_chars = { 74 | nosort=true, 75 | caseless=true, 76 | } 77 | for _, x in ipairs(rashcnet_chars) do 78 | table.insert(rashcneto_chars, x) 79 | end 80 | table.insert(rashcneto_chars, { 'O', 'Offline' }) 81 | 82 | local copy_flag_chars = { 83 | nosort=true, 84 | caseless=true, 85 | { 'D', 'Data' }, 86 | { 'A', 'Attributes' }, 87 | { 'T', 'Timestamps' }, 88 | { 'S', 'NTFS ACLs' }, 89 | { 'O', 'Owner info' }, 90 | { 'U', 'aUditing info' }, 91 | { 'X', 'Skip alt data streams' }, 92 | } 93 | local dcopy_flag_chars = { 94 | nosort=true, 95 | caseless=true, 96 | { 'D', 'Data' }, 97 | { 'A', 'Attributes' }, 98 | { 'T', 'Timestamps' }, 99 | { 'E', 'EAs' }, 100 | { 'X', 'Skip alt data streams' }, 101 | } 102 | 103 | local rashcnet = mcf.addcharflagsarg(clink.argmatcher(), rashcnet_chars) 104 | local rashcneto = mcf.addcharflagsarg(clink.argmatcher(), rashcneto_chars) 105 | local copy_flags = mcf.addcharflagsarg(clink.argmatcher(), copy_flag_chars) 106 | local dcopy_flags = mcf.addcharflagsarg(clink.argmatcher(), dcopy_flag_chars) 107 | 108 | for line in r:lines() do 109 | if unicode.fromcodepage then -- luacheck: no global 110 | line = unicode.fromcodepage(line) -- luacheck: no global 111 | end 112 | local f,d = line:match('^ *(/[^ ]+) :: (.+)$') 113 | if f then 114 | local a,b = f:match('^(.-)%[:(.+)%]$') 115 | if a then 116 | add_match(a, nil, d) 117 | add_match(a..':', b, d) 118 | else 119 | a,b = f:match('^([^:]+:)(.+)$') 120 | if not a then 121 | a,b = f:match('^([^ ]+)( .+)$') 122 | end 123 | if a then 124 | if a == "/A-:" or a == "/A+:" then 125 | -- TODO: Clink can't do completions for /A+: yet. 126 | add_match(a, b, d, rashcnet) 127 | elseif a == "/IA:" or a == "/XA:" then 128 | add_match(a, b, d, rashcneto) 129 | elseif a == "/COPY:" then 130 | add_match(a, b, d, copy_flags) 131 | elseif a == "/DCOPY:" then 132 | add_match(a, b, d, dcopy_flags) 133 | else 134 | add_match(a, b, d) 135 | end 136 | else 137 | add_match(f, nil, d) 138 | end 139 | end 140 | end 141 | end 142 | 143 | r:close() 144 | 145 | argmatcher:addflags(flags) 146 | argmatcher:hideflags(hideflags) 147 | argmatcher:adddescriptions(descriptions) 148 | return true 149 | end 150 | 151 | clink.argmatcher('robocopy'):setdelayinit(delayinit) 152 | -------------------------------------------------------------------------------- /chocolatey.lua: -------------------------------------------------------------------------------- 1 | local w = require('tables').wrap 2 | local path = require('path') 3 | 4 | local packages = function (token) 5 | local install = clink.get_env('chocolateyinstall') 6 | if install then 7 | return w(clink.find_dirs(clink.get_env('chocolateyinstall')..'/lib/*')) 8 | :filter(function(dir) 9 | return path.is_real_dir(dir) and clink.is_match(token, dir) 10 | end) 11 | :map(function (dir) 12 | local package_name = dir:match("^(%w%.*)%.") 13 | return package_name or dir 14 | end) 15 | end 16 | end 17 | 18 | local parser = clink.arg.new_parser 19 | 20 | local clist_parser = parser( 21 | "-a", "--all", "--allversions", "--all-versions", 22 | "-i", "--includeprograms", "--include-programs", 23 | "-l", "--lo", "--localonly", "--local-only", 24 | "-s", "--source".. parser({"windowsfeatures", "webpi"}), 25 | "-u", "--user", 26 | "-p", "--password") 27 | 28 | local cinst_parser = parser( 29 | -- TODO: Path to packages config. 30 | -- See https://github.com/chocolatey/choco/wiki/CommandsInstall 31 | {"all", "packages.config"}, 32 | "--ia", "--installargs", "--installarguments", "--install-arguments", 33 | "-i", "--ignoredependencies", "--ignore-dependencies", 34 | "-x", "--forcedependencies", "--force-dependencies", 35 | "-m", "--sxs", "--sidebyside", "--side-by-side", 36 | "--allowmultiple", "--allow-multiple", "--allowmultipleversions", "--allow-multiple-versions", 37 | "-n", "--skippowershell", "--skip-powershell", 38 | "--notsilent", "--not-silent", 39 | "-o", "--override", "--overrideargs", "--overridearguments", "--override-arguments", 40 | "--params", "--parameters", "--pkgparameters", "--packageparameters", "--package-parameters", 41 | "--pre", "--prerelease", 42 | "-s" .. parser({"ruby", "webpi", "cygwin", "windowsfeatures", "python"}), 43 | "--source" .. parser({"ruby", "webpi", "cygwin", "windowsfeatures", "python"}), 44 | "--version", 45 | "--x86", "--forcex86", 46 | "-u", "--user", 47 | "-p", "--password") 48 | 49 | local cuninst_parser = parser({packages}, 50 | "-a", "--all", "--allversions", "--all-versions", 51 | "-x", "--forcedependencies", "--force-dependencies", 52 | "--ia", "--installargs", "--installarguments", "--install-arguments", 53 | "-n", "--skippowershell", "--skip-powershell", 54 | "--notsilent", "--not-silent", 55 | "-o", "--override", "--overrideargs", "--overridearguments", "--override-arguments", 56 | "--params", "--parameters", "--pkgparameters", "--packageparameters", "--package-parameters", 57 | "--version") 58 | 59 | local cup_parser = parser( 60 | --TODO: complete locally installed packages 61 | {packages, "all"}, 62 | "--ia", "--installargs", "--installarguments", "--install-arguments", 63 | "-i", "--ignoredependencies", "--ignore-dependencies", 64 | "-m", "--sxs", "--sidebyside", "--side-by-side", 65 | "--allowmultiple", "--allow-multiple", "--allowmultipleversions", "--allow-multiple-versions", 66 | "-n", "--skippowershell", "--skip-powershell", 67 | "--notsilent", "--not-silent", 68 | "-o", "--override", "--overrideargs", "--overridearguments", "--override-arguments", 69 | "--params", "--parameters", "--pkgparameters", "--packageparameters", "--package-parameters", 70 | "--pre", "--prerelease", 71 | "-s" .. parser({"ruby", "webpi", "cygwin", "windowsfeatures", "python"}), 72 | "--source" .. parser({"ruby", "webpi", "cygwin", "windowsfeatures", "python"}), 73 | "--version", 74 | "--x86", "--forcex86", 75 | "-u", "--user", 76 | "-p", "--password"):loop(1) 77 | 78 | local sources_parser = parser({ 79 | "add"..parser( 80 | "-n", "--name", 81 | "-u", "--user", 82 | "-p", "--password", 83 | "-s", "-source"), 84 | "disable"..parser("-n", "--name"), 85 | "enable"..parser("-n", "--name"), 86 | "list", 87 | "remove"..parser("-n", "--name")}) 88 | 89 | local chocolatey_parser = parser({ 90 | --TODO: https://github.com/chocolatey/choco/wiki/CommandsReference 91 | -- Default Options and Switches 92 | -- new - generates files necessary for a Chocolatey package 93 | -- pack - packages up a nuspec to a compiled nupkg 94 | -- push - pushes a compiled nupkg 95 | "apikey"..parser("-s", "--source", "-k", "--key", "--apikey", "--api-key"), 96 | "setapikey"..parser("-s", "--source", "-k", "--key", "--apikey", "--api-key"), 97 | "feature"..parser({ 98 | "list", 99 | "disable"..parser("-n", "--name"), 100 | "enable"..parser("-n", "--name") 101 | }), 102 | "install"..cinst_parser, 103 | "list"..clist_parser, 104 | "outdated"..parser( 105 | "-s", "--source", 106 | "-u", "--user", 107 | "-p", "--password"), 108 | "pin"..parser({"add", "remove", "list"}, "-n", "--name", "--version"), 109 | "source"..sources_parser, 110 | "sources"..sources_parser, 111 | "search"..clist_parser, 112 | "upgrade"..cup_parser, 113 | "uninstall"..cuninst_parser 114 | }, "/?") 115 | 116 | clink.arg.register_parser("choco", chocolatey_parser) 117 | clink.arg.register_parser("chocolatey", chocolatey_parser) 118 | clink.arg.register_parser("cinst", cinst_parser) 119 | clink.arg.register_parser("clist", clist_parser) 120 | clink.arg.register_parser("cuninst", cuninst_parser) 121 | clink.arg.register_parser("cup", cup_parser) -------------------------------------------------------------------------------- /completions/sudo.lua: -------------------------------------------------------------------------------- 1 | local clink_version = require('clink_version') 2 | if not clink_version.supports_argmatcher_chaincommand then 3 | log.info("sudo.lua argmatcher requires a newer version of Clink; please upgrade.") 4 | return 5 | end 6 | 7 | require("arghelper") 8 | -- luacheck: globals os 9 | 10 | -------------------------------------------------------------------------------- 11 | -- Microsoft's sudo command. 12 | 13 | local function init_microsoft_sudo(argmatcher) 14 | local subcommands = { 15 | ["help"] = true, 16 | ["run"] = true, 17 | ["config"] = true, 18 | } 19 | 20 | local function onadvance_run(_, word) 21 | if not subcommands[word] then 22 | return -1 23 | end 24 | end 25 | 26 | local dirs = clink.argmatcher():addarg({clink.dirmatches}) 27 | 28 | local helps = clink.argmatcher():addarg({"help", "config", "run"}):nofiles() 29 | local enables = clink.argmatcher():addarg({"disable", "enable", "forceNewWindow", "disableInput", "normal", "default"}) -- luacheck: no max line length 30 | local configs = clink.argmatcher():_addexflags({ 31 | {"--enable"..enables, " ", ""}, 32 | }):nofiles() 33 | 34 | local ex_run_flags = { 35 | {"-E", "Pass the current environment variables to the command"}, 36 | {"--preserve-env"}, 37 | {"-N", "Use a new window for the command"}, 38 | {"--new-window"}, 39 | {"--disable-input"}, 40 | {"--inline"}, 41 | {"-D"..dirs, " dir", "Change the working directory before running the command"}, 42 | {"--chdir"..dirs, " dir", ""}, 43 | } 44 | local runs = clink.argmatcher():_addexflags(ex_run_flags):chaincommand() 45 | 46 | argmatcher 47 | :_addexflags({ 48 | ex_run_flags, 49 | {"-h", "Print help (see more with '--help')"}, 50 | {"--help"}, 51 | {"-V", "Print version"}, 52 | {"--version"}, 53 | }) 54 | :_addexarg({ 55 | onadvance=onadvance_run, 56 | {"help"..helps, " [subcommand]", "Print help"}, 57 | {"run"..runs, " [commandline]", "Run a command as admin"}, 58 | {"config"..configs, "Get or set current configuration information of sudo"}, 59 | }) 60 | :nofiles() 61 | end 62 | 63 | -------------------------------------------------------------------------------- 64 | -- Chrisant996 sudo command (https://github.com/chrisant996/sudo-windows). 65 | 66 | local function init_chrisant996_sudo(argmatcher) 67 | local dir = clink.argmatcher():addarg({clink.dirmatches}) 68 | local prompt = clink.argmatcher():addarg({fromhistory=true}) 69 | local user = clink.argmatcher():addarg({fromhistory=true}) 70 | 71 | argmatcher 72 | :_addexflags({ 73 | {"-?", "Display a short help message and exit"}, 74 | {"-b", "Run the command in the background"}, 75 | {"-D"..dir, " dir", "Run the command in the specified directory"}, 76 | {"-h", "Display a short help message and exit"}, 77 | {"-n", "Avoid showing any UI"}, 78 | {"-p"..prompt, " text", "Use a custom password prompt"}, 79 | {"-S", "Write the prompt to stderr and read the password from stdin instead of using the console"}, 80 | {"-u"..user, " user", "Run the command as the specified user"}, 81 | {"-V", "Print the sudo version string"}, 82 | {"--", "Stop processing options in the command line"}, 83 | {"--background", "Run the command in the background"}, 84 | {opteq=true, "--chdir="..dir, "dir", "Run the command in the specified directory"}, 85 | {"--help", "Display a short help message and exit"}, 86 | {"--non-interactive", "Avoid showing any UI"}, 87 | {opteq=true, "--prompt="..prompt, "text", "Use a custom password prompt"}, 88 | {"--stdin", "Write the prompt to stderr and read the password from stdin instead of using the console"}, 89 | {opteq=true, "--user=", "user", "Run the command as the specified user"}, 90 | {"--version", "Print the sudo version string"}, 91 | }) 92 | :chaincommand() 93 | end 94 | 95 | -------------------------------------------------------------------------------- 96 | -- Detect sudo command version. 97 | 98 | local fullname = ... -- Full command path. 99 | 100 | if fullname then 101 | if string.lower(path.getname(fullname)) == "sudo.exe" then 102 | local windir = os.getenv("windir") 103 | if windir then 104 | local cdir = clink.lower(path.getdirectory(fullname)) 105 | local wdir = clink.lower(path.join(windir, "system32")) 106 | if cdir == wdir then 107 | local sudo = clink.argmatcher(fullname) 108 | init_microsoft_sudo(sudo) 109 | return 110 | end 111 | end 112 | if os.getfileversion then 113 | local info = os.getfileversion(fullname) 114 | if info and info.companyname == "Christopher Antos" then 115 | local sudo = clink.argmatcher(fullname) 116 | init_chrisant996_sudo(sudo) 117 | return 118 | end 119 | end 120 | end 121 | else 122 | -- Alternative initialization in case the script is not located in a 123 | -- completions\ directory, in which case ... will be nil. 124 | local sysroot = os.getenv("windir") or os.getenv("systemroot") 125 | if sysroot then 126 | local system32 = clink.lower(path.join(sysroot, "system32")) 127 | local sudo = clink.argmatcher(path.join(system32, "sudo.exe")) 128 | init_microsoft_sudo(sudo) 129 | end 130 | end 131 | 132 | clink.argmatcher("sudo"):chaincommand() 133 | -------------------------------------------------------------------------------- /spec/funclib_spec.lua: -------------------------------------------------------------------------------- 1 | 2 | local map = require('funclib').map 3 | local concat = require('funclib').concat 4 | local filter = require('funclib').filter 5 | local reduce = require('funclib').reduce 6 | 7 | describe("funclib module", function() 8 | 9 | it("should export some methods", function() 10 | local methods_count = 0 11 | -- iterate through table to count keys rather than `use #... notation 12 | for _,_ in pairs(require("funclib")) do 13 | methods_count = methods_count + 1 end 14 | assert.are.equals(methods_count, 4) 15 | end) 16 | 17 | describe("'filter' function", function () 18 | local test_table = {"a", "b", nil, false} 19 | 20 | it("should exist", function() 21 | assert.are.equals(type(filter), "function") 22 | end) 23 | 24 | it("should accept nil arguments", function() 25 | assert.has_no.errors(filter) 26 | end) 27 | 28 | it("should return empty table if input table is not specified", function() 29 | assert.are.same(filter(), {}) 30 | end) 31 | 32 | it("should throw if first argument is not a table", function() 33 | assert.has_error(function() filter("aaa") end) 34 | end) 35 | 36 | it("should throw if second argument is not a function", function() 37 | assert.has_error(function() filter({"a", "b"}, "a") end) 38 | -- TODO: uncomment this 39 | -- assert.has_error(function() filter({}, "a") end) 40 | end) 41 | 42 | it("should filter out falsy values if no filter function specified", function() 43 | assert.are.same(filter(test_table), {"a", "b"}) 44 | end) 45 | 46 | it("should filter out values which doesn't satisfy filter function", function() 47 | local function test_filter1(a) return a == "a" end 48 | local function test_filter2(a) return a == nil end 49 | assert.are.same(filter(test_table, test_filter1), {"a"}) 50 | assert.are.same(filter(test_table, test_filter2), {nil}) 51 | end) 52 | end) 53 | 54 | describe("'map' function", function () 55 | local test_table = {"a", "b", "c"} 56 | 57 | it("should exist", function() 58 | assert.are.equals(type(map), "function") 59 | end) 60 | 61 | it("should accept nil arguments", function() 62 | assert.has_no.errors(map) 63 | end) 64 | 65 | it("should return empty table if input table is not specified", function() 66 | assert.are.same(map(), {}) 67 | end) 68 | 69 | it("should throw if first argument is not a table", function() 70 | assert.has_error(function() map("aaa") end) 71 | end) 72 | 73 | it("should throw if second argument is not a function", function() 74 | assert.has_error(function() map(test_table, "a") end) 75 | end) 76 | 77 | it("should return original table if no map function specified", function() 78 | assert.are.same(map(test_table), test_table) 79 | end) 80 | 81 | it("should apply map function to all values", function() 82 | local function test_map(a) return a == "a" end 83 | assert.are.same(map(test_table, test_map), {true, false, false}) 84 | end) 85 | end) 86 | 87 | describe("'reduce' function", function () 88 | local test_table = {1, 2, 3} 89 | local _noop = function() end 90 | 91 | it("should exist", function() 92 | assert.are.equals(type(reduce), "function") 93 | end) 94 | 95 | it("should accept nil arguments (except reduce func)", function() 96 | assert.has_no.errors(function() reduce(nil, nil, _noop) end) 97 | end) 98 | 99 | it("should return accumulator if input table is not specified", function() 100 | assert.are.equals(reduce("accum", nil, _noop), "accum") 101 | end) 102 | 103 | it("should throw if second argument (source table) is not a table", function() 104 | assert.has_error(function() reduce({}, "aaa", _noop) end) 105 | end) 106 | 107 | it("should throw if third argument (reduce func) is not a function", function() 108 | assert.has_error(function() reduce({}, {}, "a") end) 109 | -- TODO: uncomment this 110 | -- assert.has_error(reduce) 111 | end) 112 | 113 | it("should apply reduce func to each element of source table", function() 114 | local function test_reduce(a, v) table.insert(a, v+1) return a end 115 | assert.are.same(reduce({}, test_table, test_reduce), {2, 3, 4}) 116 | end) 117 | end) 118 | 119 | describe("'concat' function", function () 120 | it("should exist", function() 121 | assert.are.equals(type(concat), "function") 122 | end) 123 | 124 | it("should accept nil arguments", function() 125 | assert.has_no.errors(concat) 126 | end) 127 | 128 | it("should return empty table if no input arguments specified", function() 129 | assert.are.same(concat(), {}) 130 | end) 131 | 132 | it("should wrap non-table parameter into a table", function() 133 | local ret = concat("a") 134 | assert.is_not.equals(ret, {}) 135 | assert.are.equals(type(ret), "table") 136 | end) 137 | 138 | it("should omit nil arguments", function() 139 | assert.are.same(concat("a", nil, "b"), {"a", "b"}) 140 | end) 141 | 142 | it("should copy values from table params into result", function() 143 | assert.are.same(concat("a", {nil}, {"b"}), {"a", "b"}) 144 | end) 145 | end) 146 | end) 147 | -------------------------------------------------------------------------------- /completions/code.lua: -------------------------------------------------------------------------------- 1 | -- Completions for VSCode. 2 | 3 | require('arghelper') 4 | 5 | local dir_matcher = clink.argmatcher():addarg(clink.dirmatches) 6 | --local file_matcher = clink.argmatcher():addarg(clink.filematches) 7 | local locale_matcher = clink.argmatcher():addarg({fromhistory=true}) 8 | local profile_matcher = clink.argmatcher():addarg({fromhistory=true}) 9 | local category_matcher = clink.argmatcher():addarg({fromhistory=true}) 10 | local sync_matcher = clink.argmatcher():addarg({nosort=true, "on", "off"}) 11 | local port_matcher = clink.argmatcher():addarg({fromhistory=true}) 12 | local maxmemory_matcher = clink.argmatcher():addarg({fromhistory=true}) 13 | 14 | local level_matcher = clink.argmatcher() 15 | :addarg({nosort=true, "critical", "error", "warn", "info", "debug", "trace", "off"}) 16 | 17 | -- Extension ID matchers. Could potentially merge them, but install_parser is 18 | -- the most interesting one to merge, and yet it can't be merged because it 19 | -- defines flags. 20 | local uninstall_matcher = clink.argmatcher():addarg({fromhistory=true}) 21 | local enabledproposedapi_matcher = clink.argmatcher():addarg({fromhistory=true}) 22 | local disableextension_matcher = clink.argmatcher():addarg({fromhistory=true}) 23 | 24 | local function append_colon(word, word_index, line_state, builder, user_data) -- luacheck: no unused 25 | builder:setappendcharacter(":") 26 | end 27 | 28 | local function vsix_files(match_word) 29 | if clink.filematchesexact then 30 | return clink.filematchesexact(match_word.."*.vsix") 31 | else 32 | local word, expanded = rl.expandtilde(match_word) 33 | 34 | local root = (path.getdirectory(word) or ""):gsub("/", "\\") 35 | if expanded then 36 | root = rl.collapsetilde(root) 37 | end 38 | 39 | local _, ismain = coroutine.running() 40 | 41 | local matches = {} 42 | for _, i in ipairs(os.globfiles(word.."*", true)) do 43 | if i.type:find("dir") or i.name:find("%.vsix$") then 44 | local m = path.join(root, i.name) 45 | table.insert(matches, { match = m, type = i.type }) 46 | if not ismain and _ % 250 == 0 then 47 | coroutine.yield() 48 | end 49 | end 50 | end 51 | return matches 52 | end 53 | end 54 | 55 | local diff_parser = clink.argmatcher() 56 | :addarg(clink.filematches) 57 | :addarg(clink.filematches) 58 | 59 | local merge_parser = clink.argmatcher() 60 | :addarg(clink.filematches) 61 | :addarg(clink.filematches) 62 | :addarg(clink.filematches) 63 | :addarg(clink.filematches) 64 | 65 | local add_parser = clink.argmatcher() 66 | :addarg(clink.dirmatches) 67 | 68 | local goto_parser = clink.argmatcher() 69 | :addarg(clink.filematches, append_colon) 70 | 71 | local list_parser = clink.argmatcher() 72 | :_addexflags({ 73 | {"--category"..category_matcher, " category", "Filters installed extensions by provided category"}, 74 | {"--show-versions", "Show versions of installed extensions"}, 75 | }) 76 | 77 | local install_parser = clink.argmatcher() 78 | :addarg(vsix_files) 79 | :_addexflags({ 80 | {"--force", "Update extension to latest version"}, 81 | {"--pre-relese", "Install the pre-release version of the extension"}, 82 | }) 83 | 84 | -- luacheck: no max line length 85 | clink.argmatcher("code") 86 | :_addexflags({ 87 | -- Options 88 | {"-d"..diff_parser, " file file", "Compare two files with each other"}, 89 | {"--diff"..diff_parser, " file file", ""}, 90 | {"-m"..merge_parser, " file1 file2 base result", "Perform a three-way merge"}, 91 | {"--merge"..merge_parser, " file1 file2 base result", ""}, 92 | {"-a"..add_parser, " folder", "Add folder(s) to the last active window"}, 93 | {"--add"..add_parser, " folder", ""}, 94 | {"-g"..goto_parser, " file:line[:char]", "Open a file and position the cursor"}, 95 | {"--goto"..goto_parser, " file:line[:char]", ""}, 96 | {"-n", "Force to open a new window"}, 97 | {"--new-window"}, 98 | {"-r", "Force to use an already opened window"}, 99 | {"--reuse-window"}, 100 | {"-w", "Wait for the files to be closed before returning"}, 101 | {"--wait"}, 102 | {"--locale"..locale_matcher, " locale", ""}, 103 | {"--user-data-dir"..dir_matcher, " dir", ""}, 104 | {"--profile"..profile_matcher, " dir", ""}, 105 | {"-h", "Print usage"}, 106 | {"--help"}, 107 | 108 | -- Extensions Management 109 | {"--extensions-dir"..dir_matcher, " dir", ""}, 110 | {"--list-extensions"..list_parser}, 111 | {"--install-extension"..install_parser, " ext_id|path", ""}, 112 | {"--uninstall-extension"..uninstall_matcher, " ext_id", ""}, 113 | {"--enable-proposed-api"..enabledproposedapi_matcher, " ext_id", ""}, 114 | 115 | -- Troubleshooting 116 | {"-v", "Print version"}, 117 | {"--version"}, 118 | {"--verbose"}, 119 | {"--log"..level_matcher, " level", ""}, 120 | {"-s", "Print process usage and diagnostics info"}, 121 | {"--status"}, 122 | {"--prof-startup"}, 123 | {"--disable-extensions"}, 124 | {"--disable-extension"..disableextension_matcher, " ext_id", ""}, 125 | {"--sync"..sync_matcher, " on|off", ""}, 126 | {"--inspect-extensions"..port_matcher, " port", ""}, 127 | {"--inspect-brk-extensions"..port_matcher, " port", ""}, 128 | {"--disable-gpu"}, 129 | {"--max-memory"..maxmemory_matcher, " memory", ""}, 130 | {"--telemetry"}, 131 | 132 | -- Other 133 | {"--trace-deprecation"}, 134 | }) 135 | -------------------------------------------------------------------------------- /completions/spicetify.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Clink argmatcher for Spicetify 3 | -------------------------------------------------------------------------------- 4 | 5 | local function split(inputstr, sep) 6 | if sep == nil then 7 | sep = "%s" 8 | end 9 | local t={} 10 | for str in string.gmatch(inputstr, "([^"..sep.."]+)") do 11 | table.insert(t, str) 12 | end 13 | return t 14 | end 15 | 16 | local function get_extension_names(append_hyphen) 17 | local handle = io.popen("2>nul spicetify.exe path -e") 18 | local result = handle:read("*a") 19 | handle:close() 20 | local paths = split(result, "\n") 21 | local names = {} 22 | for _, path in ipairs(paths) do 23 | local name = path:match("([^\\]+)$") 24 | if append_hyphen then 25 | name = name .. "-" 26 | end 27 | table.insert(names, name) 28 | end 29 | return names 30 | end 31 | 32 | local function get_app_names(append_hyphen) 33 | local handle = io.popen("2>nul spicetify.exe path -a") 34 | local result = handle:read("*a") 35 | handle:close() 36 | local paths = split(result, "\n") 37 | local names = {} 38 | for _, path in ipairs(paths) do 39 | local name = path:match("([^\\]+)$") 40 | if append_hyphen then 41 | name = name .. "-" 42 | end 43 | table.insert(names, name) 44 | end 45 | return names 46 | end 47 | 48 | local function get_app_names_true() 49 | return get_app_names(true) 50 | end 51 | 52 | local function get_extension_names_true() 53 | return get_extension_names(true) 54 | end 55 | 56 | local function get_app_names_false() 57 | return get_app_names(false) 58 | end 59 | 60 | local function get_extension_names_false() 61 | return get_extension_names(false) 62 | end 63 | 64 | local one_arg = clink.argmatcher():addarg() 65 | 66 | local function create_arg(name) 67 | return clink.argmatcher():addarg(name .. one_arg) 68 | end 69 | 70 | local empty_parser = clink.argmatcher() 71 | 72 | local backup_parser = clink.argmatcher() 73 | :addarg("apply") 74 | 75 | local refresh_parser = clink.argmatcher() 76 | :addarg("-e") 77 | 78 | local help_parser = clink.argmatcher() 79 | :addarg("config") 80 | 81 | local watch_parser = clink.argmatcher() 82 | :addarg("-e", "-a", "-s", "-l") 83 | 84 | local path_a_flag_parser = clink.argmatcher() 85 | :addarg("root", get_app_names_false) 86 | 87 | local path_e_flag_parser = clink.argmatcher() 88 | :addarg("root", get_extension_names_false) 89 | 90 | local path_s_flag_parser = clink.argmatcher() 91 | :addarg("root", "folder", "color", "css", "js", "assets") 92 | 93 | local path_parser = clink.argmatcher() 94 | :addarg( 95 | "userdata", 96 | "all", 97 | "-e" .. path_e_flag_parser, 98 | "-a" .. path_a_flag_parser, 99 | "-s" .. path_s_flag_parser, 100 | "-c" .. empty_parser 101 | ) 102 | :nofiles() 103 | 104 | local config_custom_apps_parser = clink.argmatcher() 105 | :addarg(get_app_names_true) 106 | 107 | local config_extensions_parser = clink.argmatcher() 108 | :addarg(get_extension_names_true) 109 | 110 | local binary_parser = clink.argmatcher() 111 | :addarg("0", "1") 112 | 113 | local config_parser = clink.argmatcher() 114 | :addarg( 115 | "disable_sentry" .. binary_parser, 116 | "disable_ui_logging" .. binary_parser, 117 | "remove_rtl_rule" .. binary_parser, 118 | "expose_apis" .. binary_parser, 119 | "disable_upgrade_check" .. binary_parser, 120 | "extensions" .. config_extensions_parser, 121 | "custom_apps" .. config_custom_apps_parser, 122 | "sidebar_config" .. binary_parser, 123 | "home_config" .. binary_parser, 124 | "experimental_features" .. binary_parser, 125 | "inject_css" .. binary_parser, 126 | "replace_colors" .. binary_parser, 127 | "overwrite_assets" .. binary_parser, 128 | create_arg("spotify_launch_flags"), 129 | create_arg("prefs_path"), 130 | create_arg("current_theme"), 131 | create_arg("color_scheme"), 132 | "check_spicetify_upgrade" .. binary_parser, 133 | create_arg("spotify_path"), 134 | create_arg("xpui.js_find_8008"), 135 | create_arg("xpui.js_repl_8008"), 136 | "inject_theme_js" .. binary_parser, 137 | "check_spicetify_update" .. binary_parser, 138 | "always_enable_devtools" .. binary_parser 139 | ) 140 | :loop(1) 141 | :nofiles() 142 | 143 | local color_parser = clink.argmatcher() 144 | :addarg( 145 | create_arg("text"), 146 | create_arg("subtext"), 147 | create_arg("main"), 148 | create_arg("main-elevated"), 149 | create_arg("highlight"), 150 | create_arg("highlight-elevated"), 151 | create_arg("sidebar"), 152 | create_arg("player"), 153 | create_arg("card"), 154 | create_arg("shadow"), 155 | create_arg("selected-row"), 156 | create_arg("button"), 157 | create_arg("button-active"), 158 | create_arg("button-disabled"), 159 | create_arg("tab-active"), 160 | create_arg("notification"), 161 | create_arg("notification-error"), 162 | create_arg("misc") 163 | ) 164 | :loop(1) 165 | :nofiles() 166 | 167 | clink.argmatcher("spicetify") 168 | :addflags( 169 | "-a", "--app", 170 | "-e", "--extension", 171 | "-h" .. help_parser, "--help" .. help_parser, 172 | "-l", "--live-refresh", 173 | "-n", "--no-restart", 174 | "-q", "--quiet", 175 | "-s", "--style", 176 | "-v", "--version" 177 | ) 178 | :addarg( 179 | "apply" .. empty_parser, 180 | "backup" .. backup_parser, 181 | "config" .. config_parser, 182 | "refresh" .. refresh_parser, 183 | "restore" .. empty_parser, 184 | "clear" .. empty_parser, 185 | "enable-devtools" .. empty_parser, 186 | "watch" .. watch_parser, 187 | "restart" .. empty_parser, 188 | "path" .. path_parser, 189 | "color" .. color_parser, 190 | "config-dir" .. empty_parser, 191 | "upgrade" .. empty_parser, 192 | "update" .. empty_parser 193 | ) -------------------------------------------------------------------------------- /modules/pid_complete.lua: -------------------------------------------------------------------------------- 1 | local export = {} 2 | 3 | local generated_matches 4 | local use_popuplist = (clink.version_encoded or 0) >= 10050001 5 | 6 | local function filter_matches(matches, completion_type) 7 | if completion_type ~= "?" then 8 | local indexed = {} 9 | for _, m in ipairs(generated_matches) do 10 | indexed[m.match] = m 11 | end 12 | local items = {} 13 | for _, m in ipairs(matches) do 14 | m = indexed[m.match] 15 | if m then 16 | table.insert(items, {value=m.match, description=m.description}) 17 | end 18 | end 19 | local selected = clink.popuplist("Process ID", items) 20 | return selected and {selected} or {} 21 | end 22 | end 23 | 24 | local function run_tlist() 25 | local p = io.popen("2>nul tlist.exe") 26 | if not p then 27 | return 28 | end 29 | 30 | local matches = {} 31 | local name_len = 0 32 | for line in p:lines() do 33 | local pid, info = line:match("^([0-9]+) (.*)$") 34 | if pid then 35 | local executable, title = info:match("^(.*[^ ]) +(.*)$") 36 | if title and title ~= "" then 37 | if executable:lower() ~= "tlist.exe" then 38 | local len 39 | if not use_popuplist then 40 | len = console.cellcount(executable) 41 | end 42 | table.insert(matches, {match=pid, description=executable, title=title, len=len}) 43 | if len and name_len < len then 44 | name_len = len 45 | end 46 | end 47 | else 48 | if info:lower() ~= "tlist.exe" then 49 | local len 50 | if not use_popuplist then 51 | len = console.cellcount(info) 52 | end 53 | table.insert(matches, {match=pid, description=info, len=len}) 54 | if len and name_len < len then 55 | name_len = len 56 | end 57 | end 58 | end 59 | end 60 | end 61 | 62 | p:close() 63 | return matches, name_len 64 | end 65 | 66 | local function make_file_at_path(root, rhs) 67 | if root and rhs then 68 | if root ~= "" and rhs ~= "" then 69 | local ret = path.join(root, rhs) 70 | if os.isfile(ret) then 71 | return '"' .. ret .. '"' 72 | end 73 | end 74 | end 75 | end 76 | 77 | local function run_powershell_get_process() 78 | if not clink.execute then 79 | return 80 | end 81 | 82 | local root = os.getenv("systemroot") 83 | local child = "System32\\WindowsPowerShell\\v1.0\\powershell.exe" 84 | local powershell_exe = make_file_at_path(root, child) 85 | if not powershell_exe then 86 | return 87 | end 88 | 89 | local o = clink.execute('2>&1 '..powershell_exe..' -Command "get-process | format-list Id, Name, MainWindowTitle"') 90 | if not o then 91 | return 92 | end 93 | 94 | local matches = {} 95 | local name_len = 0 96 | 97 | if type(o) == "table" then 98 | local m 99 | local function finish_pending() 100 | if m then 101 | local len 102 | if not use_popuplist then 103 | len = console.cellcount(m["Name"]) 104 | if name_len < len then 105 | name_len = len 106 | end 107 | end 108 | table.insert(matches, { match=m["Id"], description=m["Name"], title=m["MainWindowTitle"], len=len }) 109 | m = nil 110 | end 111 | end 112 | for _,line in ipairs(o) do 113 | local field, value = line:match("^(%w+)%s+:%s*(.*)$") 114 | if field == "Id" then 115 | finish_pending() 116 | end 117 | if field and value and value ~= "" then 118 | m = m or {} 119 | m[field] = value 120 | end 121 | end 122 | finish_pending() 123 | end 124 | 125 | return matches, name_len 126 | end 127 | 128 | local function pid_matches() 129 | generated_matches = nil 130 | 131 | local matches 132 | local name_len 133 | if os.getenv("CLINK_PID_COMPLETE_TLIST") then 134 | matches, name_len = run_tlist() 135 | else 136 | matches, name_len = run_powershell_get_process() 137 | end 138 | if not matches or not name_len then 139 | return {} 140 | end 141 | 142 | matches.nosort = true 143 | table.sort(matches, function (a, b) 144 | if string.comparematches(a.description, b.description) then 145 | return true 146 | elseif string.comparematches(b.description, a.description) then 147 | return false 148 | end 149 | if string.comparematches(a.title or "", b.title or "") then 150 | return true 151 | elseif string.comparematches(b.title or "", a.title or "") then 152 | return false 153 | end 154 | if tonumber(a.match) < tonumber(b.match) then 155 | return true 156 | else 157 | return false 158 | end 159 | end) 160 | 161 | local screeninfo = os.getscreeninfo() 162 | if name_len > screeninfo.bufwidth / 3 then 163 | name_len = screeninfo.bufwidth / 3 164 | end 165 | if name_len > 32 then 166 | name_len = 32 167 | end 168 | 169 | for _, m in ipairs(matches) do 170 | if m.title then 171 | if use_popuplist then 172 | m.description = m.description.."\t"..m.title 173 | else 174 | local pad = string.rep(" ", name_len - m.len) 175 | m.description = m.description..pad.." "..m.title 176 | end 177 | end 178 | end 179 | 180 | if #matches > 1 and use_popuplist then 181 | generated_matches = matches 182 | clink.onfiltermatches(filter_matches) 183 | end 184 | 185 | return matches, name_len 186 | end 187 | 188 | export.argmatcher = clink.argmatcher():addarg(pid_matches) 189 | export.matches = pid_matches 190 | 191 | return export 192 | -------------------------------------------------------------------------------- /modules/procdump_shared.lua: -------------------------------------------------------------------------------- 1 | require("arghelper") 2 | 3 | local pid_complete = require("pid_complete") 4 | local pid_parser = pid_complete.argmatcher 5 | 6 | local function dll_file_matches(word) 7 | if clink.filematchesexact then 8 | local matches = clink.dirmatches(word) 9 | local files = clink.filematchesexact(word.."*.dll") 10 | for _, f in ipairs(files) do 11 | table.insert(matches, f) 12 | end 13 | return matches 14 | else 15 | return clink.filematches(word) 16 | end 17 | end 18 | 19 | local function get_word_direct(line_state, word_index) 20 | local word 21 | if word_index == line_state:getwordcount() and line_state:getword(word_index) == "" then 22 | local line = line_state:getline() 23 | local info = line_state:getwordinfo(word_index) 24 | word = line:sub(info.offset, line_state:getcursor() - 1) 25 | else 26 | word = line_state:getword(word_index) 27 | end 28 | return word 29 | end 30 | 31 | local function onadvance__clone_limit(_, word, word_index, line_state, _) 32 | if word then 33 | word = get_word_direct(line_state, word_index) 34 | if word ~= "" and not word:match("^[1-5]$") then 35 | return 1 36 | end 37 | end 38 | end 39 | 40 | local function onadvance__e_arg(_, word, word_index, line_state, _) 41 | if word then 42 | word = get_word_direct(line_state, word_index) 43 | if word ~= "" and word ~= "1" then 44 | return 1 45 | end 46 | end 47 | end 48 | 49 | local custom_mask = clink.argmatcher():addarg({fromhistory=true}) 50 | local dll_files = clink.argmatcher():addarg(dll_file_matches) 51 | local folders = clink.argmatcher():addarg(clink.dirmatches) 52 | local num_dumps = clink.argmatcher():addarg({"1", "2", "3", "5", "10", "20"}) 53 | local cpu_usage = clink.argmatcher():addarg({"10", "25", "50", "75", "90"}) 54 | local consecutive = clink.argmatcher():addarg({"5", "10", "15", "20", "30", "60"}) 55 | local timeouts = consecutive 56 | local clone_limit = clink.argmatcher():addarg({onadvance=onadvance__clone_limit, "1", "2", "3", "4", "5"}) 57 | local e_arg = clink.argmatcher():addarg({onadvance=onadvance__e_arg, "1"}) 58 | local launch_image = clink.argmatcher():addarg(clink.dirmatches):chaincommand() 59 | local commit_usage = clink.argmatcher():addarg({fromhistory=true}) 60 | local perf_counter = clink.argmatcher():addarg({fromhistory=true}) 61 | 62 | local initialized 63 | 64 | local function init_procdump() 65 | if initialized then 66 | return 67 | end 68 | initialized = true 69 | 70 | local pd = clink.argmatcher("procdump", "procdump64") 71 | 72 | pd:addarg(pid_parser) 73 | pd:addarg(clink.filematches) 74 | pd:nofiles() 75 | pd:_addexflags({ 76 | nosort=true, 77 | ---------------------------------------------------------------------- 78 | -- luacheck: push 79 | -- luacheck: no max line length 80 | {"-mm", "Write a 'Mini' dump file (default)"}, 81 | {"-ma", "Write a 'Full' dump file"}, 82 | {"-mp", "Write a 'MiniPlus' dump file"}, 83 | {"-mc"..custom_mask, " Mask", "Write a 'Custom' dump file defined by the specified MINIDUMP_TYPE mask (Hex)"}, 84 | {"-md"..dll_files, " Callback_DLL", "Write a 'Callback' dump file"}, 85 | {"-mk", "Also write a 'Kernel' dump file"}, 86 | {"-a", "Avoid outage (requires -r)"}, 87 | {"-at"..timeouts, " Timeout", "Avoid outage at Timeout. Cancel the trigger's collection at N seconds"}, 88 | {"-b", "Treat debug breakpoints as exceptions (otherwise ignore them)"}, 89 | {"-c"..cpu_usage, " CPU_Usage", "CPU threshold above which to create a dump of the process"}, 90 | {"-cl"..cpu_usage, " CPU_Usage", "CPU threshold below which to create a dump of the process"}, 91 | {"-e"..e_arg, " [1]", "Write a dump when the process encounters an unhandled exception (include the 1 to create dump on first chance exceptions)"}, 92 | -- -f Filter (include) on the content of exceptions and debug logging. 93 | -- Wildcards (*) are supported. 94 | -- [-f Include_Filter, ...] 95 | -- -fx Filter (exclude) on the content of exceptions and debug logging. 96 | -- Wildcards (*) are supported. 97 | -- [-fx Exclude_Filter, ...] 98 | {"-g", "Run as a native debugger in a managed process"}, 99 | {"-h", "Write dump if process has a hung window"}, 100 | {"-i"..folders, " Dump_Folder", "Install ProcDump as the AeDebug postmortem debugger (-u by itself to uninstall)"}, 101 | {"-k", "Kill the process after cloning (-r), or at end of dump collection"}, 102 | {"-l", "Display the debug logging of the process"}, 103 | {"-m"..commit_usage, " Commit_Usage", "Memory commit threshold in MB at which to create a dump"}, 104 | {"-ml"..commit_usage, " Commit_Usage", "Trigger when memory commit drops below specified MB value"}, 105 | {"-n"..num_dumps, " Count", "Number of dumps to write before exiting"}, 106 | {"-o", "Overwrite an existing dump file"}, 107 | {"-p"..perf_counter, " Counter_Threshold", "Trigger on the specified performance counter when the threshold is exceeded"}, 108 | {"-pl"..perf_counter, " Counter_Threshold", "Trigger when performance counter falls below the specified value"}, 109 | {"-r"..clone_limit, " [Limit]", "Dump using a clone. Concurrent limit is optional (default 1, max 5)"}, 110 | {"-s"..consecutive, " Seconds", "Consecutive seconds before dump is written (default is 10)"}, 111 | {"-t", "Write a dump when the process terminates"}, 112 | {"-u", "Treat CPU usage relative to a single core (used with -c) (-u by itself to uninstall ProcDump as the postmortem debugger)"}, 113 | {"-w", "Wait for the specified process to launch if it's not running"}, 114 | {"-wer", "Queue the (largest) dump to Windows Error Reporting"}, 115 | {"-x"..launch_image, " Dump_Folder Image_File [Argument, ...]", "Launch the specified image with optional arguments"}, 116 | {"-64", "On 64-bit Windows, capture 64-bit dumps even for 32-bit processes"}, 117 | {"-accepteula", "Automatically accept the license agreement"}, 118 | {"-cancel"..pid_parser, " PID", "Gracefully terminate any active monitoring of the specified PID"}, 119 | -- luacheck: pop 120 | ---------------------------------------------------------------------- 121 | }) 122 | end 123 | 124 | local exports = { 125 | init_procdump = init_procdump, 126 | } 127 | 128 | return exports 129 | -------------------------------------------------------------------------------- /completions/fastboot.lua: -------------------------------------------------------------------------------- 1 | --- fastboot.lua, Android Fastboot completion for Clink. 2 | -- @compatible Android SDK Platform-tools v31.0.3 3 | -- @author Goldie Lin 4 | -- @date 2021-08-27 5 | -- @see [Clink](https://github.com/chrisant996/clink) 6 | -- @usage 7 | -- Place it in "%LocalAppData%\clink\" if installed globally, 8 | -- or "ConEmu/ConEmu/clink/" if you used portable ConEmu & Clink. 9 | -- 10 | 11 | -- luacheck: no unused args 12 | -- luacheck: ignore clink rl_state 13 | 14 | local function dump(o) -- luacheck: ignore 15 | if type(o) == 'table' then 16 | local s = '{ ' 17 | local prefix = "" 18 | for k, v in pairs(o) do 19 | if type(k) ~= 'number' then k = '"'..k..'"' end 20 | s = s..prefix..'['..k..']="'..dump(v)..'"' 21 | prefix = ', ' 22 | end 23 | return s..' }' 24 | else 25 | return tostring(o) 26 | end 27 | end 28 | 29 | local function generate_matches(command, pattern) 30 | local f = io.popen('2>nul '..command) 31 | if f then 32 | local matches = {} 33 | for line in f:lines() do 34 | if line ~= 'List of devices attached' then 35 | table.insert(matches, line:match(pattern)) 36 | end 37 | end 38 | f:close() 39 | return matches 40 | end 41 | end 42 | 43 | local serialno_parser = clink.argmatcher():addarg({generate_matches('fastboot devices', '^(%w+)%s+.*$')}) 44 | 45 | local null_parser = clink.argmatcher():nofiles() 46 | 47 | local flashing_parser = clink.argmatcher() 48 | :nofiles() 49 | :addarg({ 50 | "lock", 51 | "unlock", 52 | "lock_critical", 53 | "unlock_critical", 54 | "lock_bootloader", 55 | "unlock_bootloader", 56 | "get_unlock_ability", 57 | "get_unlock_bootloader_nonce" 58 | }) 59 | 60 | local partitions = { 61 | "devinfo", 62 | "splash", 63 | "keystore", 64 | "ssd", 65 | "frp", 66 | "misc", 67 | "aboot", 68 | "abl", 69 | "abl_a", 70 | "abl_b", 71 | "boot", 72 | "boot_a", 73 | "boot_b", 74 | "recovery", 75 | "cache", 76 | "persist", 77 | "userdata", 78 | "system", 79 | "system_a", 80 | "system_b", 81 | "vendor", 82 | "vendor_a", 83 | "vendor_b" 84 | } 85 | 86 | local partitions_parser = clink.argmatcher() 87 | :addarg(partitions) 88 | 89 | local partitions_nofile_parser = clink.argmatcher() 90 | :nofiles() 91 | :addarg(partitions) 92 | 93 | local variables_parser = clink.argmatcher() 94 | :nofiles() 95 | :addarg({ 96 | "all", 97 | "serialno", 98 | "product", 99 | "secure", 100 | "unlocked", 101 | "variant", 102 | "kernel", 103 | "version-baseband", 104 | "version-bootloader", 105 | "charger-screen-enabled", 106 | "off-mode-charge", 107 | "battery-soc-ok", 108 | "battery-voltage", 109 | "slot-count", 110 | "current-slot", 111 | "has-slot:boot", 112 | "has-slot:modem", 113 | "has-slot:system", 114 | "slot-retry-count:a", 115 | "slot-retry-count:b", 116 | "slot-successful:a", 117 | "slot-successful:b", 118 | "slot-unbootable:a", 119 | "slot-unbootable:b" 120 | }) 121 | 122 | local slots = { 123 | "a", 124 | "b" 125 | } 126 | 127 | local slot_types = { 128 | "all", 129 | "other" 130 | } 131 | 132 | local slots_full = {} 133 | for _, i in ipairs(slots) do 134 | table.insert(slots_full, i) 135 | end 136 | for _, i in ipairs(slot_types) do 137 | table.insert(slots_full, i) 138 | end 139 | 140 | local slots_parser = clink.argmatcher() 141 | :nofiles() 142 | :addarg(slots) 143 | 144 | local slot_types_parser = clink.argmatcher() 145 | :nofiles() 146 | :addarg(slots_full) 147 | 148 | local fs_options = { 149 | "casefold", 150 | "compress", 151 | "projid" 152 | } 153 | 154 | local fs_options_parser = clink.argmatcher() 155 | :addarg(fs_options) 156 | 157 | local flash_raw_parser = clink.argmatcher() 158 | :addarg({ 159 | "boot" 160 | }) 161 | 162 | local devices_parser = clink.argmatcher() 163 | :nofiles() 164 | :addflags( 165 | "-l" 166 | ) 167 | 168 | local reboot_parser = clink.argmatcher() 169 | :nofiles() 170 | :addarg({ 171 | "bootloader", 172 | "emergency" 173 | }) 174 | 175 | local oem_parser = clink.argmatcher() 176 | :nofiles() 177 | :addarg({ 178 | "lock", 179 | "unlock", 180 | "device-info", 181 | "select-display-panel", 182 | "enable-charger-screen", 183 | "disable-charger-screen" 184 | }) 185 | 186 | local gsi_parser = clink.argmatcher() 187 | :nofiles() 188 | :addarg({ 189 | "wipe", 190 | "disable" 191 | }) 192 | 193 | local snapshotupdate_parser = clink.argmatcher() 194 | :nofiles() 195 | :addarg({ 196 | "cancel", 197 | "merge" 198 | }) 199 | 200 | clink.argmatcher("fastboot") 201 | :addflags( 202 | "-w", 203 | "-u", 204 | "-s" .. serialno_parser, 205 | "--dtb", 206 | "-c", 207 | "--cmdline", 208 | "-i", 209 | "-h", 210 | "--help", 211 | "-b", 212 | "--base", 213 | "--kernel-offset", 214 | "--ramdisk-offset", 215 | "--tags-offset", 216 | "--dtb-offset", 217 | "-n", 218 | "--page-size", 219 | "--header-version", 220 | "--os-version", 221 | "--os-patch-level", 222 | "-S", 223 | "--slot" .. slot_types_parser, 224 | "-a" .. slots_parser, 225 | "--set-active=" .. slots_parser, 226 | "--skip-secondary", 227 | "--skip-reboot", 228 | "--disable-verity", 229 | "--disable-verification", 230 | "--fs-options=" .. fs_options_parser, 231 | "--wipe-and-use-fbe", 232 | "--unbuffered", 233 | "--force", 234 | "-v", 235 | "--verbose", 236 | "--version" 237 | ) 238 | :addarg({ 239 | "help" .. null_parser, 240 | "update", 241 | "flashall" .. null_parser, 242 | "flashing" .. flashing_parser, 243 | "flash" .. partitions_parser, 244 | "erase" .. partitions_nofile_parser, 245 | "format" .. partitions_nofile_parser, 246 | "getvar" .. variables_parser, 247 | "set_active" .. slots_parser, 248 | "boot", 249 | "flash:raw" .. flash_raw_parser, 250 | "devices" .. devices_parser, 251 | "continue" .. null_parser, 252 | "reboot" .. reboot_parser, 253 | "reboot-bootloader" .. null_parser, 254 | "oem" .. oem_parser, 255 | "gsi" .. gsi_parser, 256 | "wipe-super" .. null_parser, 257 | "create-logical-partition", 258 | "delete-logical-partition", 259 | "resize-logical-partition", 260 | "snapshot-update" .. snapshotupdate_parser, 261 | "fetch" .. partitions_nofile_parser, 262 | "stage", 263 | "get_staged", 264 | }) 265 | 266 | -------------------------------------------------------------------------------- /modules/multicharflags.lua: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- 2 | -- Helpers to add multi-character flags to an argmatcher. 3 | -- 4 | -- This makes it easy to add flags like `dir /o:nge` and be able to provide 5 | -- completions on the fly even after `dir /o:ng` has been typed. 6 | -- 7 | -- local mcf = require('multicharflags') 8 | -- 9 | -- local sortflags = clink.argmatcher() 10 | -- mcf.addcharflagsarg(sortflags, { 11 | -- { 'n', 'By name (alphabetic)' }, 12 | -- { 'e', 'By extension (alphabetic)' }, 13 | -- { 'g', 'Group directories first' }, 14 | -- { 's', 'By size (smallest first)' }, 15 | -- { 'd', 'By name (alphabetic)' }, 16 | -- { '-', 'Prefix to reverse order' }, 17 | -- }) 18 | -- clink.argmatcher('dir'):addflags('/o:'..sortflags) 19 | -- 20 | -- The exported functions are: 21 | -- 22 | -- local mcf = require('multicharflags') 23 | -- 24 | -- mcf.addcharflagsarg(argmatcher, chars_table) 25 | -- This adds an arg to argmatcher, for the character flags listed in 26 | -- chars_table (each table element is a sub-table with two fields, the 27 | -- character and its description). It returns argmatcher. 28 | -- 29 | -- mcf.setcharflagsclassifier(argmatcher, chars_table) 30 | -- This makes and sets a classifier for the character flags. This is 31 | -- for specialized scenarios, and is not normally needed because 32 | -- addcharflagsarg() automatically does this. 33 | -- 34 | -- mcf.makecharflagsclassifier(chars_table) 35 | -- Makes a character flags classifier. This is for specialized 36 | -- scenarios, and is not normally needed because addcharflagsarg() 37 | -- automatically does this. 38 | 39 | if not clink then 40 | -- E.g. some unit test systems will run this module *outside* of Clink. 41 | return 42 | end 43 | 44 | -------------------------------------------------------------------------------- 45 | local function index_chars_table(t) 46 | if not t.indexed then 47 | if t.caseless then 48 | for _, m in ipairs(t) do 49 | t[m[1]:lower()] = true 50 | t[m[1]:upper()] = true 51 | end 52 | else 53 | for _, m in ipairs(t) do 54 | t[m[1]] = true 55 | end 56 | end 57 | t.indexed = true 58 | end 59 | end 60 | 61 | local function compound_matches_func(chars, word, -- luacheck: no unused args, no unused 62 | word_index, line_state, builder, user_data) -- luacheck: no unused 63 | local info = line_state:getwordinfo(word_index) 64 | if not info then 65 | return {} 66 | end 67 | 68 | -- local used = {} 69 | local available = {} 70 | 71 | if chars.caseless then 72 | for _, m in ipairs(chars) do 73 | available[m[1]:lower()] = true 74 | available[m[1]:upper()] = true 75 | end 76 | else 77 | for _, m in ipairs(chars) do 78 | available[m[1]] = true 79 | end 80 | end 81 | 82 | word = line_state:getline():sub(info.offset, line_state:getcursor() - 1) 83 | 84 | for _, m in ipairs(chars) do 85 | if chars.caseless then 86 | local l = m[1]:lower() 87 | local u = m[1]:upper() 88 | if word:find(l, 1, true--[[plain]]) or word:find(u, 1, true--[[plain]]) then 89 | -- used[l] = true 90 | -- used[u] = true 91 | available[l] = false 92 | available[u] = false 93 | end 94 | else 95 | local c = m[1] 96 | if word:find(c, 1, true--[[plain]]) then 97 | -- used[c] = true 98 | available[c] = false 99 | end 100 | end 101 | end 102 | 103 | local last_c 104 | if #word > 0 then 105 | last_c = word:sub(-1) 106 | else 107 | last_c = line_state:getline():sub(info.offset - 1, info.offset - 1) 108 | end 109 | available['+'] = chars['+'] and last_c ~= '+' and last_c ~= '-' 110 | available['-'] = chars['-'] and last_c ~= '+' and last_c ~= '-' 111 | 112 | local matches = { nosort=true } 113 | for _, m in ipairs(chars) do 114 | local c = m[1] 115 | if available[c] then 116 | table.insert(matches, { match=word..c, display='\x1b[m'..c, description=m[2], suppressappend=true }) 117 | end 118 | end 119 | 120 | if builder.setvolatile then 121 | builder:setvolatile() 122 | elseif clink._reset_generate_matches then 123 | clink._reset_generate_matches() 124 | end 125 | 126 | return matches 127 | end 128 | 129 | local function get_bad_color() 130 | local bad = settings.get('color.unrecognized') 131 | if not bad or bad == '' then 132 | bad = '91' 133 | end 134 | return bad 135 | end 136 | 137 | local function compound_classifier(chars, arg_index, -- luacheck: no unused args 138 | word, word_index, line_state, classifications) 139 | local info = line_state:getwordinfo(word_index) 140 | if not info then 141 | return 142 | end 143 | 144 | local bad 145 | local good = settings.get('color.arg') 146 | 147 | for i = 1, #word do 148 | local c = word:sub(i, i) 149 | if chars[c] then 150 | classifications:applycolor(info.offset + i - 1, 1, good) 151 | else 152 | bad = bad or get_bad_color() 153 | classifications:applycolor(info.offset + i - 1, 1, bad) 154 | end 155 | end 156 | 157 | if chars['+'] then 158 | local plus_pos = info.offset + info.length 159 | if line_state:getline():sub(plus_pos, plus_pos) == '+' then 160 | classifications:applycolor(plus_pos, 1, good) 161 | end 162 | end 163 | end 164 | 165 | local function make_char_flags_classifier(chars) 166 | local function classifier_func(...) 167 | compound_classifier(chars, ...) 168 | end 169 | return classifier_func 170 | end 171 | 172 | local function set_char_flags_classifier(argmatcher, chars) 173 | if argmatcher.setclassifier then 174 | argmatcher:setclassifier(make_char_flags_classifier(chars)) 175 | end 176 | return argmatcher 177 | end 178 | 179 | local function add_char_flags_arg(argmatcher, chars, ...) 180 | index_chars_table(chars) 181 | 182 | local matches_func = function (...) 183 | return compound_matches_func(chars, ...) 184 | end 185 | 186 | local t = { matches_func } 187 | if chars['+'] then 188 | t.loopchars = '+' 189 | end 190 | 191 | argmatcher:addarg(t, ...) 192 | set_char_flags_classifier(argmatcher, chars) 193 | 194 | return argmatcher 195 | end 196 | 197 | -------------------------------------------------------------------------------- 198 | return { 199 | addcharflagsarg = add_char_flags_arg, 200 | makecharflagsclassifier = make_char_flags_classifier, 201 | setcharflagsclassifier = set_char_flags_classifier, 202 | } 203 | -------------------------------------------------------------------------------- /completions/gsudo.lua: -------------------------------------------------------------------------------- 1 | local clink_version = require('clink_version') 2 | if not clink_version.supports_argmatcher_chaincommand then 3 | log.info("gsudo.lua argmatcher requires a newer version of Clink; please upgrade.") 4 | return 5 | end 6 | 7 | require('arghelper') 8 | 9 | -- luacheck: no max line length 10 | -- luacheck: globals string.equalsi 11 | 12 | local integrity = clink.argmatcher():addarg({'Untrusted', 'Low', 'Medium', 'MediumPlus', 'High', 'System' }) 13 | local username = clink.argmatcher():addarg({fromhistory=true}) 14 | local loglevel = clink.argmatcher():addarg({'All', 'Debug', 'Info', 'Warning', 'Error', 'None'}) 15 | 16 | clink.argmatcher('gsudo') 17 | :chaincommand() 18 | :_addexflags({ 19 | { hide=true, '-?' }, 20 | { hide=true, '-h' }, 21 | { '--help', 'Show help text' }, 22 | { hide=true, '-v' }, 23 | { '--version', 'Show version info' }, 24 | -- New Window options: 25 | { hide=true, '-n' }, 26 | { '--new', 'Starts the command in a new console (and returns immediately)' }, 27 | { hide=true, '-w' }, 28 | { '--wait', 'When in new console, wait for the command to end and return the exitcode' }, 29 | { '--keepShell', 'Keep elevated shell open after running {command}' }, 30 | { '--keepWindow', 'When in new console, ask for keypress before closing the console' }, 31 | { '--close', 'Override settings and always close new window at end' }, 32 | -- Security options: 33 | { hide=true, '-i'..integrity }, 34 | { '--integrity'..integrity, ' {v}', 'Run with specified integrity level' }, 35 | { hide=true, '-u'..username }, 36 | { '--user'..username, ' {username}', 'Run as the specified user. Asks for password. For local admins shows UAC unless \'-i Medium\'' }, 37 | { hide=true, '-s' }, 38 | { '--system', 'Run as Local System account (NT AUTHORITY\\SYSTEM)' }, 39 | { '--ti', 'Run as member of NT SERVICE\\TrustedInstaller group' }, 40 | { hide=true, '-k' }, 41 | { '--reset-timestamp', 'Kills all cached credentials. The next time gsudo is run a UAC popup will be appear' }, 42 | -- Shell related options: 43 | { hide=true, '-d' }, 44 | { '--direct', 'Skip Shell detection. Assume CMD shell or CMD {command}' }, 45 | -- Other options: 46 | { '--loglevel'..loglevel, ' {val}', 'Set minimum log level to display' }, 47 | { '--debug', 'Enable debug mode' }, 48 | { '--copyns', 'Connect network drives to the elevated user. Warning: Interactively asks for credentials' }, 49 | -- Configuration: 50 | -- gsudo config {key} [--global] Affects all users (overrides user settings) 51 | -- From PowerShell: 52 | --{ ScriptBlock } Must be wrapped in { curly brackets } 53 | { '--loadProfile', 'When elevating PowerShell commands, load user profile' }, 54 | -- Deprecated: 55 | { hide=true, '--copyev', '(deprecated) Copy all environment variables to the elevated process' }, 56 | { hide=true, '--attached' }, 57 | { hide=true, '--piped' }, 58 | { hide=true, '--vt' }, 59 | }) 60 | 61 | local gen = clink.generator(1) 62 | 63 | local function parse_words(line_state) 64 | local cwi = line_state:getcommandwordindex() 65 | if not cwi or cwi < 1 then 66 | return 67 | end 68 | 69 | local cw = line_state:getword(cwi) 70 | if not (string.equalsi(cw, 'sudo') or 71 | string.equalsi(cw, 'gsudo')) then 72 | return 73 | end 74 | 75 | local nw = line_state:getword(cwi + 1) 76 | if not (nw == 'config' or 77 | nw == 'cache' or 78 | nw == 'status') then 79 | return 80 | end 81 | 82 | return nw, cwi + 1 83 | end 84 | 85 | function gen:generate(line_state, match_builder) -- luacheck: no unused 86 | local nw, nwi = parse_words(line_state) 87 | if not nw then 88 | return 89 | end 90 | 91 | local ls = line_state 92 | if nw == 'config' then 93 | local wc = ls:getwordcount() 94 | if wc == nwi + 1 then 95 | local f = io.popen('2>nul gsudo.exe config') 96 | if not f then 97 | return true 98 | end 99 | for line in f:lines() do 100 | local opt = line:match('^([^ ]+) = ') 101 | if opt then 102 | match_builder:addmatch(opt, 'word') 103 | end 104 | end 105 | f:close() 106 | elseif wc == nwi + 2 then 107 | local info = ls:getwordinfo(wc) 108 | local tocursor = ls:getline():sub(info.offset, ls:getcursor() - 1) 109 | if tocursor == '-' or tocursor:match('^%-%-') then 110 | match_builder:addmatch({ 111 | match = '--global', 112 | description = 'Affects all users (overrides user settings)', 113 | type = 'word', 114 | }) 115 | end 116 | end 117 | return true 118 | elseif nw == 'cache' then 119 | match_builder:addmatches({'on', 'off', 'help'}, 'arg') 120 | return true 121 | elseif nw == 'status' then 122 | return true 123 | end 124 | end 125 | 126 | local clf = clink.classifier(1) 127 | 128 | function clf:classify(commands) -- luacheck: no unused 129 | local none = settings.get('color.unexpected') 130 | for _, c in ipairs(commands) do 131 | local ls = c.line_state 132 | local nw, nwi = parse_words(ls) 133 | if nw then 134 | local info, endinfo, ccw 135 | if nw == 'status' then 136 | info = ls:getwordinfo(nwi + 1) 137 | elseif nw == 'cache' then 138 | ccw = ls:getword(nwi + 1) 139 | if not (ccw == 'on' or ccw == 'off' or ccw == 'help') then 140 | ccw = nil 141 | end 142 | info = ls:getwordinfo(nwi + (ccw and 2 or 1)) 143 | end 144 | if info then 145 | endinfo = ls:getwordinfo(ls:getwordcount()) 146 | c.classifications:applycolor(info.offset, endinfo.offset + endinfo.length - info.offset, none) 147 | end 148 | c.classifications:classifyword(nwi, 'a', true) 149 | if nw == 'cache' and ccw then 150 | c.classifications:classifyword(nwi + 1, 'a', true) 151 | elseif nw == 'config' and ls:getword(nwi + 2) == '--global' then 152 | c.classifications:classifyword(nwi + 2, 'f', true) 153 | end 154 | end 155 | end 156 | end 157 | -------------------------------------------------------------------------------- /pip.lua: -------------------------------------------------------------------------------- 1 | local matchers = require("matchers") 2 | local w = require("tables").wrap 3 | 4 | local parser = clink.arg.new_parser 5 | 6 | local function get_packages(dir, token) 7 | local list 8 | if dir and dir ~= "" then 9 | local finder = matchers.create_files_matcher(dir .. "\\*.dist-info") 10 | list = finder(token) 11 | end 12 | return w(list) 13 | end 14 | 15 | local function pip_libs_list(token) 16 | local sysconfig = {} 17 | 18 | local handle = io.popen('2>nul python -m sysconfig') 19 | if handle then 20 | for line in handle:lines() do 21 | local name, value = line:match('^%s+(.-) = "(.*)"%s*$') 22 | if name and value then 23 | sysconfig[name] = value 24 | end 25 | end 26 | handle:close() 27 | end 28 | 29 | local libpaths = w() 30 | table.insert(libpaths, sysconfig["platlib"]) 31 | table.insert(libpaths, sysconfig["purelib"]) 32 | libpaths = libpaths:dedupe() 33 | 34 | local list = w() 35 | for _,libpath in ipairs(libpaths) do 36 | list = list:concat(get_packages(libpath, token)) 37 | end 38 | 39 | list = list:map( 40 | function(package) 41 | package = package:gsub("-[%d%.]+dist%-info$", "") 42 | return package 43 | end 44 | ) 45 | 46 | return list 47 | end 48 | 49 | local pip_default_flags = { 50 | "--help", 51 | "-h", 52 | "--isolated", 53 | "--verbose", 54 | "-v", 55 | "--version", 56 | "-V", 57 | "--quiet", 58 | "-q", 59 | "--log", 60 | "--proxy", 61 | "--retries", 62 | "--timeout", 63 | "--exists-action", 64 | "--trusted-host", 65 | "--cert", 66 | "--client-cert", 67 | "--cache-dir", 68 | "--no-cache-dir", 69 | "--disable-pip-version-check", 70 | "--no-color" 71 | } 72 | 73 | local pip_requirement_flags = { 74 | "--requirement" .. parser({clink.matches_are_files}), 75 | "-r" .. parser({clink.matches_are_files}) 76 | } 77 | 78 | local pip_index_flags = { 79 | "--index-url", 80 | "-i", 81 | "--extra-index-url", 82 | "--no-index", 83 | "--find-links", 84 | "-f" 85 | } 86 | 87 | local pip_install_download_wheel_flags = { 88 | pip_requirement_flags, 89 | "--no-binary", 90 | "--only-binary", 91 | "--prefer-binary", 92 | "--no-build-isolation", 93 | "--use-pep517", 94 | "--constraint", 95 | "-c", 96 | "--src", 97 | "--no-deps", 98 | "--progress-bar" .. parser({"off", "on", "ascii", "pretty", "emoji"}), 99 | "--global-option", 100 | "--pre", 101 | "--no-clean", 102 | "--requires-hashes" 103 | } 104 | 105 | local pip_install_download_flags = { 106 | pip_install_download_wheel_flags, 107 | "--platform", 108 | "--python-version", 109 | "--implementation" .. parser({"pp", "jy", "cp", "ip"}), 110 | "--abi" 111 | } 112 | 113 | local pip_install_parser = 114 | parser( 115 | {}, 116 | "--editable", 117 | "-e", 118 | "--target", 119 | "-t", 120 | "--user", 121 | "--root", 122 | "--prefix", 123 | "--build", 124 | "-b", 125 | "--upgrade", 126 | "-U", 127 | "--upgrade-strategy" .. parser({"eager", "only-if-needed"}), 128 | "--force-reinstall", 129 | "--ignore-installed", 130 | "-I", 131 | "--ignore-requires-python", 132 | "--install-option", 133 | "--compile", 134 | "--no-compile", 135 | "--no-warn-script-location", 136 | "--no-warn-conflicts" 137 | ):loop(1) 138 | pip_install_parser:add_flags(pip_install_download_flags) 139 | pip_install_parser:add_flags(pip_index_flags) 140 | pip_install_parser:add_flags(pip_default_flags) 141 | 142 | local pip_download_parser = parser({}, "--build", "-b", "--dest", "-d"):loop(1) 143 | pip_download_parser:add_flags(pip_install_download_flags) 144 | pip_download_parser:add_flags(pip_index_flags) 145 | pip_download_parser:add_flags(pip_default_flags) 146 | 147 | local pip_uninstall_parser = 148 | parser({pip_libs_list}, "--yes", "-y"):add_flags(pip_default_flags, pip_requirement_flags):loop(1) 149 | 150 | local pip_freeze_parser = parser({}, "--find-links", "--local", "-l", "--user", "--all", "--exclude-editable") 151 | pip_freeze_parser:add_flags(pip_default_flags, pip_requirement_flags) 152 | 153 | local pip_list_parser = 154 | parser( 155 | {}, 156 | "--outdated", 157 | "-o", 158 | "--uptodate", 159 | "-u", 160 | "--editable", 161 | "-e", 162 | "--local", 163 | "-l", 164 | "--user", 165 | "--pre", 166 | "--format" .. parser({"columns", "freeze", "json"}), 167 | "--not-required", 168 | "--exclude-editable", 169 | "--include-editable" 170 | ) 171 | pip_list_parser:add_flags(pip_default_flags) 172 | 173 | local pip_config_parser = 174 | parser( 175 | { 176 | "list", 177 | "edit", 178 | "get", 179 | "set", 180 | "unset" 181 | }, 182 | "--editor", 183 | "--global", 184 | "--user", 185 | "--venv", 186 | pip_default_flags 187 | ) 188 | pip_config_parser:add_flags(pip_default_flags) 189 | 190 | local pip_search_parser = parser({}, "--index", "-i"):add_flags(pip_default_flags) 191 | 192 | local pip_wheel_parser = 193 | parser( 194 | {}, 195 | "--wheel-dir", 196 | "-w", 197 | "--build-option", 198 | "--editable", 199 | "-e", 200 | "--ignore-requires-python", 201 | "--build", 202 | "-b" 203 | ):loop(1) 204 | pip_wheel_parser:add_flags(pip_install_download_flags) 205 | pip_wheel_parser:add_flags(pip_index_flags) 206 | pip_wheel_parser:add_flags(pip_default_flags) 207 | 208 | local pip_hash_parser = 209 | parser( 210 | {}, 211 | "--algorithm" .. parser({"sha256", "sha384", "sha512"}), 212 | "-a" .. parser({"sha256", "sha384", "sha512"}), 213 | pip_default_flags 214 | ) 215 | pip_hash_parser:add_flags(pip_default_flags) 216 | 217 | local pip_completion_parser = parser({}, "--bash", "-b", "--zsh", "-z", "--fish", "-f"):add_flags(pip_default_flags) 218 | 219 | local pip_help_parser = 220 | parser( 221 | { 222 | "install", 223 | "download", 224 | "uninstall", 225 | "freeze", 226 | "list", 227 | "show", 228 | "config", 229 | "search", 230 | "wheel", 231 | "hash", 232 | "completion", 233 | "help" 234 | } 235 | ) 236 | pip_help_parser:add_flags(pip_default_flags) 237 | 238 | local pip_parser = 239 | parser( 240 | { 241 | "install" .. pip_install_parser, 242 | "download" .. pip_download_parser, 243 | "uninstall" .. pip_uninstall_parser, 244 | "freeze" .. pip_freeze_parser, 245 | "list" .. pip_list_parser, 246 | "show" .. parser({pip_libs_list}, pip_default_flags), 247 | "config" .. pip_config_parser, 248 | "search" .. pip_search_parser, 249 | "wheel" .. pip_wheel_parser, 250 | "hash" .. pip_hash_parser, 251 | "completion" .. pip_completion_parser, 252 | "help" .. pip_help_parser 253 | } 254 | ) 255 | pip_parser:add_flags(pip_default_flags) 256 | 257 | clink.arg.register_parser("pip", pip_parser) 258 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Build status](https://github.com/vladimir-kotikov/clink-completions/actions/workflows/code-check.yml/badge.svg?branch=master)](https://github.com/vladimir-kotikov/clink-completions/actions/workflows/code-check.yml) 3 | [![codecov](https://codecov.io/gh/vladimir-kotikov/clink-completions/branch/master/graph/badge.svg)](https://codecov.io/gh/vladimir-kotikov/clink-completions) 4 | 5 | clink-completions 6 | ================= 7 | 8 | Completion files for [Clink](https://github.com/chrisant996/clink) util. Bundled with [Cmder](https://github.com/cmderdev/cmder). 9 | 10 | Requirements 11 | ============ 12 | 13 | These completions requires Clink v0.4.3 or newer. 14 | 15 | Notes 16 | ===== 17 | 18 | The `master` branch of this repo contains all available completions. If you lack some functionality, post a feature request. 19 | 20 | Some completion generators in this bundle use features from the latest Clink distribution. If you get an error messages while using these completions, consider upgrading Clink to the latest version. 21 | 22 | If this doesn't help, feel free to submit an issue. 23 | 24 | Installation and Updates 25 | ======================== 26 | 27 | ### If you use Cmder 28 | 29 | If you're using [Cmder](https://github.com/cmderdev/cmder), then the clink-completions are already bundled with it. 30 | 31 | Installing updates for Cmder also updates clink-completions, but not necessarily the latest clink-completions. 32 | 33 | To update Cmder to use the very latest clink-completions, do this: 34 | 35 | 1. Go to the [Releases](https://github.com/vladimir-kotikov/clink-completions/releases) page. 36 | 2. Download the "Source code (zip)" file under "Assets" for the latest release. 37 | 3. Extract the files to your Cmder `vendor\clink-completions` directory. 38 | 4. Start a new session of Cmder. 39 | 40 | Otherwise, here are a couple of ways to install the clink-completions scripts, when using a recent version of [Clink](https://github.com/chrisant996/clink): 41 | 42 | ### Using git 43 | 44 | 1. Make sure you have [git](https://www.git-scm.com/downloads) installed. 45 | 2. Clone this repo into a new local directory via git clone https://github.com/vladimir-kotikov/clink-completions local_directory (replace local_directory with the name of the directory where you want to install the scripts). 46 | > **Important:** Avoid naming it `completions`, because that's a reserved subdirectory name in Clink. See [Completion directories](https://chrisant996.github.io/clink/clink.html#completion-directories) for more info. 47 | 3. Tell Clink to load scripts from the repo via clink installscripts full_path_to_local_directory. 48 | > **Important:** Specify the full path to the local directory (don't use a relative path). 49 | 4. Start a new session of Clink. 50 | 51 | Get updates using `git pull` and normal git workflow. 52 | 53 | ### From the .zip file 54 | 55 | 1. Go to the [Releases](https://github.com/vladimir-kotikov/clink-completions/releases) page. 56 | 2. Download the "Source code (zip)" file under "Assets" for the latest release. 57 | 3. Extract the files to a local directory. 58 | > **Important:** Avoid naming it `completions`, because that's a reserved subdirectory name in Clink. See [Completion directories](https://chrisant996.github.io/clink/clink.html#completion-directories) for more info. 59 | 4. Tell Clink to load scripts from the repo via clink installscripts full_path_to_local_directory (only when installing the first time; skip this step when updating). 60 | > **Important:** Specify the full path to the local directory (don't use a relative path). 61 | 5. Start a new session of Clink. 62 | 63 | Get updates by following the steps again, but skip step 4. 64 | 65 | Repo structure 66 | ============== 67 | 68 | Script files in the root directory are loaded when Clink starts. 69 | 70 | Scripts in the `completions\` directory are not loaded until the associated command is actually used. Most completion scripts could be located in the completions directory, except that older versions of Clink don't load scripts from the completions directory. 71 | 72 | Scripts in the `modules\` directory contain helper functions. The `!init.lua` script (or `.init.lua` script) tells Clink about the modules and completions directories. 73 | 74 | Scripts in the `spec\` directory are tests which the `busted` package can run. 75 | 76 | 77 | Development and contribution 78 | ============================ 79 | 80 | The new flow is single `master` branch for all more or less valuable changes. The `master` should be clean and show nice history of project. The bugfixes are made and land directly into `master`. 81 | 82 | Feature development should be done in a separate topic branch per feature. Submit a pull request for merging the feature into the `master` branch, and include a meaning commit description for the feature changes. 83 | 84 | Avoid reusing a topic branch after it's been merged into `master`, because reusing leads to unnecessary merge conflicts. The more the topic branch is reused, the harder it will become to accurately resolve the merge conflicts. 85 | 86 | The `dev` branch is volatile and should not be used by contributors. 87 | 88 | Test 89 | ==== 90 | 91 | You will need `busted` package to be installed locally (to `lua_modules` directory). To install it 92 | using Luarocks call `luarocks --lua-version 5.2 install --tree=lua_modules busted`. You might also want to install 93 | `luacov` to get the coverage information. 94 | 95 | After installing call `test.bat` from repo root and watch tests passing. That's it. 96 | 97 | ### Getting `tests` to run on Windows 98 | 99 | > [!IMPORTANT] 100 | > Clink and clink-completions use Lua **5.2**; be sure to download Lua 5.2 (not 5.4 or other versions). 101 | 102 | **Prerequisites:** 103 | 104 | 1. Make a local luabin directory, for example `c:\luabin`. 105 | 2. `set PATH=%PATH%;c:\luabin` to add your luabin directory to the system PATH. 106 | 3. Install Lua 5.2 executables from [LuaBinaries](https://luabinaries.sourceforge.net/download.html) to your luabin directory. 107 | 4. Download Lua 5.2 sources zip from [LuaBinaries](https://luabinaries.sourceforge.net/download.html), and extract the headers from its `include` subdirectory into `include\lua\5.2` under your luabin directory. 108 | 5. Install [MinGW](https://sourceforge.net/projects/mingw/), which is needed because the luasystem luarock wants to build itself from scratch. 109 | 6. Download [luacheck](https://github.com/lunarmodules/luacheck/releases) into your luabin directory. 110 | 7. Download the [luarocks](https://github.com/luarocks/luarocks/wiki/Installation-instructions-for-Windows) executable files into your luabin directory. 111 | 8. `luarocks --local config variables.lua c:\luabin\lua52.exe` to tell luarocks where to find your Lua binaries. 112 | 9. `luarocks --lua-version 5.2 install busted` to install busted. 113 | 10. `luarocks --lua-version 5.2 install luacov` to install luacov. 114 | 11. `set PATH=%PATH%;%USERPROFILE%\AppData\Roaming\luarocks\bin` to add the luarocks bin directory to the system PATH, so that `busted` can be found and executed. 115 | 116 | That should get everything set up. 117 | 118 | **Running `tests`:** 119 | 120 | Make sure the PATH has your luabin directory and the luarocks bin directory (from steps 2 and 11 in the prerequisites above). 121 | 122 | Then run `tests` from the clink-completions repo root. 123 | -------------------------------------------------------------------------------- /completions/delta.lua: -------------------------------------------------------------------------------- 1 | -- Completions for delta (https://github.com/dandavison/delta). 2 | 3 | -------------------------------------------------------------------------------- 4 | local function list_languages(_, _, _, builder) 5 | local m = {} 6 | local f = io.popen('2>nul delta.exe --list-languages') 7 | if f then 8 | if builder.setforcequoting then 9 | builder:setforcequoting() 10 | for line in f:lines() do 11 | local lang = line:match('^([^\x1b]+) \x1b') 12 | if lang then 13 | lang = lang:gsub(' +$', '') 14 | table.insert(m, lang) 15 | end 16 | end 17 | else 18 | for line in f:lines() do 19 | local lang = line:match('^([^\x1b]+) \x1b') 20 | if lang then 21 | lang = lang:gsub(' +$', '') 22 | if lang:find('[+ ()]') then 23 | lang = '"' .. lang .. '"' 24 | end 25 | table.insert(m, lang) 26 | end 27 | end 28 | end 29 | f:close() 30 | end 31 | return m 32 | end 33 | 34 | -------------------------------------------------------------------------------- 35 | local function list_themes(_, _, _, builder) 36 | local m = {} 37 | local f = io.popen('2>nul delta.exe --list-syntax-themes') 38 | if f then 39 | if builder.setforcequoting then 40 | builder:setforcequoting() 41 | for line in f:lines() do 42 | line = line:gsub('^dark%s+', ''):gsub('^light%s+', '') 43 | table.insert(m, line) 44 | end 45 | else 46 | for line in f:lines() do 47 | line = line:gsub('^dark%s+', ''):gsub('^light%s+', '') 48 | if line:find('[+ ()]') then 49 | line = '"' .. line .. '"' 50 | end 51 | table.insert(m, line) 52 | end 53 | end 54 | f:close() 55 | end 56 | return m 57 | end 58 | 59 | -------------------------------------------------------------------------------- 60 | local function add_pending(pending, flags, descriptions, hideflags) -- luacheck: no unused 61 | if pending then 62 | if pending.arginfo and pending.desc and not pending.values then 63 | if pending.arginfo:find('[a-z]') then 64 | pending.values = pending.values or {} 65 | for _, v in ipairs(string.explode(pending.arginfo, '<|>')) do 66 | table.insert(pending.values, v) 67 | end 68 | elseif pending.long == '--default-language' then 69 | pending.values = { list_languages } 70 | elseif pending.long == '--syntax-theme' then 71 | pending.values = { list_themes } 72 | elseif pending.arginfo == '' then 73 | pending.values = { clink.filematches } 74 | elseif pending.arginfo == '' then 75 | pending.values = { history=true, clink.filematches } 76 | end 77 | if not pending.values then 78 | pending.values = { history=true } 79 | end 80 | end 81 | 82 | if pending.desc then 83 | pending.desc = pending.desc:gsub('%([^)]+%)', '') 84 | pending.desc = pending.desc:gsub('For example.*$', '') 85 | pending.desc = pending.desc:gsub('%..*$', '') 86 | pending.desc = pending.desc:gsub(' +$', '') 87 | end 88 | 89 | if pending.values then 90 | local parser = clink.argmatcher():addarg(pending.values) 91 | if pending.short then 92 | table.insert(flags, pending.short .. parser) 93 | end 94 | if pending.long then 95 | table.insert(flags, pending.long .. parser) 96 | end 97 | else 98 | if pending.short then 99 | table.insert(flags, pending.short) 100 | end 101 | if pending.long then 102 | table.insert(flags, pending.long) 103 | end 104 | end 105 | 106 | if pending.desc then 107 | if pending.short then 108 | descriptions[pending.short] = { pending.desc } 109 | if pending.arginfo then 110 | table.insert(descriptions[pending.short], 1, pending.arginfo) 111 | end 112 | end 113 | if pending.long then 114 | descriptions[pending.long] = { pending.desc } 115 | if pending.arginfo then 116 | table.insert(descriptions[pending.long], 1, pending.arginfo) 117 | end 118 | end 119 | end 120 | end 121 | end 122 | 123 | -------------------------------------------------------------------------------- 124 | local inited 125 | 126 | -------------------------------------------------------------------------------- 127 | local function delayinit(argmatcher) 128 | if inited then 129 | return 130 | end 131 | inited = true 132 | 133 | local f = io.popen('2>nul delta.exe --help') 134 | if not f then 135 | return 136 | end 137 | 138 | local flags = {} 139 | local descriptions = {} 140 | local hideflags = {} 141 | local pending 142 | 143 | local section = 'text' 144 | for line in f:lines() do 145 | -- delta has no way to suppress escape codes in --help output? 146 | line = console.plaintext(line) -- luacheck: no global 147 | 148 | local short, long = line:match('^ (%-.), (%-%-%g+)') 149 | if not short then 150 | long = line:match('^ (%-%-%g+)') 151 | end 152 | if long then 153 | add_pending(pending, flags, descriptions, hideflags) 154 | section = 'desc' 155 | pending = {} 156 | pending.short = short 157 | pending.long = long 158 | pending.arginfo = line:match('%-%-%g+ (.*)$') 159 | if pending.arginfo then 160 | pending.arginfo = ' ' .. pending.arginfo 161 | end 162 | elseif section == 'desc' then 163 | line = line:gsub('^( +)', ''):gsub('( +)$', '') 164 | if line == '' then 165 | section = pending.arginfo and 'values' or 'text' 166 | else 167 | if pending.desc then 168 | pending.desc = pending.desc .. ' ' 169 | else 170 | pending.desc = '' 171 | end 172 | pending.desc = pending.desc .. line 173 | end 174 | elseif section == 'values' then 175 | local value = line:match('^ +%* ([-A-Za-z0-9_]+)') 176 | if value then 177 | if not pending.values then 178 | pending.values = {} 179 | end 180 | table.insert(pending.values, value) 181 | end 182 | end 183 | end 184 | add_pending(pending, flags, descriptions, hideflags) 185 | 186 | f:close() 187 | 188 | argmatcher:addflags(flags) 189 | argmatcher:addflags(hideflags) 190 | 191 | if argmatcher.adddescriptions then 192 | argmatcher:adddescriptions(descriptions) 193 | end 194 | if argmatcher.hideflags then 195 | argmatcher:hideflags(hideflags) 196 | end 197 | end 198 | 199 | -------------------------------------------------------------------------------- 200 | clink.argmatcher('delta'):setdelayinit(delayinit) 201 | 202 | -------------------------------------------------------------------------------- /completions/where.lua: -------------------------------------------------------------------------------- 1 | require('arghelper') 2 | 3 | -------------------------------------------------------------------------------- 4 | -- Helper function to remember the /r argument, if any. 5 | 6 | local function onarg_root(arg_index, word, _, _, user_data) 7 | if arg_index == 1 then 8 | -- Remember the /r root argument. 9 | if user_data and user_data.shared_user_data then 10 | word = word:gsub("['\"]", "") 11 | user_data.shared_user_data.where_root = word 12 | end 13 | end 14 | end 15 | 16 | -------------------------------------------------------------------------------- 17 | -- Define flags. 18 | 19 | local dir_matcher = clink.argmatcher():addarg({clink.dirmatches, onarg=onarg_root}) 20 | 21 | -- luacheck: push 22 | -- luacheck: no max line length 23 | local flag_def_table = { 24 | {"/r", dir_matcher, " dir", "Recursively searches and displays the files that match the given pattern starting from the specified directory"}, 25 | {"/q", "Returns only the exit code, without displaying the list of matched files. (Quiet mode)"}, 26 | {"/f", "Displays the matched filename in double quotes"}, 27 | {"/t", "Displays the file size, last modified date and time for all matched files"}, 28 | {"/?", "Displays help message"}, 29 | } 30 | -- luacheck: pop 31 | 32 | local flags = {} 33 | for i = 1, 2 do 34 | for _,f in ipairs(flag_def_table) do 35 | if i == 2 then 36 | f[1] = f[1]:gsub("^/", "-") 37 | end 38 | if f[3] then 39 | table.insert(flags, { f[1]..f[2], f[3], f[4] }) 40 | if f[1]:upper() ~= f[1] then 41 | table.insert(flags, { hide=true, f[1]:upper()..f[2], f[3], f[4] }) 42 | end 43 | else 44 | table.insert(flags, { f[1], f[2] }) 45 | if f[1]:upper() ~= f[1] then 46 | table.insert(flags, { hide=true, f[1]:upper(), f[2] }) 47 | end 48 | end 49 | end 50 | end 51 | 52 | -------------------------------------------------------------------------------- 53 | -- Argmatcher for "where". 54 | 55 | local where = clink.argmatcher("where") 56 | :setflagsanywhere(false) 57 | :_addexflags(flags) 58 | 59 | -------------------------------------------------------------------------------- 60 | -- Allow completion of pattern in "$envvar:pattern" and "path:pattern". 61 | 62 | local where__generator = clink.generator(20) 63 | 64 | function where__generator:generate(line_state, builder) -- luacheck: no unused 65 | -- where__generator exists purely to define getwordbreakinfo(). 66 | return 67 | end 68 | 69 | function where__generator:getwordbreakinfo(line_state) -- luacheck: no unused 70 | local cwi = line_state:getcommandwordindex() 71 | if line_state:getword(cwi):lower() == "where" then 72 | local prev_word = line_state:getword(line_state:getwordcount() - 1) 73 | if prev_word ~= "/r" and prev_word ~= "/R" and prev_word ~= "-r" then 74 | local word = line_state:getendword() 75 | local scope = word:match("^(.*:)[^:]-$") 76 | if scope then 77 | return #scope, 0 78 | end 79 | scope = word:match("^(%$[^:]*)$") 80 | if scope then 81 | return 1, 0 82 | end 83 | end 84 | end 85 | end 86 | 87 | -------------------------------------------------------------------------------- 88 | -- Argument completions. 89 | 90 | local function get_scope(line_state, word_index) 91 | if line_state:getwordcount() == word_index and word_index > 1 then 92 | local last = line_state:getwordinfo(word_index) 93 | local prev = line_state:getwordinfo(word_index - 1) 94 | if prev and last and prev.offset+prev.length == last.offset then 95 | local line = line_state:getline() 96 | local word = line:sub(prev.offset, prev.offset + prev.length - 1) 97 | 98 | -- Does a finished scope exist? ("$var:" or "path:") 99 | local scope, endquote 100 | if prev.quoted then 101 | scope = word:match('^(.*:)$') 102 | endquote = scope:match('":$') 103 | else 104 | scope = word:match('^(.*:)$') 105 | end 106 | if scope then 107 | -- "where" has several quirks with respect to out-of-place 108 | -- quotes in arguments. This catches a couple of them, but 109 | -- isn't trying to (and doesn't) catch all quirks. 110 | local bad 111 | if scope:find('"') then 112 | -- FAILS: pa"th:* 113 | bad = true 114 | elseif prev.quoted and not endquote and line:sub(last.offset, last.offset + 1) == '""' then 115 | -- FAILS: "path:""* 116 | -- works: "path":""* 117 | -- works: path:""* 118 | bad = true 119 | end 120 | return scope, bad 121 | end 122 | 123 | -- Does an unfinished "$var" scope exist? 124 | scope = word:match('^%$[^:]*$') 125 | if scope then 126 | return scope 127 | end 128 | end 129 | end 130 | end 131 | 132 | -------------------------------------------------------------------------------- 133 | -- Argument completion. 134 | 135 | local function where_arg_completion(...) -- luacheck: no unused 136 | local word, word_index, line_state, builder, user_data = ... -- luacheck: no unused 137 | 138 | -- Construct the where command. 139 | local scope, bad = get_scope(line_state, word_index) 140 | if bad then 141 | return {} 142 | elseif scope and scope:find("^%$[^:]*$") then 143 | builder:addmatches(os.getenvnames(), "word") 144 | builder:setappendcharacter(":") 145 | return {} 146 | elseif os.getenv("WHERE_BREAK_PATHPATTERN_SYNTAX") then 147 | -- PROBLEM: This code was for vladimir-kotikov/clink-completions#196. 148 | -- But it's disabled by default because (1) it breaks the `path:pattern` 149 | -- syntax. Also, this code doesn't restrict to only executable files as 150 | -- originally requested, because doing that would break other valid ways 151 | -- to use where. 152 | local where_args 153 | if scope then 154 | -- When "$envvar:pattern" or "path:pattern" are used. 155 | scope = scope:gsub('"', '') 156 | where_args = ' "'..rl.expandtilde(scope)..'*"' 157 | elseif user_data and user_data.shared_user_data and user_data.shared_user_data.where_root then 158 | -- When /r is used. 159 | where_args = ' /r "'..rl.expandtilde(user_data.shared_user_data.where_root)..'" *' 160 | else 161 | -- Default. 162 | where_args = ' *' 163 | end 164 | 165 | -- Collect matches by running where. 166 | if where_args then 167 | local p = io.popen("2>nul where"..where_args) 168 | if p then 169 | -- Add matches. 170 | for l in p:lines() do 171 | -- REVIEW: If filtering by file type is desired, this is a 172 | -- good place to add that. 173 | builder:addmatch(path.getname(l), "file") 174 | end 175 | p:close() 176 | return {} 177 | end 178 | end 179 | end 180 | 181 | return clink.filematches(...) 182 | end 183 | 184 | where:addarg(where_arg_completion):loop(1) 185 | -------------------------------------------------------------------------------- /completions/bat.lua: -------------------------------------------------------------------------------- 1 | -- Completions for bat (https://github.com/sharkdp/bat). 2 | 3 | -------------------------------------------------------------------------------- 4 | local function list_languages(_, _, _, builder) 5 | local m = {} 6 | local f = io.popen('2>nul bat.exe --list-languages') 7 | if f then 8 | if builder.setforcequoting then 9 | builder:setforcequoting() 10 | for line in f:lines() do 11 | local lang = line:match('^([^:]+):') 12 | if lang then 13 | table.insert(m, lang) 14 | end 15 | end 16 | else 17 | for line in f:lines() do 18 | local lang = line:match('^([^:]+):') 19 | if lang then 20 | if lang:find('[+ ()]') then 21 | lang = '"' .. lang .. '"' 22 | end 23 | table.insert(m, lang) 24 | end 25 | end 26 | end 27 | f:close() 28 | end 29 | return m 30 | end 31 | 32 | -------------------------------------------------------------------------------- 33 | local function list_themes(_, _, _, builder) 34 | local m = {} 35 | local f = io.popen('2>nul bat.exe --list-themes') 36 | if f then 37 | if builder.setforcequoting then 38 | builder:setforcequoting() 39 | for line in f:lines() do 40 | table.insert(m, line) 41 | end 42 | else 43 | for line in f:lines() do 44 | if line:find('[+ ()]') then 45 | line = '"' .. line .. '"' 46 | end 47 | table.insert(m, line) 48 | end 49 | end 50 | f:close() 51 | end 52 | return m 53 | end 54 | 55 | -------------------------------------------------------------------------------- 56 | local function add_pending(pending, flags, descriptions, hideflags) -- luacheck: no unused 57 | if pending then 58 | if pending.arginfo and pending.desc and not pending.values then 59 | local values = pending.desc:match('Possible values: ([^.]+)') 60 | if values then 61 | values = values:gsub('*', '') 62 | else 63 | values = pending.desc:match('^[^(.]*%([^)]+%)') 64 | end 65 | if values then 66 | pending.values = pending.values or {} 67 | for _, v in ipairs(string.explode(values, ', ')) do 68 | table.insert(pending.values, v) 69 | end 70 | elseif pending.long == '--language' then 71 | pending.values = { list_languages } 72 | elseif pending.long == '--file-name' then 73 | pending.values = { clink.filematches } 74 | elseif pending.long == '--diff-context' then 75 | pending.values = { '1', '2', '3', '5', '10', '20', '50' } 76 | elseif pending.long == '--tabs' then 77 | pending.values = { '0', '1', '2', '4', '8' } 78 | elseif pending.long == '--terminal-width' then 79 | pending.values = { history=true, '72', '80', '100', '120' } 80 | elseif pending.long == '--theme' then 81 | pending.values = { list_themes } 82 | end 83 | if not pending.values then 84 | pending.values = { history=true } 85 | end 86 | end 87 | 88 | if pending.desc then 89 | pending.desc = pending.desc:gsub('%([^)]+%)', '') 90 | pending.desc = pending.desc:gsub('For example.*$', '') 91 | pending.desc = pending.desc:gsub('%..*$', '') 92 | pending.desc = pending.desc:gsub(' +$', '') 93 | end 94 | 95 | if pending.values then 96 | local parser = clink.argmatcher():addarg(pending.values) 97 | if pending.short then 98 | table.insert(flags, pending.short .. parser) 99 | end 100 | if pending.long then 101 | table.insert(flags, pending.long .. parser) 102 | end 103 | else 104 | if pending.short then 105 | table.insert(flags, pending.short) 106 | end 107 | if pending.long then 108 | table.insert(flags, pending.long) 109 | end 110 | end 111 | 112 | if pending.desc then 113 | if pending.short then 114 | descriptions[pending.short] = { pending.desc } 115 | if pending.arginfo then 116 | table.insert(descriptions[pending.short], 1, pending.arginfo) 117 | end 118 | end 119 | if pending.long then 120 | descriptions[pending.long] = { pending.desc } 121 | if pending.arginfo then 122 | table.insert(descriptions[pending.long], 1, pending.arginfo) 123 | end 124 | end 125 | end 126 | end 127 | end 128 | 129 | -------------------------------------------------------------------------------- 130 | local inited 131 | 132 | -------------------------------------------------------------------------------- 133 | local function delayinit(argmatcher) 134 | if inited then 135 | return 136 | end 137 | inited = true 138 | 139 | local f = io.popen('2>nul bat.exe --help') 140 | if not f then 141 | return 142 | end 143 | 144 | local flags = {} 145 | local descriptions = {} 146 | local hideflags = {} 147 | local pending 148 | 149 | local section = 'text' 150 | for line in f:lines() do 151 | local short, long = line:match('^ (%-.), (%-%-%g+)') 152 | if not short then 153 | long = line:match('^ (%-%-%g+)') 154 | end 155 | if long then 156 | add_pending(pending, flags, descriptions, hideflags) 157 | section = 'desc' 158 | pending = {} 159 | pending.short = short 160 | pending.long = long 161 | pending.arginfo = line:match('%-%-%g+ (.*)$') 162 | if pending.arginfo then 163 | pending.arginfo = ' ' .. pending.arginfo 164 | end 165 | elseif section == 'desc' then 166 | line = line:gsub('^( +)', ''):gsub('( +)$', '') 167 | if line == '' then 168 | section = pending.arginfo and 'values' or 'text' 169 | else 170 | if pending.desc then 171 | pending.desc = pending.desc .. ' ' 172 | else 173 | pending.desc = '' 174 | end 175 | pending.desc = pending.desc .. line 176 | end 177 | elseif section == 'values' then 178 | local value = line:match('^ +%* ([-A-Za-z0-9_]+)') 179 | if value then 180 | if not pending.values then 181 | pending.values = {} 182 | end 183 | table.insert(pending.values, value) 184 | end 185 | end 186 | end 187 | add_pending(pending, flags, descriptions, hideflags) 188 | 189 | f:close() 190 | 191 | argmatcher:addflags(flags) 192 | argmatcher:addflags(hideflags) 193 | 194 | if argmatcher.adddescriptions then 195 | argmatcher:adddescriptions(descriptions) 196 | end 197 | if argmatcher.hideflags then 198 | argmatcher:hideflags(hideflags) 199 | end 200 | end 201 | 202 | -------------------------------------------------------------------------------- 203 | clink.argmatcher('bat'):setdelayinit(delayinit) 204 | 205 | -------------------------------------------------------------------------------- /completions/eza.lua: -------------------------------------------------------------------------------- 1 | require("arghelper") 2 | 3 | -- luacheck: no max line length 4 | 5 | local cols = clink.argmatcher():addarg({fromhistory=true}) 6 | local field = clink.argmatcher():addarg("all", "age", "size") 7 | local levels = clink.argmatcher():addarg({fromhistory=true, "1", "2", "3", "4"}) 8 | local mode = clink.argmatcher():addarg("gradient", "fixed") 9 | local sortfield = clink.argmatcher():addarg("name", "Name", "extension", "Extension", "size", "type", "modified", "accessed", "created", "inode", "none", "date", "time", "old", "new") 10 | local timefield = clink.argmatcher():addarg("modified", "accessed", "created") 11 | local timestyles = clink.argmatcher():addarg({nosort=true, "default", "iso", "long-iso", "full-iso", "relative", '"+%Y-%m-%d %H:%M"'}) 12 | local when = clink.argmatcher():addarg("always", "auto", "never") 13 | 14 | clink.argmatcher("eza") 15 | :_addexflags({ 16 | -- META OPTIONS 17 | { "-?", "show help" }, 18 | { "--help" }, 19 | { "-v", "show version of eza" }, 20 | { "--version" }, 21 | 22 | -- DISPLAY OPTIONS 23 | { "-1", "display one entry per line" }, 24 | { "--oneline" }, 25 | { "-l", "display extended file metadata as a table" }, 26 | { "--long" }, 27 | { "-G", "display entries as a grid (default)" }, 28 | { "--grid" }, 29 | { "-x", "sort the grid across, rather than downwards" }, 30 | { "--across" }, 31 | { "-R", "recurse into directories" }, 32 | { "--recurse" }, 33 | { "-T", "recurse into directories as a tree" }, 34 | { "--tree" }, 35 | { "-X", "dereference symbolic links when displaying information" }, 36 | { "--dereference" }, 37 | { "-F", "display type indicator by file names" }, 38 | { "--classify" }, 39 | { "-F="..when, "WHEN", "when to display type indicator by file names" }, 40 | { "--classify="..when, "WHEN", "" }, 41 | { opteq=true, "-w="..cols, "COLS", "set screen width in columns" }, 42 | { hide=true, "-w"..cols }, 43 | { opteq=true, "--width="..cols, "COLS", "" }, --"set screen width in columns" 44 | { hide=true, "--width"..cols }, 45 | { "--color" }, --"use terminal colors" 46 | { "--color="..when, "WHEN", "" }, --"when to use terminal colors" 47 | { hide=true, "--colour" }, 48 | { hide=true, "--colour="..when }, 49 | { "--color-scale" }, --"highlight levels of all fields distinctly" 50 | { "--color-scale="..field, "FIELD", "" }, --"highlight levels of FIELD distinctly" 51 | { hide=true, "--colour-scale" }, 52 | { hide=true, "--colour-scale="..field }, 53 | { opteq=true, "--color-scale-mode="..mode, "MODE", "" }, --"use gradient or fixed colors in --color-scale" 54 | { hide=true, opteq=true, "--colour-scale-mode="..mode }, 55 | { "--icons" }, --"display icons" 56 | { "--icons="..when, "WHEN", "" }, --"when to display icons" 57 | { "--no-quotes" }, --"don't quote file names with spaces" 58 | { "--hyperlink" }, --"display entries as hyperlinks" 59 | { "--follow-symlinks" }, --"drill down into symbolic links that point to directories" 60 | { "--absolute" }, --"display entries with their absolute path" 61 | 62 | -- FILTERING AND SORTING OPTIONS 63 | { "-a", "show hidden and 'dot' files" }, 64 | { "--all" }, 65 | { "-aa", "also show the '.' and '..' directories" }, 66 | { hide=true, "-A" }, --"equivalent to --all; included for compatibility with `ls -A`" 67 | { hide=true, "--almost-all" }, 68 | { "-d", "list directories as files; don't list their contents" }, 69 | { "--list-dirs" }, 70 | { opteq=true, "-L"..levels, " DEPTH", "limit the depth of recursion" }, 71 | { opteq=true, "--level="..levels, "DEPTH", "" }, 72 | { "-r", "reverse the sort order" }, 73 | { "--reverse" }, 74 | { opteq=true, "-s="..sortfield, "SORT_FIELD", "which field to sort by" }, 75 | { opteq=true, "--sort="..sortfield, "SORT_FIELD", "" }, 76 | { "--group-directories-first" }, --"list directories before other files" 77 | { "--group-directories-last" }, --"list directories after other files" 78 | { "-D", "list only directories" }, 79 | { "--only-dirs" }, 80 | { "-f", "list only files" }, 81 | { "--only-files" }, 82 | { "--show-symlinks" }, --"explicitly show symbolic links (for use with --only-dirs | --only-files)" 83 | { "--no-symlinks" }, --"do not show symbolic links" 84 | -- -I, --ignore-glob GLOBS glob patterns (pipe-separated) of files to ignore 85 | { "--git-ignore" }, --"ignore files mentioned in '.gitignore'" 86 | 87 | -- LONG VIEW OPTIONS 88 | { "-b", "list file sizes with binary prefixes" }, 89 | { "--binary" }, 90 | { "-B", "list file sizes in bytes, without any prefixes" }, 91 | { "--bytes" }, 92 | -- -g, --group list each file's group 93 | -- --smart-group only show group if it has a different name from owner 94 | { "-h", "add a header row to each column" }, 95 | { "--header" }, 96 | -- -H, --links list each file's number of hard links 97 | -- -i, --inode list each file's inode number 98 | { "-m", "use the modified timestamp field" }, 99 | { "--modified" }, 100 | { hide=true, "--changed" }, 101 | -- -M, --mounts show mount details (Linux and Mac only) 102 | -- -n, --numeric list numeric user and group IDs 103 | { "-O", "list file flags (Mac, BSD, and Windows only)" }, 104 | { "--flags" }, 105 | { "--no-permissions" }, --"suppress the permissions field" 106 | -- -o, --octal-permissions list each file's permission in octal format 107 | { "-S", "show size of allocated file system blocks" }, 108 | { "--blocksize" }, 109 | { opteq=true, "-t"..timefield, " FIELD", "which timestamp field to list" }, 110 | { opteq=true, "--time="..timefield, "FIELD", "" }, 111 | { "-u", "use the accessed timestamp field" }, 112 | { "--accessed" }, 113 | { "-U", "use the created timestamp field" }, 114 | { "--created" }, 115 | { opteq=true, "--time-style="..timestyles, "STYLE", "" }, --"how to format timestamps" 116 | -- --total-size show the size of a directory as the size of all files and directories inside (unix only) 117 | { "--no-filesize" }, 118 | -- --no-user suppress the user field 119 | { "--no-time" }, 120 | -- --stdin read file names from stdin, one per line or other separator specified in environment 121 | { "--git" }, --"list each file's Git status, if tracked or ignored" 122 | { "--no-git" }, --"suppress Git status" 123 | { "--git-repos" }, --"list root of git-tree status" 124 | { "--git-repos-no-status" }, --"list each git-repos branch name (much faster)" 125 | }) 126 | --------------------------------------------------------------------------------