envo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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