aboutsummaryrefslogtreecommitdiffhomepage
path: root/core/args.lua
blob: d42255397ce308447ba0d386e54e3db1d43c04b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
-- Copyright 2007-2022 Mitchell. See LICENSE.

local M = {}

--[[ This comment is for LuaDoc.
---
-- Processes command line arguments for Textadept.
-- @field _G.events.ARG_NONE (string)
--   Emitted when no command line arguments are passed to Textadept on startup.
module('args')]]

events.ARG_NONE = 'arg_none'

-- Map of registered command line options.
-- @class table
-- @name options
local options = {}

---
-- Registers a command line option with short and long versions *short* and *long*, respectively.
-- *narg* is the number of arguments the option accepts, *f* is the function called when the
-- option is set, and *description* is the option's description when displaying help.
-- @param short The string short version of the option.
-- @param long The string long version of the option.
-- @param narg The number of expected parameters for the option.
-- @param f The Lua function to run when the option is set. It is passed *narg* string arguments.
-- @param description The string description of the option for command line help.
-- @name register
function M.register(short, long, narg, f, description)
  local option = {
    narg = assert_type(narg, 'number', 3), f = assert_type(f, 'function', 4),
    description = assert_type(description, 'string', 5)
  }
  options[assert_type(short, 'string', 1)] = option
  options[assert_type(long, 'string', 2)] = option
end

-- Processes command line argument table *arg*, handling options previously defined using
-- `args.register()` and treating unrecognized arguments as filenames to open.
-- Emits an `ARG_NONE` event when no arguments are present unless *no_emit_arg_none* is `true`.
-- @param arg Argument table.
-- @param no_emit_arg_none When `true`, do not emit `ARG_NONE` when no arguments are present.
--   The default value is `false`.
-- @see register
-- @see _G.events
local function process(arg, no_emit_arg_none)
  local no_args = true
  local i = 1
  while i <= #arg do
    local option = options[arg[i]]
    if option then
      option.f(table.unpack(arg, i + 1, i + option.narg))
      i = i + option.narg
    else
      local filename = lfs.abspath(arg[i], arg[-1] or lfs.currentdir())
      if lfs.attributes(filename, 'mode') ~= 'directory' then
        io.open_file(filename)
      else
        lfs.chdir(filename)
      end
      no_args = false
    end
    i = i + 1
  end
  if no_args and not no_emit_arg_none then events.emit(events.ARG_NONE) end
end
events.connect(events.INITIALIZED, function() if arg then process(arg) end end)
-- Undocumented, single-instance event handler for forwarding arguments.
events.connect('command_line', function(arg) process(arg, true) end)

if not CURSES then
  -- Shows all registered command line options on the command line.
  M.register('-h', '--help', 0, function()
    print('Usage: textadept [args] [filenames]')
    local list = {}
    for name in pairs(options) do list[#list + 1] = name end
    table.sort(list, function(a, b) return a:match('[^-]+') < b:match('[^-]+') end)
    for _, name in ipairs(list) do
      local option = options[name]
      print(string.format('  %s [%d args]: %s', name, option.narg, option.description))
    end
    os.exit()
  end, 'Shows this')
  -- Shows Textadept version and copyright on the command line.
  M.register('-v', '--version', 0, function()
    print(_RELEASE .. '\n' .. _COPYRIGHT)
    quit()
  end, 'Prints Textadept version and copyright')
  -- After Textadept finishes initializing and processes arguments, remove the help and
  -- version options in order to prevent another instance from sending '-h', '--help', '-v',
  -- and '--version' to the first instance, killing the latter.
  events.connect(events.INITIALIZED, function()
    options['-h'], options['--help'] = nil, nil
    options['-v'], options['--version'] = nil, nil
  end)
end

-- Set `_G._USERHOME`.
-- This needs to be set as soon as possible since the processing of arguments is positional.
_USERHOME = os.getenv(not WIN32 and 'HOME' or 'USERPROFILE') .. '/.textadept'
for i, option in ipairs(arg) do
  if (option == '-u' or option == '--userhome') and arg[i + 1] then
    _USERHOME = arg[i + 1]
    break
  end
end
local mode = lfs.attributes(_USERHOME, 'mode')
assert(not mode or mode == 'directory', '"%s" is not a directory', _USERHOME)
if not mode then assert(lfs.mkdir(_USERHOME), 'cannot create "%s"', _USERHOME) end
local user_init = _USERHOME .. '/init.lua'
mode = lfs.attributes(user_init, 'mode')
assert(not mode or mode == 'file', '"%s" is not a file (%s)', user_init, mode)
if not mode then assert(io.open(user_init, 'w'), 'unable to create "%s"', user_init):close() end

-- Placeholders.
M.register('-u', '--userhome', 1, function() end, 'Sets alternate _USERHOME')
M.register('-f', '--force', 0, function() end, 'Forces unique instance')

-- Run unit tests.
-- Note: have them run after the last `events.INITIALIZED` handler so everything is completely
-- initialized (e.g. menus, macro module, etc.).
M.register('-t', '--test', 1, function(patterns)
  events.connect(events.INITIALIZED, function()
    local arg = {}
    for patt in (patterns or ''):gmatch('[^,]+') do arg[#arg + 1] = patt end
    local env = setmetatable({arg = arg}, {__index = _G})
    assert(loadfile(_HOME .. '/test/test.lua', 't', env))()
  end)
end, 'Runs unit tests indicated by comma-separated list of patterns (or all)')

return M