confinicky 0.1.6 → 0.2.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,182 @@
1
+ module Confinicky
2
+
3
+ module Controllers
4
+
5
+ ##
6
+ # The command group controller allows you to manipulate the
7
+ # contents of a specific grouping of commands in a nice OO
8
+ # way.
9
+ class Commands
10
+
11
+ ##
12
+ # The path to the file on disk representing the model.
13
+ attr_reader :path
14
+
15
+ def initialize(file_type_key: :env)
16
+ @path = Confinicky::ConfigurationFile.path_for_key(key: file_type_key)
17
+ @shell_file = Confinicky::ShellFile.new(file_path: path)
18
+ @commands = []
19
+ @table_tite = "Commands"
20
+ end
21
+
22
+ ##
23
+ # Matches a given string against the names of the group's contents.
24
+ #
25
+ # ==== Attributes
26
+ #
27
+ # * +query+ - The string used to match a given command name.
28
+ #
29
+ # ==== Examples
30
+ #
31
+ # # Find the PATH environment variable.
32
+ # Exports.new.find("PATH")
33
+ # # => {name: "PATH", value: "/Users/name/bin/"}
34
+ def find(query: nil)
35
+ match = @commands.find{|command| command[0] =~ /^#{query}/ }
36
+ {name: match[0], value: match[1]} unless match.nil?
37
+ end
38
+
39
+ ##
40
+ # Detects duplicate definitions.
41
+ def duplicates
42
+ duplicates = {}
43
+ @commands.each do |command|
44
+ duplicates[command[0]] = (duplicates[command[0]].nil?) ? 1 : duplicates[command[0]]+1
45
+ end
46
+ duplicates.delete_if { |key,value| value==1}.sort_by{|key,value| value}.reverse
47
+ end
48
+
49
+ ##
50
+ # Finds duplicate export statements and reduces them to the
51
+ # most recent statement.
52
+ def clean!
53
+ for duplicate in duplicates.map{|duplicate| duplicate[0]}
54
+ last_value = @commands.find_all{|c| c[0] =~ /^#{duplicate}/ }.last
55
+ @commands.delete_if{ |c| c[0] == duplicate}
56
+ @commands << [duplicate, last_value]
57
+ end
58
+ end
59
+
60
+ ##
61
+ # Parses an assignment such as "MY_VAR=1234" and injects it into
62
+ # the exports or updates an existing variable if possible.
63
+ #
64
+ # ==== Attributes
65
+ #
66
+ # * +assignment+ - The value which will be assigned to the command.
67
+ #
68
+ # ==== Examples
69
+ #
70
+ # # Create or update an environment variable called MY_VAR.
71
+ # Exports.new.set("MY_VAR=A short phrase.")
72
+ #
73
+ # # Create or update an environment variable called MY_VAR.
74
+ # Aliases.new.set("home=cd ~")
75
+ def set!(assignment)
76
+ assignment = assignment.split("=")
77
+ return false if assignment.length < 2
78
+ remove! assignment[0]
79
+ assignment[1] = "\'#{assignment[1]}\'" if assignment[1] =~ /\s/
80
+ @commands << assignment
81
+ end
82
+
83
+ ##
84
+ # Removes an environment variable if it exists.
85
+ def remove!(variable_name)
86
+ @commands.delete_if { |i| i[0] == variable_name }
87
+ end
88
+
89
+ ##
90
+ # Updates the actual shell file on disk.
91
+ def save!
92
+ @shell_file.write!
93
+ end
94
+
95
+ ##
96
+ # Creates a copy of the associated shell file.
97
+ def backup!
98
+ @shell_file.backup!
99
+ end
100
+
101
+ ##
102
+ # The total number of commands managed by the controller.
103
+ def length
104
+ @commands.length
105
+ end
106
+
107
+ ##
108
+ # Creates a table representation of the command data.
109
+ def to_table
110
+ make_table(title: @table_title, rows: @commands)
111
+ end
112
+
113
+ ##
114
+ # Returns a table for the contents of a specific variable when split
115
+ # by a specified separating string.
116
+ #
117
+ # ==== Attributes
118
+ #
119
+ # * +name+ - The name of the variable, alias, etc., to inspect.
120
+ # * +separator+ - A string used to split the value. Defaults to a ':'.
121
+ #
122
+ # ==== Examples
123
+ #
124
+ # # Create or update an environment variable called MY_VAR.
125
+ # Exports.inspect("PATH")
126
+ # # +--------+-----------------------------------------------------------+
127
+ # # | Values in PATH |
128
+ # # +--------+-----------------------------------------------------------+
129
+ # # | index | value |
130
+ # # +--------+-----------------------------------------------------------+
131
+ # # | 1 | /Users/name/.rvm/gems/ruby-2.1.2/bin |
132
+ # # | 2 | /Users/name/.rvm/gems/ruby-2.1.2@global/bin |
133
+ # # | 3 | /Users/name/.rvm/rubies/ruby-2.1.2/bin |
134
+ # # +--------+-----------------------------------------------------------+
135
+ def inspect(name: nil, separator:":")
136
+ return nil if (match = find(query: name)).nil?
137
+ count = 0
138
+ rows = match[:value].split(separator).map{|partition| [count+=1, partition]}
139
+ make_table(title: "Values in #{name}", rows: rows, headings: ['index', 'value'])
140
+ end
141
+
142
+ private
143
+
144
+ ##
145
+ # Returns a terminal table with a specified title and contents.
146
+ #
147
+ # ==== Attributes
148
+ #
149
+ # * +title+ - A string to be printed out as the title.
150
+ # * +rows+ - An array of sub arrays +[[name, value],[name, value],...]+
151
+ # * +headings+ - An array of strings for heading titles +['Name', 'Value']+
152
+ #
153
+ # ==== Examples
154
+ #
155
+ # # Create or update an environment variable called MY_VAR.
156
+ # Exports.inspect("PATH")
157
+ # # +--------+-----------------------------------------------------------+
158
+ # # | [title] |
159
+ # # +--------+-----------------------------------------------------------+
160
+ # # | Name | Value |
161
+ # # +--------+-----------------------------------------------------------+
162
+ # # | PATH | '/Users/name/.rvm/gems/ruby-2.1.2/bin:/Users/name/.rvm... |
163
+ # # | FOO | 3000 |
164
+ # # | BAR | 'some other value' |
165
+ # # +--------+-----------------------------------------------------------+
166
+ def make_table(title: '', rows: [], headings: ['Name', 'Value'])
167
+ return nil if rows.length < 1
168
+ table = Terminal::Table.new(title: title, headings: headings) do |t|
169
+ for row in rows
170
+ if row[1].length > 100
171
+ t.add_row [row[0], row[1][0...100]+"..."]
172
+ else
173
+ t.add_row row
174
+ end
175
+ end
176
+ end
177
+ return table
178
+ end
179
+
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,35 @@
1
+ module Confinicky
2
+ module Controllers
3
+
4
+ ##
5
+ # A subclass of the command group controller specifically
6
+ # for managing export statements.
7
+ class Exports < Commands
8
+
9
+ def initialize
10
+ super(file_type_key: :env)
11
+ @commands = @shell_file.exports
12
+ @table_title = "Environment Variables"
13
+ end
14
+
15
+ ##
16
+ # Updates the actual shell file on disk.
17
+ def save!
18
+ @shell_file.exports = @commands
19
+ @shell_file.write!
20
+ end
21
+
22
+ ##
23
+ # Finds duplicate export statements and replaces them with the actual
24
+ # value from the environment.
25
+ def clean!
26
+ for duplicate in duplicates.map{|duplicate| duplicate[0]}
27
+ @commands.delete_if{ |i| i[0] == duplicate}
28
+ @commands << [duplicate, ENV[duplicate]]
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,98 @@
1
+ module Confinicky
2
+
3
+ module Parsers
4
+
5
+ ##
6
+ # A simple class that parses a line of shell code to
7
+ # into a classified and attributed string.
8
+ class Command
9
+
10
+ EXPORT_COMMAND = 'export'
11
+ ALIAS_COMMAND = 'alias'
12
+
13
+ ##
14
+ # The name of the command.
15
+ attr_reader :name
16
+
17
+ ##
18
+ # The assigned value of the command.
19
+ attr_reader :value
20
+
21
+ ##
22
+ # Takes a line of code as a parameter and performs
23
+ # classification.
24
+ def initialize(line: nil)
25
+ @line = line
26
+ @current_command_type = EXPORT_COMMAND if export?
27
+ @current_command_type = ALIAS_COMMAND if alias?
28
+ process_attributes! unless line?
29
+ end
30
+
31
+ ##
32
+ # The command should be regarded as a general line of code that
33
+ # is of no interest if it doesn't match one of the supported
34
+ # command types.
35
+ def line?
36
+ !export? && !alias?
37
+ end
38
+
39
+ ##
40
+ # Returns true if the line of code matches an export command.
41
+ def export?
42
+ matches_command_type?(EXPORT_COMMAND) && has_expression?
43
+ end
44
+
45
+ ##
46
+ # Returns true if the line of code matches an alias command.
47
+ def alias?
48
+ matches_command_type?(ALIAS_COMMAND) && has_expression?
49
+ end
50
+
51
+ ##
52
+ # Returns an array containing the name / value pair for the command.
53
+ def values_array
54
+ [@name, @expression.value]
55
+ end
56
+
57
+ def open?
58
+ return false if line? || !has_expression?
59
+ @expression.open?
60
+ end
61
+
62
+ def closed?
63
+ !@expression.open?
64
+ end
65
+
66
+ def append(value)
67
+ @expression.append_value value
68
+ end
69
+
70
+ private
71
+
72
+ ##
73
+ # Returns true if the command matches the specified type.
74
+ def matches_command_type?(command_type)
75
+ raise "No command type was specified." if command_type.nil?
76
+ return !(@line =~ /\A#{command_type} /).nil?
77
+ end
78
+
79
+ ##
80
+ # Returns true if the command actually has an expression.
81
+ def has_expression?
82
+ return Confinicky::Parsers::Expression.new(statement: @line).valid?
83
+ end
84
+
85
+ ##
86
+ # Parses the commands name and assigned values into attributes.
87
+ def process_attributes!
88
+ if !@current_command_type.nil?
89
+ @expression = Confinicky::Parsers::Expression.new(statement: @line.gsub(/\A#{@current_command_type} /,""))
90
+ @name = @expression.name
91
+ @value = @expression.value
92
+ end
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,93 @@
1
+ module Confinicky
2
+
3
+ module Parsers
4
+
5
+ ##
6
+ # A simple class that parses a basic assignment expression
7
+ class Expression
8
+
9
+ SINGLE_QUOTE_MATCHER = /'([^']+)'/
10
+ DOUBLE_QUOTE_MATCHER = /"([^"]+)"/
11
+
12
+ ##
13
+ # The name of the variable being assigned. (LHS)
14
+ attr_reader :name
15
+
16
+ ##
17
+ # Takes a line of code as a parameter and performs
18
+ # classification.
19
+ def initialize(statement: nil)
20
+ @statement = statement.split("=")
21
+ if valid?
22
+ @name = @statement[0]
23
+ @value = @statement[1]
24
+ @value.gsub!("\n","") unless open?
25
+ end
26
+ end
27
+
28
+ ##
29
+ # Ensures that the expression has an assigned value.
30
+ def valid?
31
+ @statement.length == 2 && !@statement[1].nil?
32
+ end
33
+
34
+ ##
35
+ # Determines whether or not both types of quotes are present.
36
+ def uses_both_quotes?
37
+ !(@value =~ /'/).nil? && !(@value =~ /"/).nil?
38
+ end
39
+
40
+ ##
41
+ # Determines if the expression is wrapped in double quotes.
42
+ def uses_double_quotes?
43
+ uses_both_quotes? && (@value =~ /'/) > (@value =~ /"/) || (@value =~ /"/) && (@value =~ /'/).nil?
44
+ end
45
+
46
+ ##
47
+ # Determines if the expression is wrapped in single quotes.
48
+ def uses_single_quotes?
49
+ !uses_double_quotes? && !(@value =~ /'/).nil?
50
+ end
51
+
52
+ ##
53
+ # Appends additional string content to the value stored in the
54
+ # parser.
55
+ def append_value(value)
56
+ @value += value
57
+ end
58
+
59
+ ##
60
+ # Determines if our statement is open ended. Meaning no closing quote
61
+ # for a provided opening quote.
62
+ def open?
63
+ !@value.nil? && (uses_double_quotes? || uses_single_quotes?) && extracted_value.nil?
64
+ end
65
+
66
+ ##
67
+ # The value which is being assigned to the variable in the expression (RHS)
68
+ def value
69
+ quote = uses_double_quotes? ? "\"" : "\'"
70
+ value = extracted_value
71
+ value = "#{quote}#{value}#{quote}" if !value.nil? && value =~ /\s/ && value != "\n"
72
+ value
73
+ end
74
+
75
+ protected
76
+
77
+ ##
78
+ # Extracts the value from quotes if necessary. If the expression is
79
+ # has an opening quote without a corresponding closing quote the
80
+ # method will return nil.
81
+ def extracted_value
82
+ if uses_single_quotes? || uses_double_quotes?
83
+ matcher = uses_double_quotes? ? DOUBLE_QUOTE_MATCHER : SINGLE_QUOTE_MATCHER
84
+ matches = @value.scan(matcher)
85
+ return nil if matches.length < 1
86
+ return matches[0][0]
87
+ end
88
+ @value
89
+ end
90
+
91
+ end
92
+ end
93
+ end
@@ -1,77 +1,54 @@
1
1
  require 'fileutils'
2
2
 
3
3
  module Confinicky
4
+
4
5
  ##
5
6
  # A model that loads and represents a shell file.
6
7
  class ShellFile
7
8
 
8
9
  ##
9
- # The preserved lines of code from the shell file which confinicky
10
- # will write back to the new shell file in the order they were received.
11
- attr_reader :lines
10
+ # The alias commands stored in the shell file.
11
+ attr_accessor :aliases
12
12
 
13
- attr_reader :exports
14
13
  ##
15
- # References the actual file path from the shell configuration.
16
- def self.file_path
17
- ENV[Confinicky::FILE_PATH_VAR]
18
- end
14
+ # A list of all exports in the shell file.
15
+ attr_accessor :exports
19
16
 
20
17
  ##
21
- # Returns true if the file path has been configured in the environment.
22
- def self.has_path?
23
- self.file_path.nil?
24
- end
25
-
26
- ##
27
- # Returns true if the file actually exists.
28
- def self.exists?
29
- File.exists? ENV[Confinicky::FILE_PATH_VAR]
30
- end
18
+ # The preserved lines of code from the shell file which confinicky
19
+ # will write back to the new shell file in the order they were received.
20
+ attr_reader :lines
31
21
 
32
22
  ##
33
- # Copies the current shell file to a temporary location.
34
- def self.backup!
35
- backup_name = Confinicky::ShellFile.file_path+Time.now.getutc.to_i.to_s+".bak.tmp"
36
- FileUtils.cp(Confinicky::ShellFile.file_path, backup_name)
37
- backup_name
38
- end
23
+ # Returns the file path for the current instance of the shell file class.
24
+ attr_reader :file_path
39
25
 
40
26
  ##
41
27
  # Parses the configuration file if it exists.
42
- def initialize(file_path: Confinicky::ShellFile.file_path)
28
+ def initialize(file_path: "")
43
29
  raise "Config file not found. Please set" if !File.exists?(@file_path = file_path)
44
30
  @exports = []
31
+ @aliases = []
45
32
  @lines = []
46
33
 
47
34
  file = File.new(@file_path, "r")
35
+ command = nil
48
36
 
49
37
  while (line = file.gets)
50
- if line =~ /\Aexport /
51
- export = line.gsub(/\Aexport /,"").split("=")
52
- if export[1].nil?
53
- @lines << line
54
- else
55
- @exports << [export[0], export[1].gsub(/\n/, "")]
56
- end
38
+ if !command.nil? && command.open?
39
+ command.append line
57
40
  else
58
- @lines << line
41
+ command = Confinicky::Parsers::Command.new(line: line)
59
42
  end
43
+
44
+ @lines << line if command.line?
45
+ @exports << command.values_array if command.export? and command.closed?
46
+ @aliases << command.values_array if command.alias? and command.closed?
60
47
  end
61
48
 
62
49
  file.close()
63
50
  end
64
51
 
65
- ##
66
- # Detects duplicate definitions.
67
- def find_duplicates
68
- duplicates = {}
69
- @exports.each do |export|
70
- duplicates[export[0]] = (duplicates[export[0]].nil?) ? 1 : duplicates[export[0]]+1
71
- end
72
- duplicates.delete_if { |key,value| value==1}.sort_by{|key,value| value}.reverse
73
- end
74
-
75
52
  ##
76
53
  # Returns a list of all exports in alphanumeric order.
77
54
  def exports
@@ -79,31 +56,11 @@ module Confinicky
79
56
  end
80
57
 
81
58
  ##
82
- # Finds duplicate export statements and replaces them with the actual
83
- # value from the environment.
84
- def clean!
85
- for duplicate in find_duplicates.map{|duplicate| duplicate[0]}
86
- @exports.delete_if{ |i| i[0] == duplicate}
87
- @exports << [duplicate, ENV[duplicate]]
88
- end
89
- write!
90
- end
91
-
92
- ##
93
- # Parses an assignment such as "MY_VAR=1234" and injects it into
94
- # the exports or updates an existing variable if possible.
95
- def set!(assignment)
96
- assignment = assignment.split("=")
97
- return false if assignment.length < 2
98
- remove! assignment[0]
99
- assignment[1] = "\'#{assignment[1]}\'" if assignment[1] =~ /\s/
100
- @exports << assignment
101
- end
102
-
103
- ##
104
- # Removes an environment variable if it exists.
105
- def remove!(variable_name)
106
- @exports.delete_if { |i| i[0] == variable_name }
59
+ # Copies the current shell file to a temporary location.
60
+ def backup!
61
+ backup_name = @file_path+Time.now.getutc.to_i.to_s+".bak.tmp"
62
+ FileUtils.cp(@file_path, backup_name)
63
+ backup_name
107
64
  end
108
65
 
109
66
  ##
@@ -113,14 +70,10 @@ module Confinicky
113
70
  for line in @lines
114
71
  f.write line
115
72
  end
116
- f.puts @exports.map{|e| "export #{e.join("=")}"}.join("\n")
73
+ f.puts @exports.map{|e| "export #{e.join("=")}"}.join("\n") if !@exports.nil? and @exports.length > 0
74
+ f.puts @aliases.map{|a| "alias #{a.join("=")}"}.join("\n") if !@aliases.nil? and @aliases.length > 0
117
75
  end
118
- end
119
-
120
- ##
121
- # Returns the file path for the current instance of the shell file class.
122
- def file_path
123
- @file_path
76
+ true
124
77
  end
125
78
 
126
79
  end
@@ -4,8 +4,8 @@ module Confinicky
4
4
  # users of the gem can query it at run time.
5
5
  module Version
6
6
  MAJOR = 0
7
- MINOR = 1
8
- PATCH = 6
7
+ MINOR = 2
8
+ PATCH = 0
9
9
  BUILD = ''
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].compact.join('.')
data/lib/confinicky.rb CHANGED
@@ -1,3 +1,9 @@
1
1
  require 'confinicky/version'
2
2
  require 'confinicky/config'
3
- require 'confinicky/shell_file'
3
+ require 'confinicky/configuration_file'
4
+ require 'confinicky/shell_file'
5
+ require 'confinicky/parsers/command'
6
+ require 'confinicky/parsers/expression'
7
+ require 'confinicky/controllers/commands'
8
+ require 'confinicky/controllers/exports'
9
+ require 'confinicky/controllers/aliases'
@@ -0,0 +1,44 @@
1
+ require 'helper'
2
+
3
+ class TestCommandParser < MiniTest::Test
4
+
5
+ def setup
6
+ @line = 'HOST="http://www.example.com"'
7
+ @alias = 'alias ..=\'cd ../\''
8
+ @export = 'export TEST=1234'
9
+ @no_expression = 'export DISPLAY'
10
+ end
11
+
12
+ def test_should_recognize_a_general_line
13
+ assert_equal true, Confinicky::Parsers::Command.new(line: @line).line?
14
+ end
15
+
16
+ def test_should_recognize_an_export
17
+ assert_equal true, Confinicky::Parsers::Command.new(line: @export).export?
18
+ end
19
+
20
+ def test_should_recognize_an_alias
21
+ assert_equal true, Confinicky::Parsers::Command.new(line: @alias).alias?
22
+ end
23
+
24
+ def test_should_not_report_a_line_as_an_export
25
+ assert_equal false, Confinicky::Parsers::Command.new(line: @line).export?
26
+ end
27
+
28
+ def test_should_not_report_a_line_as_an_alias
29
+ assert_equal false, Confinicky::Parsers::Command.new(line: @line).alias?
30
+ end
31
+
32
+ def test_should_not_report_an_export_as_a_line
33
+ assert_equal false, Confinicky::Parsers::Command.new(line: @export).line?
34
+ end
35
+
36
+ def test_should_not_report_an_alias_as_a_line
37
+ assert_equal false, Confinicky::Parsers::Command.new(line: @alias).line?
38
+ end
39
+
40
+ def test_should_recognize_any_line_without_an_expression_as_a_line
41
+ assert_equal true, Confinicky::Parsers::Command.new(line: @no_expression).line?
42
+ end
43
+
44
+ end
@@ -0,0 +1,64 @@
1
+ require 'fileutils'
2
+ require 'helper'
3
+
4
+ class TestCommandGroups < MiniTest::Test
5
+
6
+ def setup
7
+ Confinicky::ConfigurationFile.force_config!({
8
+ files: {
9
+ aliases: "test/sample_bash_file.sh",
10
+ env: "test/sample_bash_file.sh"
11
+ }
12
+ })
13
+ @aliases = Confinicky::Controllers::Aliases.new
14
+ @exports = Confinicky::Controllers::Exports.new
15
+ @less='\'-i -N -w -z-4 -g -e -M -X -F -R -P%t?f%f \
16
+ :stdin .?pb%pb\%:?lbLine %lb:?bbByte %bb:-...\''
17
+ end
18
+
19
+ def test_clean_file
20
+ @exports.clean!
21
+ assert_equal 17, @exports.length
22
+ end
23
+
24
+ def test_shell_file_duplicates
25
+ assert_equal 1, @exports.duplicates.length
26
+ end
27
+
28
+ def test_add_var
29
+ @exports.set!("NEW_VAR=12345")
30
+ assert_equal 19, @exports.length
31
+ end
32
+
33
+ def test_add_alias
34
+ @aliases.set!("home=cd ~")
35
+ assert_equal 33, @aliases.length
36
+ end
37
+
38
+ def test_remove_var
39
+ @exports.remove!("PATH")
40
+ assert_equal 16, @exports.length
41
+ end
42
+
43
+ def test_remove_alias
44
+ @aliases.remove!("debug")
45
+ assert_equal 31, @aliases.length
46
+ end
47
+
48
+ def test_add_var_should_not_duplicate
49
+ @exports.set!("NEW_VAR=12345")
50
+ @exports.set!("NEW_VAR=123456")
51
+ assert_equal 19, @exports.length
52
+ end
53
+
54
+ def test_whitespace
55
+ @exports.set!("NEW_STRING=A String")
56
+ assert_equal "\'A String\'", @exports.find(query: "NEW_STRING")[:value]
57
+ end
58
+
59
+ def test_multiline_statements
60
+ assert_equal false, @exports.find(query: "LESS")[:value].nil?
61
+ assert_equal @less, @exports.find(query: "LESS")[:value]
62
+ end
63
+
64
+ end