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.
@@ -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
@@ -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
@@ -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