geordi 1.8.0 → 1.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c07036b9657ebe6dfccf09dbc5c74e9b449b1eed88fc04ca5e880284dcbdc1e7
4
- data.tar.gz: 1bf48b1318b06182ccfe8bc0e294c7344195ed654e28c983f6c44bdea0bde7cf
3
+ metadata.gz: 70f9242df556d6b8259f341827cc8996f6970de1e9d51c5ff13cbc44307a7ee7
4
+ data.tar.gz: 59e54eca6438f61af0c7640f88771e5f7f67008237445a0d25521b3adea4f0fd
5
5
  SHA512:
6
- metadata.gz: ae4590a4eb870deec8055b6e4cea3e433b85dc202b94be155fa78d2b8ce8f05b9126c73c2151b04ccf1d09fd5cedf2e80b2f49b80b3efabba61fe551cf5e92e2
7
- data.tar.gz: 2829290660f89550294c2f83423bfa5608533f6f952b951def2ccf39fec167a5146dfe9d84b94eb40f6707085d7a1ba27cc81b0014892d86092e1c9eb05f8602
6
+ metadata.gz: d143f1c68d472cc9918d6de6d2271b6633d0f9f01182d835b9667d872d65c495cc85b6c2752545e670b4bc685367baeb7122a32b7e4d3265ea8cc6412629be5d
7
+ data.tar.gz: ec93583e23658f4c1c69febc903f8d3d05c98b6e0e7ec7066c4c678277161fcc9d4e8f365e79e38792ee0c7a5b8bb9deec07408983c50c694a3884be254d2124
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- geordi (1.7.1)
4
+ geordi (1.9.0)
5
5
  thor (>= 0.18.0)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -75,6 +75,18 @@ to use an `=` instead of a space to separate parameter name and value,
75
75
  e.g. `--format=pretty`.
76
76
 
77
77
 
78
+ ### geordi delete_dumps [DIRECTORY]
79
+
80
+ Delete database dump files (`*.dump`).
81
+
82
+ Example: `geordi delete_dumps` or `geordy delete_dumps ~/tmp/dumps`
83
+
84
+ Recursively search for files ending in `*.dump` and offer to delete those. When
85
+ no argument is given, two default directories are searched for dump files: the
86
+ current working directory and `~/dumps` (for dumps created with geordi).
87
+
88
+ Geordi will ask for confirmation before actually deleting files.
89
+
78
90
  ### `geordi deploy [STAGE]`
79
91
 
80
92
  Guided deployment across branches.
@@ -105,6 +117,26 @@ instead of `cap deploy:migrations`. You can force using `deploy` by passing the
105
117
  -M option: `geordi deploy -M staging`.
106
118
 
107
119
 
120
+ ### `geordi drop_databases`
121
+
122
+ Delete local MySQL/MariaDB and Postgres databases that are not whitelisted.
123
+
124
+ Example: `geordi drop_databases`
125
+
126
+ Check both MySQL/MariaDB and Postgres on the machine running geordi for databases
127
+ and offer to delete them. Excluded are databases that are whitelisted. This comes
128
+ in handy when you're keeping your currently active projects in the whitelist files
129
+ and perform regular housekeeping with Geordi.
130
+
131
+ When called with `-P` or `-M` options, only handles Postgres resp. MySQL/MariaDB.
132
+
133
+ When called with `--postgres <port or local socket>` or `--mysql <port or local socket>`,
134
+ will instruct the underlying management commands to use those connection methods
135
+ instead of the defaults. This is useful when running multiple installations.
136
+
137
+ Geordi will ask for confirmation before actually dropping databases and will
138
+ offer to edit the whitelist instead.
139
+
108
140
  ### `geordi dump [TARGET]`
109
141
 
110
142
  Handle dumps (see `geordi help dump` for details).
@@ -97,7 +97,7 @@ Feature: The cucumber command
97
97
 
98
98
  When I run `geordi cucumber --verbose features`
99
99
  Then the output should contain "# Running features"
100
- And the output should match /^> .*cucumber .*--tags ~@solo/
100
+ And the output should match /^> .*cucumber .*--tags \"~@solo\"/
101
101
  And the output should contain "# Running @solo features"
102
102
  And the output should match /^> .*cucumber .*--tags @solo/
103
103
 
@@ -122,7 +122,7 @@ Feature: The cucumber command
122
122
 
123
123
  When I run `geordi cucumber features/no_solo --verbose`
124
124
  Then the output should contain "# Running features"
125
- And the output should match /^> .*cucumber .*--tags ~@solo/
125
+ And the output should match /^> .*cucumber .*--tags \"~@solo\"/
126
126
  But the output should not contain "# Running @solo features"
127
127
 
128
128
 
@@ -0,0 +1,60 @@
1
+ desc 'drop-databases', 'Delete local non-whitelisted databases'
2
+ long_desc <<-LONGDESC
3
+
4
+ Delete local MySQL/MariaDB and Postgres databases that are not whitelisted.
5
+
6
+ Example: `geordi drop_databases`
7
+
8
+ Check both MySQL/MariaDB and Postgres on the machine running geordi for databases
9
+ and offer to delete them. Excluded are databases that are whitelisted. This comes
10
+ in handy when you're keeping your currently active projects in the whitelist files
11
+ and perform regular housekeeping with Geordi.
12
+
13
+ When called with `-P` or `-M` options, only handles Postgres resp. MySQL/MariaDB.
14
+
15
+ When called with `--postgres <port or local socket>` or `--mysql <port or local socket>`,
16
+ will instruct the underlying management commands to use those connection methods
17
+ instead of the defaults. This is useful when running multiple installations.
18
+ LONGDESC
19
+
20
+ option :postgres_only, :aliases => '-P', :type => :boolean,
21
+ :desc => 'Only clean Postgres', :default => false
22
+ option :mysql_only, :aliases => '-M', :type => :boolean,
23
+ :desc => 'Only clean MySQL/MariaDB', :default => false
24
+ option :postgres, :banner => 'PORT_OR_SOCKET',
25
+ :desc => 'Use Postgres port or socket'
26
+ option :mysql, :banner => 'PORT_OR_SOCKET',
27
+ :desc => 'Use MySQL/MariaDB port or socket'
28
+
29
+ def drop_databases
30
+ require 'geordi/db_cleaner'
31
+ fail '-P and -M are mutually exclusive' if options.postgres_only and options.mysql_only
32
+ mysql_flags = nil
33
+ postgres_flags = nil
34
+
35
+ unless options.mysql.nil?
36
+ begin
37
+ mysql_port = Integer(options.mysql)
38
+ mysql_flags = "--port=#{mysql_port} --protocol=TCP"
39
+ rescue AttributeError
40
+ unless File.exist? options.mysql
41
+ fail "Path #{options.mysql} is not a valid MySQL socket"
42
+ end
43
+ mysql_flags = "--socket=#{options.mysql}"
44
+ end
45
+ end
46
+
47
+ unless options.postgres.nil?
48
+ postgres_flags = "--port=#{options.postgres}"
49
+ end
50
+
51
+ extra_flags = {'mysql' => mysql_flags,
52
+ 'postgres' => postgres_flags
53
+ }
54
+ cleaner = DBCleaner.new(extra_flags)
55
+ cleaner.clean_mysql unless options.postgres_only
56
+ cleaner.clean_postgres unless options.mysql_only
57
+
58
+ success 'Done.'
59
+ end
60
+
@@ -0,0 +1,46 @@
1
+ desc 'delete_dumps [DIRECTORY]', 'delete database dump files (*.dump)'
2
+ long_desc <<-LONGDESC
3
+ Delete database dump files (`*.dump`).
4
+
5
+ Example: `geordi delete_dumps` or `geordy delete_dumps ~/tmp/dumps`
6
+
7
+ Recursively search for files ending in `*.dump` and offer to delete those. When
8
+ no argument is given, two default directories are searched for dump files: the
9
+ current working directory and `~/dumps` (for dumps created with geordi).
10
+
11
+ Geordi will ask for confirmation before actually deleting files.
12
+
13
+ LONGDESC
14
+
15
+ def delete_dumps(dump_directory = nil)
16
+ deletable_dumps = []
17
+ if dump_directory.nil?
18
+ dump_directories = [
19
+ File.join(Dir.home, 'dumps'),
20
+ Dir.pwd
21
+ ]
22
+ else
23
+ dump_directories = [dump_directory]
24
+ end
25
+ announce 'Looking for *.dump in ' << dump_directories.join(',')
26
+ dump_directories.each do |d|
27
+ d2 = File.expand_path(d)
28
+ unless File.directory? File.realdirpath(d2)
29
+ warn "Directory #{d2} does not exist"
30
+ next
31
+ end
32
+ deletable_dumps.concat(Dir.glob("#{d2}/**/*.dump"))
33
+ end
34
+ if deletable_dumps.empty?
35
+ success 'No dumps to delete' if deletable_dumps.empty?
36
+ exit 0
37
+ end
38
+ deletable_dumps.uniq!
39
+ note 'The following dumps can be deleted:'
40
+ puts
41
+ puts deletable_dumps
42
+ prompt 'Delete those dumps', 'n', /y|yes/ or fail 'Cancelled.'
43
+ deletable_dumps.each do |dump|
44
+ File.delete dump unless File.directory? dump
45
+ end
46
+ end
@@ -0,0 +1,239 @@
1
+ require 'fileutils'
2
+ require 'open3'
3
+ require 'tempfile'
4
+
5
+ module Geordi
6
+ class DBCleaner
7
+ include Geordi::Interaction
8
+
9
+ def initialize(extra_flags)
10
+ puts 'Please enter your sudo password if asked, for db operations as system users'
11
+ puts "We're going to run `sudo -u postgres psql` for PostgreSQL"
12
+ puts ' and `sudo mysql` for MariaDB (which uses PAM auth)'
13
+ `sudo true`
14
+ fail 'sudo access is required for database operations as database users' if $? != 0
15
+ @derivative_dbname = /_(test\d?|development|cucumber)$/
16
+ base_directory = ENV['XDG_CONFIG_HOME']
17
+ base_directory = "#{Dir.home}" if base_directory.nil?
18
+ @whitelist_directory = File.join(base_directory, '.config', 'geordi', 'whitelists')
19
+ FileUtils.mkdir_p(@whitelist_directory) unless File.directory? @whitelist_directory
20
+ @mysql_command = decide_mysql_command(extra_flags['mysql'])
21
+ @postgres_command = decide_postgres_command(extra_flags['postgres'])
22
+ end
23
+
24
+ def edit_whitelist(dbtype)
25
+ whitelist = whitelist_fname(dbtype)
26
+ if File.exist? whitelist
27
+ whitelisted_dbs = Geordi::Util.stripped_lines(File.read(whitelist))\
28
+ .delete_if { |l| l.start_with? '#' }
29
+ else
30
+ whitelisted_dbs = Array.new
31
+ end
32
+ all_dbs = list_all_dbs(dbtype)
33
+ tmp = Tempfile.open("geordi_whitelist_#{dbtype}")
34
+ tmp.write <<-HEREDOC
35
+ # Put each whitelisted database on a new line.
36
+ # System databases will never be deleted.
37
+ # When you whitelist foo, foo_development and foo_test\\d? are whitelisted, too.
38
+ # This works even if foo does not exist. Also, you will only see foo in this list.
39
+ #
40
+ # Syntax: keep foo
41
+ # drop bar
42
+ HEREDOC
43
+ tmpfile_content = Array.new
44
+ all_dbs.each do |db|
45
+ next if is_whitelisted?(dbtype, db)
46
+ next if is_protected?(dbtype, db)
47
+ db.sub!(@derivative_dbname, '')
48
+ tmpfile_content.push(['drop', db])
49
+ end
50
+ whitelisted_dbs.each do |db|
51
+ tmpfile_content.push(['keep', db])
52
+ end
53
+ tmpfile_content.sort_by! { |k| k[1] }
54
+ tmpfile_content.uniq!
55
+ tmpfile_content.each do |line|
56
+ tmp.write("#{line[0]} #{line[1]}\n")
57
+ end
58
+ tmp.close
59
+ texteditor = Geordi::Util.decide_texteditor
60
+ system("#{texteditor} #{tmp.path}")
61
+ File.open(tmp.path, 'r') do |wl_edited|
62
+ whitelisted_dbs = Array.new
63
+ whitelist_storage = File.open(whitelist, 'w')
64
+ lines = Geordi::Util.stripped_lines(wl_edited.read)
65
+ lines.each do |line|
66
+ next if line.start_with?('#')
67
+ fail 'Invalid edit to whitelist file' unless line.split.length == 2
68
+ fail 'Invalid edit to whitelist file' unless %w[keep drop k d].include? line.split[0]
69
+ db_status, db_name = line.split
70
+ if db_status == 'keep'
71
+ whitelisted_dbs.push db_name
72
+ whitelist_storage.write(db_name << "\n")
73
+ end
74
+ end
75
+ whitelist_storage.close
76
+ end
77
+ end
78
+
79
+ def decide_mysql_command(extra_flags)
80
+ cmd = 'sudo mysql'
81
+ unless extra_flags.nil?
82
+ if extra_flags.include? 'port'
83
+ port = Integer(extra_flags.split('=')[1].split()[0])
84
+ fail "Port #{port} is not open" unless Geordi::Util.is_port_open? port
85
+ end
86
+ cmd << " #{extra_flags}"
87
+ end
88
+ Open3.popen3("#{cmd} -e 'QUIT'") do |stdin, stdout, stderr, thread|
89
+ break if thread.value.exitstatus == 0
90
+ # sudo mysql was not successful, switching to mysql-internal user management
91
+ mysql_error = stderr.read.lines[0].chomp.strip.split[1]
92
+ if %w[1045 1698].include? mysql_error # authentication failed
93
+ cmd = 'mysql -uroot'
94
+ cmd << " #{extra_flags}" unless extra_flags.nil?
95
+ unless File.exist? File.join(Dir.home, '.my.cnf')
96
+ puts "Please enter your MySQL/MariaDB password for account 'root'."
97
+ warn "You should create a ~/.my.cnf file instead, or you'll need to enter your MySQL root password for each db."
98
+ warn "See https://makandracards.com/makandra/50813-store-mysql-passwords-for-development for more information."
99
+ cmd << ' -p' # need to ask for password now
100
+ end
101
+ Open3.popen3("#{cmd} -e 'QUIT'") do |stdin2, stdout2, stderr2, thread2|
102
+ fail 'Could not connect to MySQL/MariaDB' unless thread2.value.exitstatus == 0
103
+ end
104
+ elsif mysql_error == '2013' # connection to port or socket failed
105
+ fail 'MySQL/MariaDB connection failed, is this the correct port?'
106
+ end
107
+ end
108
+ return cmd
109
+ end
110
+ private :decide_mysql_command
111
+
112
+ def decide_postgres_command(extra_flags)
113
+ cmd = 'sudo -u postgres psql'
114
+ unless extra_flags.nil?
115
+ begin
116
+ port = Integer(extra_flags.split('=')[1])
117
+ fail "Port #{port} is not open" unless Geordi::Util.is_port_open? port
118
+ rescue ArgumentError
119
+ socket = extra_flags.split('=')[1]
120
+ fail "Socket #{socket} does not exist" unless File.exist? socket
121
+ end
122
+ cmd << " #{extra_flags}"
123
+ end
124
+ return cmd
125
+ end
126
+ private :decide_postgres_command
127
+
128
+ def list_all_dbs(dbtype)
129
+ if dbtype == 'postgres'
130
+ return list_all_postgres_dbs
131
+ else
132
+ return list_all_mysql_dbs
133
+ end
134
+ end
135
+
136
+ def list_all_postgres_dbs
137
+ `#{@postgres_command} -t -A -c 'SELECT DATNAME FROM pg_database WHERE datistemplate = false'`.split
138
+ end
139
+
140
+ def list_all_mysql_dbs
141
+ if @mysql_command.include? '-p'
142
+ puts "Please enter your MySQL/MariaDB account 'root' for: list all databases"
143
+ end
144
+ `#{@mysql_command} -B -N -e 'show databases'`.split
145
+ end
146
+
147
+ def clean_mysql
148
+ announce 'Checking for MySQL databases'
149
+ database_list = list_all_dbs('mysql')
150
+ # confirm_deletion includes option for whitelist editing
151
+ deletable_dbs = confirm_deletion('mysql', database_list)
152
+ return if deletable_dbs.nil?
153
+ deletable_dbs.each do |db|
154
+ if @mysql_command.include? '-p'
155
+ puts "Please enter your MySQL/MariaDB account 'root' for: DROP DATABASE #{db}"
156
+ else
157
+ note "Dropping MySQL/MariaDB database #{db}"
158
+ end
159
+ `#{@mysql_command} -e 'DROP DATABASE \`#{db}\`;'`
160
+ end
161
+ end
162
+
163
+ def clean_postgres
164
+ announce 'Checking for Postgres databases'
165
+ database_list = list_all_dbs('postgres')
166
+ deletable_dbs = confirm_deletion('postgres', database_list)
167
+ return if deletable_dbs.nil?
168
+ deletable_dbs.each do |db|
169
+ note "Dropping PostgreSQL database `#{db}`."
170
+ `#{@postgres_command} -c 'DROP DATABASE "#{db}";'`
171
+ end
172
+ end
173
+
174
+ def whitelist_fname(dbtype)
175
+ File.join(@whitelist_directory, dbtype) << '.txt'
176
+ end
177
+
178
+ def confirm_deletion(dbtype, database_list)
179
+ proceed = ''
180
+ until %w[y n].include? proceed
181
+ deletable_dbs = filter_whitelisted(dbtype, database_list)
182
+ if deletable_dbs.empty?
183
+ note "No #{dbtype} databases found that were not whitelisted"
184
+ if prompt('Edit the whitelist? [y]es or [n]o') == 'y'
185
+ proceed = 'e'
186
+ else
187
+ return []
188
+ end
189
+ end
190
+ if proceed.empty?
191
+ note "The following #{dbtype} databases are not whitelisted and could be deleted:"
192
+ deletable_dbs.sort.each do |db|
193
+ puts db
194
+ end
195
+ note "Those #{dbtype} databases are not whitelisted and could be deleted."
196
+ proceed = prompt('Proceed? [y]es, [n]o or [e]dit whitelist')
197
+ end
198
+ case proceed
199
+ when 'e'
200
+ proceed = '' # reset user selection
201
+ edit_whitelist dbtype
202
+ when 'n'
203
+ success 'Not deleting anything'
204
+ return []
205
+ when 'y'
206
+ return deletable_dbs
207
+ end
208
+ end
209
+ end
210
+ private :confirm_deletion
211
+
212
+ def is_protected?(dbtype, database_name)
213
+ protected = {
214
+ 'mysql' => %w[mysql information_schema performance_schema sys],
215
+ 'postgres' => ['postgres'],
216
+ }
217
+ protected[dbtype].include? database_name
218
+ end
219
+
220
+ def is_whitelisted?(dbtype, database_name)
221
+ if File.exist? whitelist_fname(dbtype)
222
+ whitelist_content = Geordi::Util.stripped_lines(File.open(whitelist_fname(dbtype), 'r').read)
223
+ else
224
+ whitelist_content = Array.new
225
+ end
226
+ whitelist_content.include? database_name.sub(@derivative_dbname, '')
227
+ end
228
+
229
+ def filter_whitelisted(dbtype, database_list)
230
+ # n.b. `delete` means 'delete from list of dbs that should be deleted in this context
231
+ # i.e. `delete` means 'keep this database'
232
+ deletable_dbs = database_list.dup
233
+ deletable_dbs.delete_if { |db| is_whitelisted?(dbtype, db) if File.exist? whitelist_fname(dbtype) }
234
+ deletable_dbs.delete_if { |db| is_protected?(dbtype, db) }
235
+ deletable_dbs.delete_if { |db| db.start_with? '#' }
236
+ end
237
+ private :filter_whitelisted
238
+ end
239
+ end
@@ -1,31 +1,42 @@
1
+ # Use the methods in this file to communicate with the user
2
+ #
1
3
  module Geordi
2
4
  module Interaction
3
5
 
6
+ # Start your command by `announce`-ing what you're about to do
4
7
  def announce(text)
5
8
  message = "\n# #{text}"
6
9
  puts "\e[4;34m#{message}\e[0m" # blue underline
7
10
  end
8
11
 
12
+ # Any hints, comments, infos or explanations should be `note`d. Please do
13
+ # not print any output (data, file contents, lists) with `note`.
9
14
  def note(text)
10
15
  puts '> ' + text
11
16
  end
12
17
 
18
+ # Like `note`, but yellow. Use to warn the user.
13
19
  def warn(text)
14
20
  message = "> #{text}"
15
21
  puts "\e[33m#{message}\e[0m" # yellow
16
22
  end
17
23
 
24
+ # Like `note`, but pink. Use to print (bash) commands.
25
+ # Also see Util.system!
18
26
  def note_cmd(text)
19
27
  message = "> #{text}"
20
28
  puts "\e[35m#{message}\e[0m" # pink
21
29
  end
22
30
 
31
+ # Exit execution with status code 1 and give a short note what happened,
32
+ # e.g. "Failed" or "Cancelled"
23
33
  def fail(text)
24
34
  message = "\nx #{text}"
25
35
  puts "\e[31m#{message}\e[0m" # red
26
36
  exit(1)
27
37
  end
28
38
 
39
+ # When you're done, inform the user with a `success` and a short message
29
40
  def success(text)
30
41
  message = "\n> #{text}"
31
42
  puts "\e[32m#{message}\e[0m" # green
data/lib/geordi/util.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'geordi/interaction'
2
+ require 'socket'
2
3
 
3
4
  module Geordi
4
5
  class Util
@@ -79,6 +80,37 @@ module Geordi
79
80
  end
80
81
  end
81
82
 
83
+ # try to guess user's favorite cli text editor
84
+ def decide_texteditor
85
+ %w[$VISUAL $EDITOR /usr/bin/editor vi].each do |texteditor|
86
+ if cmd_exists? texteditor and texteditor.start_with? '$'
87
+ return ENV[texteditor[1..-1]]
88
+ elsif cmd_exists? texteditor
89
+ return texteditor
90
+ end
91
+ end
92
+ end
93
+
94
+ # check if given cmd is executable. Absolute path or command in $PATH allowed.
95
+ def cmd_exists? cmd
96
+ system("which #{cmd} > /dev/null")
97
+ return $?.exitstatus.zero?
98
+ end
99
+
100
+ def is_port_open?(port)
101
+ begin
102
+ socket = TCPSocket.new('127.0.0.1', port)
103
+ socket.close
104
+ return true
105
+ rescue Errno::ECONNREFUSED
106
+ return false
107
+ end
108
+ end
109
+
110
+ # splint lines e.g. read from a file into lines and clean those up
111
+ def stripped_lines(input_string)
112
+ input_string.lines.map(&:chomp).map(&:strip)
113
+ end
82
114
  end
83
115
  end
84
116
  end
@@ -1,3 +1,3 @@
1
1
  module Geordi
2
- VERSION = '1.8.0'
2
+ VERSION = '1.9.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: geordi
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henning Koch
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-28 00:00:00.000000000 Z
11
+ date: 2018-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -97,6 +97,8 @@ files:
97
97
  - lib/geordi/commands/create_database_yml.rb
98
98
  - lib/geordi/commands/create_databases.rb
99
99
  - lib/geordi/commands/cucumber.rb
100
+ - lib/geordi/commands/db_clean.rb
101
+ - lib/geordi/commands/delete_dumps.rb
100
102
  - lib/geordi/commands/deploy.rb
101
103
  - lib/geordi/commands/dump.rb
102
104
  - lib/geordi/commands/eurest.rb
@@ -117,6 +119,7 @@ files:
117
119
  - lib/geordi/commands/vnc.rb
118
120
  - lib/geordi/commands/with_rake.rb
119
121
  - lib/geordi/cucumber.rb
122
+ - lib/geordi/db_cleaner.rb
120
123
  - lib/geordi/dump_loader.rb
121
124
  - lib/geordi/firefox_for_selenium.rb
122
125
  - lib/geordi/gitpt.rb