envo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ module Envo
2
+ class Error < StandardError
3
+ def initialize(msg)
4
+ super
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,24 @@
1
+ module Envo
2
+ class Host
3
+ def initialize(shell)
4
+ @shell = shell
5
+ end
6
+ attr_reader :shell
7
+
8
+ def env
9
+ ENV
10
+ end
11
+
12
+ def pwd
13
+ Dir.pwd
14
+ end
15
+
16
+ def home
17
+ Dir.home
18
+ end
19
+
20
+ def path_exists?(path)
21
+ File.exist?(path)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ module Envo
2
+ HostShell = -> {
3
+ env_shell = ENV['SHELL']
4
+ break Shell::WinCmd if !env_shell
5
+ if env_shell =~ /bash/
6
+ raise Error.new "bash on Windows (msys) is not supported yet" if env_shell =~ /^[a-zA-Z]\:/
7
+ break Shell::Bash
8
+ end
9
+ raise Error.new "Unknown shell! Please report on https://github.com/iboB/envo/issues"
10
+ }.()
11
+ end
@@ -0,0 +1,27 @@
1
+ module Envo
2
+ class Logger
3
+ ERROR = 0
4
+ WARN = 1
5
+ INFO = 2
6
+ DEBUG = 3
7
+ def initialize()
8
+ @max_level = DEBUG
9
+ end
10
+ attr_accessor :max_level
11
+ def log(level, text)
12
+ return if level > max_level
13
+ stream = level == 0 ? STDERR : STDOUT
14
+ stream.puts(text)
15
+ end
16
+ def plog(level, text)
17
+ return if level > max_level
18
+ stream = level == 0 ? STDERR : STDOUT
19
+ stream.print(text)
20
+ end
21
+ def error(text); log(ERROR, text); end
22
+ def warn(text); log(WARN, text); end
23
+ def puts(text); log(INFO, text); end
24
+ def print(text); plog(INFO, text); end
25
+ def debug(text); log(DEBUG, text); end
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ module Envo
2
+ class ParseResult
3
+ def initialize()
4
+ @opts = {}
5
+ @cmds = []
6
+ end
7
+ attr_accessor :opts, :cmds
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module Envo
2
+ class ParsedCmd
3
+ def initialize(cmd, opts)
4
+ @cmd = cmd
5
+ @opts = opts
6
+ end
7
+ attr_reader :cmd
8
+ attr_accessor :opts
9
+ end
10
+ end
@@ -0,0 +1,58 @@
1
+ require 'csv'
2
+
3
+ module Envo
4
+ class ScriptParser
5
+ def initialize(opts)
6
+ @known_cmds = {}
7
+ @known_opts = opts
8
+ end
9
+ def add_cmd(name, parse_func)
10
+ raise Envo::Error "cmd #{name} is already added to parser" if @known_cmds[name]
11
+ @known_cmds[name] = parse_func
12
+ end
13
+ def parse(lines)
14
+ result = ParseResult.new
15
+
16
+ lines.each_with_index do |line, li|
17
+ li += 1
18
+ line.strip!
19
+ next if line.empty?
20
+ next if line[0] == '#' # comment
21
+
22
+ line_opts = if line[0] == '{' # opts pack
23
+ i = line.index('}')
24
+ raise Envo::Error.new "#{li}: malformed options pack" if !i
25
+ opts = line[1...i].split(',')
26
+ line = line[i+1..]
27
+ opts
28
+ else
29
+ []
30
+ end
31
+
32
+ raise Envo::Error.new "#{li}: missing command" if line.empty?
33
+
34
+ tokens = []
35
+ begin
36
+ tokens = CSV::parse_line(line, col_sep: ' ').compact
37
+ rescue
38
+ puts "AAAAAAA: #{line.inspect}"
39
+ raise Envo::Error.new "#{li}: malformed line"
40
+ end
41
+
42
+ raise Envo::Error.new "#{li}: missing command" if tokens.empty?
43
+ cmd = tokens.shift
44
+ raise Envo::Error.new "#{li}: unknown command '#{cmd}'" if !@known_cmds[cmd]
45
+ parsed_cmd = @known_cmds[cmd].(cmd, tokens, line_opts)
46
+
47
+ cmd_opts = {}
48
+ parsed_cmd.opts.each do |opt|
49
+ cmd_opts.merge! @known_opts.parse_script(opt)
50
+ end
51
+ parsed_cmd.opts = cmd_opts
52
+
53
+ result.cmds << parsed_cmd
54
+ end
55
+ result
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,63 @@
1
+ module Envo
2
+ module Shell
3
+ module Bash
4
+ extend self
5
+
6
+ def installer
7
+ Cli::InstallerBash
8
+ end
9
+
10
+ def path_var_name
11
+ 'PATH'
12
+ end
13
+ def home_var_name
14
+ 'HOME'
15
+ end
16
+
17
+ def likely_abs_path?(val)
18
+ !val.empty? && val[0] == '/'
19
+ end
20
+ def likely_rel_path?(val)
21
+ return !val.empty? && val[0] == '.'
22
+ end
23
+ def fix_path(path)
24
+ path
25
+ end
26
+
27
+ LIST_SEP = ':'
28
+ def likely_list?(val)
29
+ # we have some work
30
+ # if the value includes our list separtor ":", we need to make sure whether a url:port combination is not a better fit
31
+ return false if !val.include?(LIST_SEP)
32
+
33
+ sep_cnt = val.count(LIST_SEP)
34
+ return true if sep_cnt > 2
35
+
36
+ # match scheme://url
37
+ return false if val =~ /^\w+\:\/\//
38
+
39
+ return true if sep_cnt == 2 # everything else with 2 separators is a list
40
+
41
+ # match display type strings address:digit.digit
42
+ return false if val =~ /\:\d.\d$/
43
+
44
+ # match something:number to be interpreted as addr:port
45
+ !(val =~ /.*\:\d+$/)
46
+ end
47
+ def list_to_ar(list)
48
+ list.split(LIST_SEP)
49
+ end
50
+ def ar_to_list(ar)
51
+ ar.join(LIST_SEP)
52
+ end
53
+
54
+ def cmd_set_env_var(name, value)
55
+ escaped = value.to_s.inspect.gsub("'"){ "\\'" }
56
+ "export #{name}=#{escaped}"
57
+ end
58
+ def cmd_unset_env_var(name)
59
+ "unset -v #{name}"
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,47 @@
1
+ module Envo
2
+ module Shell
3
+ module WinCmd
4
+ extend self
5
+
6
+ def installer
7
+ Cli::InstallerWinCmd
8
+ end
9
+
10
+ def path_var_name
11
+ 'Path'
12
+ end
13
+ def home_var_name
14
+ 'HOME'
15
+ end
16
+
17
+ def likely_abs_path?(val)
18
+ val =~ /^[a-zA-Z]\:\\/
19
+ end
20
+ def likely_rel_path?(val)
21
+ return !val.empty? && val[0] == '.'
22
+ end
23
+ def fix_path(path)
24
+ path.gsub('/', '\\')
25
+ end
26
+
27
+ LIST_SEP = ';'
28
+ def likely_list?(val)
29
+ val.include?(LIST_SEP)
30
+ end
31
+ def list_to_ar(list)
32
+ list.split(LIST_SEP)
33
+ end
34
+ def ar_to_list(ar)
35
+ ar.join(LIST_SEP)
36
+ end
37
+
38
+ def cmd_set_env_var(name, value)
39
+ escaped = value # TODO
40
+ "set #{name}=#{escaped}"
41
+ end
42
+ def cmd_unset_env_var(name)
43
+ "set #{name}="
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,71 @@
1
+ module Envo
2
+ class State
3
+ def initialize(env)
4
+ @real_env = env.to_h
5
+ @work_env = nil
6
+ end
7
+
8
+ attr_reader :real_env
9
+
10
+ def set(name, val)
11
+ if val == nil
12
+ unset(name)
13
+ else
14
+ work_env[name] = val.to_s
15
+ end
16
+ end
17
+
18
+ def unset(name)
19
+ work_env.delete(name)
20
+ end
21
+
22
+ def get(name)
23
+ work_env[name]
24
+ end
25
+
26
+ def work_env
27
+ return @work_env if @work_env
28
+ # if @real_env is ENV, we can use to_h to clone it into the work env
29
+ # however it can be an actual hash in which case to_h will return the same one
30
+ # and we would have to use clone
31
+ # so to make this work in all cases we preform a manual shallow copy
32
+ @work_env = @real_env.map { |k, v| [k, v] }.to_h
33
+ end
34
+
35
+ class Patch
36
+ def initialize(removed = [], changed = {}, added = {})
37
+ @removed = removed
38
+ @changed = changed
39
+ @added = added
40
+ end
41
+
42
+ def empty?
43
+ @removed.empty? && @changed.empty? && @added.empty?
44
+ end
45
+
46
+ attr_reader :removed, :changed, :added
47
+ end
48
+
49
+ def diff
50
+ return Patch.new if !@work_env
51
+
52
+ real_names = @real_env.keys
53
+ work_names = @work_env.keys
54
+
55
+ removed_names = real_names - work_names
56
+ added_names = work_names - real_names
57
+ preserved_names = real_names - removed_names
58
+
59
+ changed = preserved_names.map { |v|
60
+ r = @real_env[v]
61
+ w = @work_env[v]
62
+
63
+ r == w ? nil : [v, w]
64
+ }.compact.to_h
65
+
66
+ added = added_names.map { |v| [v, @work_env[v]] }.to_h
67
+
68
+ Patch.new(removed_names, changed, added)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,107 @@
1
+ module Envo
2
+ class ListVal
3
+ def initialize(ar)
4
+ @ar = ar
5
+ end
6
+ attr_reader :ar
7
+
8
+ def insert(elem, pos = nil)
9
+ # assume unique elements
10
+ old_index = @ar.index(elem)
11
+ new_index = case pos
12
+ when :front then 0
13
+ when :back then -1
14
+ else old_index
15
+ end
16
+
17
+ return @ar << elem if !new_index
18
+ return @ar if new_index == old_index
19
+ return @ar.insert(new_index, elem) if !old_index
20
+
21
+ # we need to reorder
22
+ @ar.delete_at(old_index)
23
+ @ar.insert(new_index, elem)
24
+ end
25
+ def delete(elem)
26
+ @ar.delete(elem)
27
+ end
28
+ def delete_at(index)
29
+ @ar.delete_at(index)
30
+ end
31
+ def uniq!
32
+ @ar.uniq!
33
+ end
34
+ def clean!
35
+ uniq!
36
+ end
37
+ def shift(elem, dir)
38
+ i = @ar.index(elem)
39
+ return nil if i == nil
40
+ shift_at(i, dir)
41
+ end
42
+ def shift_at(i, dir)
43
+ return nil if i>@ar.size
44
+
45
+ if dir == :front
46
+ return i if i == 0
47
+ elem = ar[i]
48
+ @ar.delete_at i
49
+ @ar.unshift(elem)
50
+ 0
51
+ elsif dir == :back
52
+ return i if i == (@ar.size-1)
53
+ elem = ar[i]
54
+ @ar.delete_at i
55
+ @ar << elem
56
+ @ar.size-1
57
+ elsif dir == :up
58
+ return i if i == 0
59
+ @ar[i-1], @ar[i] = @ar[i], @ar[i-1]
60
+ i - 1
61
+ elsif dir == :down
62
+ return i if i == (@ar.size-1)
63
+ @ar[i+1], @ar[i] = @ar[i], @ar[i+1]
64
+ i + 1
65
+ else
66
+ -1
67
+ end
68
+ end
69
+
70
+ def pp_attribs(elem)
71
+ @ar.count(elem) > 1 ? 'D' : ' '
72
+ end
73
+ def pretty_print(ctx)
74
+ ctx.puts "["
75
+ @ar.each_with_index do |v, i|
76
+ str = pp_attribs(v) + ' '
77
+ str += "#{i}:".ljust(4)
78
+ str += v
79
+ ctx.puts str
80
+ end
81
+ ctx.puts ']'
82
+ end
83
+
84
+ # casts
85
+ def type
86
+ :list
87
+ end
88
+ def accept_assign?(other)
89
+ other.list?
90
+ end
91
+ def invalid_description
92
+ @ar.empty? ? "empty list" : nil
93
+ end
94
+ def list?
95
+ true
96
+ end
97
+ def to_list
98
+ return self
99
+ end
100
+ def accept_item?(item)
101
+ true
102
+ end
103
+ def to_s
104
+ raise StandardError.new "list can't be converted to String"
105
+ end
106
+ end
107
+ end