envo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/envo-install +18 -0
- data/bin/envo_gen_tmp_helper.rb +50 -0
- data/bin/envo_run +30 -0
- data/lib/envo.rb +45 -0
- data/lib/envo/cli/envo.bat +11 -0
- data/lib/envo/cli/envo.sh +6 -0
- data/lib/envo/cli/help.rb +20 -0
- data/lib/envo/cli/installer_bash.rb +131 -0
- data/lib/envo/cli/installer_win_cmd.rb +96 -0
- data/lib/envo/cli/runner.rb +129 -0
- data/lib/envo/cli_parser.rb +54 -0
- data/lib/envo/cmd_clean.rb +53 -0
- data/lib/envo/cmd_copy.rb +56 -0
- data/lib/envo/cmd_list.rb +50 -0
- data/lib/envo/cmd_list_add.rb +102 -0
- data/lib/envo/cmd_list_del.rb +78 -0
- data/lib/envo/cmd_path.rb +28 -0
- data/lib/envo/cmd_reset.rb +66 -0
- data/lib/envo/cmd_run.rb +68 -0
- data/lib/envo/cmd_set.rb +76 -0
- data/lib/envo/cmd_show.rb +82 -0
- data/lib/envo/cmd_swap.rb +49 -0
- data/lib/envo/cmd_unset.rb +45 -0
- data/lib/envo/context.rb +156 -0
- data/lib/envo/error.rb +7 -0
- data/lib/envo/host.rb +24 -0
- data/lib/envo/host_shell.rb +11 -0
- data/lib/envo/logger.rb +27 -0
- data/lib/envo/parse_result.rb +9 -0
- data/lib/envo/parsed_cmd.rb +10 -0
- data/lib/envo/script_parser.rb +58 -0
- data/lib/envo/shell/bash.rb +63 -0
- data/lib/envo/shell/win_cmd.rb +47 -0
- data/lib/envo/state.rb +71 -0
- data/lib/envo/val/list_val.rb +107 -0
- data/lib/envo/val/no_val.rb +35 -0
- data/lib/envo/val/path_list_val.rb +24 -0
- data/lib/envo/val/path_val.rb +41 -0
- data/lib/envo/val/string_val.rb +40 -0
- data/lib/envo/val/val_builder.rb +59 -0
- data/lib/envo/version.rb +4 -0
- metadata +89 -0
data/lib/envo/cmd_run.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
module Envo
|
2
|
+
class CmdRun
|
3
|
+
Name = 'run'
|
4
|
+
def self.register_help(help)
|
5
|
+
help.add_cmd 'run <script>', <<~EOF
|
6
|
+
run a script of envo commands
|
7
|
+
if script is a relative or an absolute path, it tries to load the exact filename
|
8
|
+
otherwise it searches for '<script>.envoscript' in .envo/ subdirs of current tree and in <home>/.envo/
|
9
|
+
EOF
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.register_cli_parser(parser)
|
13
|
+
parser.add_cmd(Name, ->(cmd, args) { parse_cli(args) })
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.register_script_parser(parser)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.parse_cli(args)
|
20
|
+
opts = CliParser.filter_opts(args)
|
21
|
+
raise Envo::Error.new "run: provide a single script name. Use 'run <script>'" if args.size != 1
|
22
|
+
ParsedCmd.new(CmdRun.new(args[0]), opts)
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(script)
|
26
|
+
@script = script
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :script
|
30
|
+
|
31
|
+
module Opts
|
32
|
+
extend self
|
33
|
+
def parse_script(opt)
|
34
|
+
case opt
|
35
|
+
when 'force' then return {interact: :force}
|
36
|
+
when 'no-force' then return {interact: :noforce}
|
37
|
+
when 'interactive' then return {interact: :interact}
|
38
|
+
when 'raw' then return {raw: true}
|
39
|
+
else raise Envo::Error.new "script option: #{opt}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def execute(ctx)
|
45
|
+
file = ctx.find_script(@script)
|
46
|
+
lines = ctx.load_script(file)
|
47
|
+
parser = ScriptParser.new(Opts)
|
48
|
+
|
49
|
+
[
|
50
|
+
CmdShow,
|
51
|
+
CmdSet,
|
52
|
+
CmdReset,
|
53
|
+
CmdUnset,
|
54
|
+
CmdList,
|
55
|
+
CmdClean,
|
56
|
+
CmdCopy,
|
57
|
+
CmdSwap,
|
58
|
+
CmdPath,
|
59
|
+
CmdRun,
|
60
|
+
].each { |cmd| cmd.register_script_parser(parser) }
|
61
|
+
|
62
|
+
res = parser.parse(lines)
|
63
|
+
|
64
|
+
scope = ctx.new_scope({interact: :force})
|
65
|
+
scope.execute(res)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/envo/cmd_set.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
module Envo
|
2
|
+
class CmdSet
|
3
|
+
Name = 'set'
|
4
|
+
def self.register_help(help)
|
5
|
+
help.add_cmd "set <name>=<val>", <<~EOF
|
6
|
+
set a value to an environment variable
|
7
|
+
'set name=' unsets the value
|
8
|
+
EOF
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.register_cli_parser(parser)
|
12
|
+
parser.add_cmd(Name, ->(cmd, args) { parse_cli(args) })
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.register_script_parser(parser)
|
16
|
+
parser.add_cmd(Name, ->(cmd, tokens, opts) { parse_tokens(tokens, opts) })
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.parse_cli(args)
|
20
|
+
opts = CliParser.filter_opts(args)
|
21
|
+
parse_tokens(args, opts)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.parse_tokens(args, opts)
|
25
|
+
# find first instance of equals
|
26
|
+
i = args.find_index { |arg| arg =~ /=/ }
|
27
|
+
raise Envo::Error.new "set: missing '='. Use 'set <name> = <val>'" if !i
|
28
|
+
|
29
|
+
elem = args[i]
|
30
|
+
eq_i = elem.index('=')
|
31
|
+
|
32
|
+
first = elem[0...eq_i]
|
33
|
+
second = elem[eq_i+1..]
|
34
|
+
split = [first, '=', second].select { |s| !s.empty? }
|
35
|
+
|
36
|
+
args[i..i] = split
|
37
|
+
|
38
|
+
i = args.index('=')
|
39
|
+
raise Envo::Error.new "set: bad name '#{args[0...i].join(' ')}'. Use 'set <name> = <val>'" if i != 1
|
40
|
+
|
41
|
+
return ParsedCmd.new(CmdUnset.new([args[0]]), opts) if args.size == 2
|
42
|
+
|
43
|
+
ParsedCmd.new(CmdSet.new(args[0], args[2..]), opts)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.parse_script(args)
|
47
|
+
puts "#{Name} parse_script"
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize(name, value)
|
51
|
+
@name = name
|
52
|
+
@value = value
|
53
|
+
end
|
54
|
+
|
55
|
+
attr_accessor :name, :value
|
56
|
+
|
57
|
+
def execute(ctx)
|
58
|
+
ename = ctx.expand_name(@name)
|
59
|
+
new_val = ctx.expand_value(@value)
|
60
|
+
|
61
|
+
old_val = ctx.smart_get(ename)
|
62
|
+
|
63
|
+
ok = old_val.type == new_val.type
|
64
|
+
ok ||= old_val.accept_assign?(new_val)
|
65
|
+
ok ||= ctx.ask("Assign #{new_val.type} to #{old_val.type}?")
|
66
|
+
raise Envo::Error.new "set: assignment of #{new_val.type} to #{old_val.type}" if !ok
|
67
|
+
|
68
|
+
idesc = new_val.invalid_description
|
69
|
+
ok = !idesc
|
70
|
+
ok ||= ctx.ask("Assign #{idesc} to #{ename}?")
|
71
|
+
raise Envo::Error.new "set: assignment of #{idesc} to #{ename}" if !ok
|
72
|
+
|
73
|
+
ctx.smart_set(ename, new_val)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Envo
|
2
|
+
class CmdShow
|
3
|
+
Name = 'show'
|
4
|
+
def self.register_help(help)
|
5
|
+
help.add_cmd "show <name> ...", <<~EOF
|
6
|
+
show values of environment variables
|
7
|
+
--name - display the name of the variable along with the value
|
8
|
+
shorhand: 's'
|
9
|
+
EOF
|
10
|
+
|
11
|
+
help.add_cmd 'rshow <name> ...', <<~EOF
|
12
|
+
show the *raw* value of environment variables with no pretty prints
|
13
|
+
a shortcut to 'show --raw <name> ...'
|
14
|
+
EOF
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.register_cli_parser(parser)
|
18
|
+
parser.add_cmd('show', ->(cmd, args) { parse_cli(args) })
|
19
|
+
parser.add_cmd('s', ->(cmd, args) { parse_cli(args) })
|
20
|
+
parser.add_cmd('rshow', ->(cmd, args) { parse_cli(args + ['--raw']) })
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.register_script_parser(parser)
|
24
|
+
parser.add_cmd(Name, ->(cmd, tokens, opts) { parse_script(tokens, opts) })
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.parse_cli(args)
|
28
|
+
opts = CliParser.filter_opts(args)
|
29
|
+
show_names = false
|
30
|
+
opts.filter! do |opt|
|
31
|
+
if opt == '--name'
|
32
|
+
show_names = true
|
33
|
+
false
|
34
|
+
else
|
35
|
+
true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
ParsedCmd.new(CmdShow.new(args, show_names), opts)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.parse_script(tokens, opts)
|
42
|
+
show_names = false
|
43
|
+
opts.filter! do |opt|
|
44
|
+
if opt == 'name'
|
45
|
+
show_names = true
|
46
|
+
false
|
47
|
+
else
|
48
|
+
true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
ParsedCmd.new(CmdShow.new(tokens, show_names), opts)
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(names, show_names)
|
55
|
+
raise Error.new 'show: no names provided' if names.empty?
|
56
|
+
@names = names
|
57
|
+
@show_names = show_names
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_reader :names, :show_names
|
61
|
+
|
62
|
+
def execute(ctx)
|
63
|
+
@names.each do |name|
|
64
|
+
ename = ctx.expand_name(name)
|
65
|
+
|
66
|
+
pname = show_names ? "#{ename}=" : ''
|
67
|
+
|
68
|
+
if ctx.raw?
|
69
|
+
ctx.puts("#{pname}#{ctx.raw_get(ename)}")
|
70
|
+
else
|
71
|
+
val = ctx.smart_get(ename)
|
72
|
+
if val.type == :empty
|
73
|
+
ctx.puts("No var with name #{ename}")
|
74
|
+
else
|
75
|
+
ctx.print(pname)
|
76
|
+
val.pretty_print(ctx)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Envo
|
2
|
+
class CmdSwap
|
3
|
+
Name = 'swap'
|
4
|
+
def self.register_help(help)
|
5
|
+
help.add_cmd 'swap <name1> <name2>', "swap values of two environment variables"
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.register_cli_parser(parser)
|
9
|
+
parser.add_cmd(Name, ->(cmd, args) { parse_cli(args) })
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.register_script_parser(parser)
|
13
|
+
parser.add_cmd(Name, ->(cmd, tokens, opts) { parse_tokens(tokens, opts) })
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse_cli(args)
|
17
|
+
opts = CliParser.filter_opts(args)
|
18
|
+
parse_tokens(args, opts)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.parse_tokens(args, opts)
|
22
|
+
raise Envo::Error.new "swap: provide two names to swap. Use 'swap <name1> <name2>'" if args.size != 2
|
23
|
+
ParsedCmd.new(CmdSwap.new(args[0], args[1]), opts)
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(name_a, name_b)
|
27
|
+
@name_a = name_a
|
28
|
+
@name_b = name_b
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_accessor :name_a, :name_b
|
32
|
+
|
33
|
+
def execute(ctx)
|
34
|
+
ea = ctx.expand_name(@name_a)
|
35
|
+
raw_a = ctx.raw_get(ea)
|
36
|
+
raise Envo::Error.new "swap: no such var '#{ea}'" if !raw_a && !ctx.force?
|
37
|
+
|
38
|
+
eb = ctx.expand_name(@name_b)
|
39
|
+
raw_b = ctx.raw_get(eb)
|
40
|
+
raise Envo::Error.new "swap: no such var '#{eb}'" if !raw_b && !ctx.force?
|
41
|
+
|
42
|
+
return if ea == eb # swap something with itself...
|
43
|
+
return if !raw_a && !raw_b # no point in doing anything if they don't exist
|
44
|
+
|
45
|
+
ctx.raw_set(ea, raw_b)
|
46
|
+
ctx.raw_set(eb, raw_a)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Envo
|
2
|
+
class CmdUnset
|
3
|
+
Name = 'unset'
|
4
|
+
def self.register_help(help)
|
5
|
+
help.add_cmd 'unset <name> ...', "unset values of one or more environment variables"
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.register_cli_parser(parser)
|
9
|
+
parser.add_cmd(Name, ->(cmd, args) { parse_cli(args) })
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.register_script_parser(parser)
|
13
|
+
parser.add_cmd(Name, ->(cmd, tokens, opts) { parse_tokens(tokens, opts) })
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse_cli(args)
|
17
|
+
opts = CliParser.filter_opts(args)
|
18
|
+
ParsedCmd.new(CmdUnset.new(args), opts)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.parse_tokens(tokens, opts)
|
22
|
+
ParsedCmd.new(CmdUnset.new(tokens), opts)
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(names)
|
26
|
+
raise Envo::Error.new 'unset: no names provided' if names.empty?
|
27
|
+
@names = names
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :names
|
31
|
+
|
32
|
+
def execute(ctx)
|
33
|
+
@names.each do |name|
|
34
|
+
ename = ctx.expand_name(name)
|
35
|
+
raw_old_val = ctx.raw_get(ename)
|
36
|
+
|
37
|
+
if raw_old_val
|
38
|
+
ctx.unset(ename)
|
39
|
+
else
|
40
|
+
raise Envo::Error.new "unset: no such var '#{ename}'" if !ctx.force?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/envo/context.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
module Envo
|
2
|
+
class Context
|
3
|
+
def initialize(host, log, opts, state = nil)
|
4
|
+
@host = host
|
5
|
+
@log = log
|
6
|
+
@default_opts = opts
|
7
|
+
@opts = opts
|
8
|
+
reflect_opts_change
|
9
|
+
|
10
|
+
@state = state || State.new(host.env)
|
11
|
+
|
12
|
+
create_common_locals
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :host, :state
|
16
|
+
|
17
|
+
# create another context based on this one (same state, log, and host)
|
18
|
+
# which provides different opts an locals
|
19
|
+
# thus scripts can be executed which don't leak values in the scope above
|
20
|
+
def new_scope(defaults = {})
|
21
|
+
Context.new(@host, @log, @opts.merge(defaults), @state)
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_script(script)
|
25
|
+
if @host.shell.likely_rel_path?(script) || @host.shell.likely_abs_path?(script)
|
26
|
+
return script if @host.path_exists?(script)
|
27
|
+
raise Envo::Error.new "'#{script}' doesn't exist"
|
28
|
+
end
|
29
|
+
|
30
|
+
# look for '.envo/<file>.envoscript'
|
31
|
+
script = script + '.envoscript'
|
32
|
+
dir = @host.pwd
|
33
|
+
found = while true
|
34
|
+
check = File.join(dir, '.envo', script)
|
35
|
+
break check if @host.path_exists?(check)
|
36
|
+
new_dir = File.dirname(dir)
|
37
|
+
break nil if new_dir == dir
|
38
|
+
dir = new_dir
|
39
|
+
end
|
40
|
+
|
41
|
+
if !found
|
42
|
+
check = File.join(@host.home, '.envo', script)
|
43
|
+
found = check if @host.path_exists?(check)
|
44
|
+
end
|
45
|
+
|
46
|
+
raise Envo::Error.new "Can't find '#{script}' in .envo/ parent dirs or in <home>/.envo/" if !found
|
47
|
+
found
|
48
|
+
end
|
49
|
+
|
50
|
+
def load_script(path)
|
51
|
+
File.readlines(path)
|
52
|
+
end
|
53
|
+
|
54
|
+
# parse access
|
55
|
+
def expand_name(name)
|
56
|
+
@locals[name] || name
|
57
|
+
end
|
58
|
+
def expand_value(val)
|
59
|
+
if raw?
|
60
|
+
if val.class == Array
|
61
|
+
if val.size == 1
|
62
|
+
StringVal.new(val[0])
|
63
|
+
else
|
64
|
+
ListVal.new(val)
|
65
|
+
end
|
66
|
+
else
|
67
|
+
StringVal.new(val)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
ValBuilder.from_user_text(val, @host)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
# local vars
|
74
|
+
def local_var_name?(name)
|
75
|
+
name =~ /^@[a-zA-Z]/
|
76
|
+
end
|
77
|
+
def set_local_var(name, value)
|
78
|
+
@locals[name] = value
|
79
|
+
end
|
80
|
+
def create_common_locals
|
81
|
+
@locals = {
|
82
|
+
'@path' => host.shell.path_var_name,
|
83
|
+
'@home' => host.shell.home_var_name,
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
# env access
|
88
|
+
def smart_get(name)
|
89
|
+
ValBuilder.from_env_string(raw_get(name), @host)
|
90
|
+
end
|
91
|
+
def raw_get(name)
|
92
|
+
@state.get(name)
|
93
|
+
end
|
94
|
+
|
95
|
+
def smart_set(name, value)
|
96
|
+
if value.list?
|
97
|
+
rv = @host.shell.ar_to_list(value.ar)
|
98
|
+
raw_set(name, rv)
|
99
|
+
else
|
100
|
+
raw_set(name, value.to_env_s)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
def raw_set(name, value)
|
104
|
+
@state.set(name, value)
|
105
|
+
end
|
106
|
+
|
107
|
+
def unset(name)
|
108
|
+
@state.unset(name)
|
109
|
+
end
|
110
|
+
|
111
|
+
# opt queries
|
112
|
+
def raw?
|
113
|
+
@opts[:raw]
|
114
|
+
end
|
115
|
+
def force?
|
116
|
+
@opts[:interact] == :force
|
117
|
+
end
|
118
|
+
def noforce?
|
119
|
+
@opts[:interact] == :noforce
|
120
|
+
end
|
121
|
+
def interact?
|
122
|
+
@opts[:interact] == :interact
|
123
|
+
end
|
124
|
+
|
125
|
+
def reflect_opts_change
|
126
|
+
@log.max_level = @opts[:log_level]
|
127
|
+
end
|
128
|
+
|
129
|
+
# io
|
130
|
+
def ask(question)
|
131
|
+
return true if force?
|
132
|
+
return false if noforce?
|
133
|
+
|
134
|
+
print "#{question} (y/n): "
|
135
|
+
answer = STDIN.gets.chomp
|
136
|
+
answer.downcase!
|
137
|
+
return answer == 'y' || answer == 'yes'
|
138
|
+
end
|
139
|
+
|
140
|
+
def error(text); @log.error(text); end
|
141
|
+
def warn(text); @log.warn(text); end
|
142
|
+
def print(text); @log.print(text); end
|
143
|
+
def puts(text); @log.puts(text); end
|
144
|
+
def debug(text); @log.debug(text); end
|
145
|
+
|
146
|
+
# execution
|
147
|
+
def execute(pack)
|
148
|
+
pack_opts = pack.opts
|
149
|
+
pack.cmds.each do |cmd|
|
150
|
+
@opts = @default_opts.merge(cmd.opts, pack_opts)
|
151
|
+
reflect_opts_change
|
152
|
+
cmd.cmd.execute(self)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|