incline 0.1.7 → 0.1.8
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 +4 -4
- data/Gemfile.lock +4 -4
- data/app/controllers/incline/account_activations_controller.rb +2 -2
- data/app/controllers/incline/contact_controller.rb +1 -1
- data/app/controllers/incline/password_resets_controller.rb +2 -2
- data/app/controllers/incline/sessions_controller.rb +2 -2
- data/app/controllers/incline/users_controller.rb +1 -1
- data/exe/incline +12 -0
- data/lib/incline/cli/errors.rb +8 -0
- data/lib/incline/cli/helpers/yaml.rb +743 -0
- data/lib/incline/cli/usage.rb +77 -0
- data/lib/incline/cli/version.rb +23 -0
- data/lib/incline/cli.rb +120 -0
- data/lib/incline/extensions/action_controller_base.rb +1 -1
- data/lib/incline/extensions/test_case.rb +8 -2
- data/lib/incline/version.rb +1 -1
- data/test/cli/yaml_contents_test.rb +88 -0
- data/test/controllers/incline/welcome_controller_test.rb +3 -3
- data/test/dummy/app/controllers/dummy_controller.rb +10 -0
- data/test/dummy/app/views/dummy/hello.html.erb +1 -0
- data/test/dummy/config/routes.rb +3 -1
- data/test/integration/incline/users_signup_test.rb +2 -2
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0254285858902e4313110fddc419672ac8ae267b'
|
4
|
+
data.tar.gz: f22408a384aa6aaf8ee1f36195a220d1677fb09c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8f26912a02f65f50705909925952a0478bed454cc32a3d5eff854a1186d030e72905370f3791fd40dae334c72fffad0d8cf2177e4077e8924cdc9b4028ae61a
|
7
|
+
data.tar.gz: 200de0b8b26cabf2303f94ee7df3d5aad0c3ec7c7a8745871df0825d3c16e6b4039da7115f50b70972310c661ba0b7ba50ee4a69d292b353cd1714301e3f63a9
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
incline (0.1.
|
4
|
+
incline (0.1.8)
|
5
5
|
ansi (~> 1.5.0)
|
6
6
|
bcrypt
|
7
7
|
bootstrap-sass
|
@@ -58,7 +58,7 @@ GEM
|
|
58
58
|
tzinfo (~> 1.1)
|
59
59
|
ansi (1.5.0)
|
60
60
|
arel (6.0.4)
|
61
|
-
autoprefixer-rails (7.1.2.
|
61
|
+
autoprefixer-rails (7.1.2.3)
|
62
62
|
execjs
|
63
63
|
bcrypt (3.1.11)
|
64
64
|
bootstrap-sass (3.3.7)
|
@@ -151,7 +151,7 @@ GEM
|
|
151
151
|
sprockets (>= 2.8, < 4.0)
|
152
152
|
sprockets-rails (>= 2.0, < 4.0)
|
153
153
|
tilt (>= 1.1, < 3)
|
154
|
-
shells (0.1.
|
154
|
+
shells (0.1.10)
|
155
155
|
net-ssh (~> 3.0.2)
|
156
156
|
rubyserial (~> 0.4.0)
|
157
157
|
spawnling (2.1.6)
|
@@ -165,7 +165,7 @@ GEM
|
|
165
165
|
sqlite3 (1.3.13)
|
166
166
|
thor (0.19.4)
|
167
167
|
thread_safe (0.3.6)
|
168
|
-
tilt (2.0.
|
168
|
+
tilt (2.0.8)
|
169
169
|
tiny_tds (1.3.0)
|
170
170
|
mini_portile2 (~> 2.0)
|
171
171
|
tzinfo (1.2.3)
|
@@ -8,7 +8,7 @@ module Incline
|
|
8
8
|
def edit
|
9
9
|
if logged_in?
|
10
10
|
flash[:danger] = 'You cannot reactivate your account.'
|
11
|
-
redirect_to root_url
|
11
|
+
redirect_to main_app.root_url
|
12
12
|
else
|
13
13
|
user = User.find_by(email: params[:email].downcase)
|
14
14
|
if user && !user.activated? && user.authenticated?(:activation, params[:id])
|
@@ -18,7 +18,7 @@ module Incline
|
|
18
18
|
redirect_to user
|
19
19
|
else
|
20
20
|
flash[:danger] = 'Invalid activation link'
|
21
|
-
redirect_to root_url
|
21
|
+
redirect_to main_app.root_url
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
@@ -40,7 +40,7 @@ module Incline
|
|
40
40
|
end
|
41
41
|
|
42
42
|
flash[:info] = 'An email with password reset information has been sent to you.'
|
43
|
-
redirect_to root_url
|
43
|
+
redirect_to main_app.root_url
|
44
44
|
end
|
45
45
|
|
46
46
|
##
|
@@ -98,7 +98,7 @@ module Incline
|
|
98
98
|
|
99
99
|
def valid_user
|
100
100
|
unless @user && @user.enabled? && @user.activated? && @user.authenticated?(:reset, params[:id])
|
101
|
-
redirect_to root_url
|
101
|
+
redirect_to main_app.root_url
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
@@ -29,7 +29,7 @@ module Incline
|
|
29
29
|
redirect_back_or @user
|
30
30
|
else
|
31
31
|
flash[:safe_warning] = 'Your account has not yet been activated.<br/>Check your email for the activation link.'
|
32
|
-
redirect_to root_url
|
32
|
+
redirect_to main_app.root_url
|
33
33
|
end
|
34
34
|
else
|
35
35
|
# deny login.
|
@@ -42,7 +42,7 @@ module Incline
|
|
42
42
|
# DELETE /incline/logout
|
43
43
|
def destroy
|
44
44
|
log_out if logged_in?
|
45
|
-
redirect_to root_url
|
45
|
+
redirect_to main_app.root_url
|
46
46
|
end
|
47
47
|
|
48
48
|
end
|
data/exe/incline
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# If installed as a gem, this won't do anything, however if we are
|
4
|
+
# working from the source, this will ensure our lib path is searched
|
5
|
+
# for require and include calls.
|
6
|
+
git_path = File.expand_path('../../.git', __FILE__)
|
7
|
+
if File.exist?(git_path)
|
8
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'incline/cli'
|
12
|
+
Incline::CLI.new.execute(*ARGV)
|
@@ -0,0 +1,743 @@
|
|
1
|
+
module Incline
|
2
|
+
module CliHelpers
|
3
|
+
|
4
|
+
##
|
5
|
+
# Adds YAML text helper methods.
|
6
|
+
#
|
7
|
+
# Does not parse YAML files, but does allow for updating raw text files to be YAML compliant.
|
8
|
+
module Yaml
|
9
|
+
|
10
|
+
class YamlError < ::Incline::CLI::CliError; end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Helper class to process the YAML file contents easily.
|
14
|
+
class YamlContents
|
15
|
+
|
16
|
+
##
|
17
|
+
# Creates a new YAML contents.
|
18
|
+
def initialize(content)
|
19
|
+
@content = content.to_s.gsub("\r\n", "\n").strip + "\n"
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Returns the YAML contents.
|
24
|
+
def to_s
|
25
|
+
@content
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Adds a key to the YAML contents if it is missing.
|
30
|
+
# Does nothing to the key if it exists.
|
31
|
+
#
|
32
|
+
# add_key [ "default", "name" ], "george"
|
33
|
+
#
|
34
|
+
# The 'key' should be an array defining the path.
|
35
|
+
#
|
36
|
+
# Value can be nil, a string, a symbol, a number, or a boolean.
|
37
|
+
#
|
38
|
+
# The 'make_safe_value' option can be used to provide an explicit text value.
|
39
|
+
# This can be useful if you want to add a specific value, like an ERB command.
|
40
|
+
#
|
41
|
+
# add_key [ "default", "name" ], "<%= ENV[\"DEFAULT_USER\"] %>", false
|
42
|
+
#
|
43
|
+
# You can also use a hash for the value to specify advanced options.
|
44
|
+
# Currently only three advanced options are recognized.
|
45
|
+
#
|
46
|
+
# The first option, :value, simply sets the value. If this is the only
|
47
|
+
# hash key provided, then the value supplied is treated as if it was the original
|
48
|
+
# value. In other words, only setting :value is the same as not using a hash and
|
49
|
+
# just passing in the value, so the value must be nil, a string, a symbol, a number,
|
50
|
+
# or a boolean.
|
51
|
+
#
|
52
|
+
# The second option, :safe, works the opposite of the 'make_safe_value' parameter.
|
53
|
+
# If :safe is a non-false value, then it is like 'make_safe_value' is set to false.
|
54
|
+
# If :safe is a false value, then it is like 'make_safe_value' is set to true.
|
55
|
+
# The :safe value can be set to true and the :value option can set the value, or
|
56
|
+
# the :safe value can be set to the value directly since all strings are non-false.
|
57
|
+
#
|
58
|
+
# The third option, :before_section, tells add_key to insert the section before the
|
59
|
+
# named section (if the new section doesn't exist). This can be useful if the named
|
60
|
+
# section is going to be referencing the key you are adding. Otherwise, when a
|
61
|
+
# section needs to be added, it gets added to the end of the file.
|
62
|
+
#
|
63
|
+
# Returns the contents object.
|
64
|
+
def add_key(key, value, make_safe_value = true)
|
65
|
+
|
66
|
+
# ensure the parent structure exists!
|
67
|
+
if key.count > 1
|
68
|
+
add_key(key[0...-1], nil)
|
69
|
+
else
|
70
|
+
# If the base key already exists, no need to look further.
|
71
|
+
return self if @content =~ /^#{key.first}:/
|
72
|
+
|
73
|
+
unless @content[0] == '#'
|
74
|
+
@content = "# File modified by Incline v#{Incline::VERSION}.\n" + @content
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
val_name = key.last
|
79
|
+
|
80
|
+
# construct a regular expression to find the parent group and value.
|
81
|
+
rex_str = '^('
|
82
|
+
rex_prefix = /\A./
|
83
|
+
key.each_with_index do |attr,level|
|
84
|
+
lev = (level < 1 ? '' : ('\\s\\s' * (level)))
|
85
|
+
if lev != ''
|
86
|
+
rex_str += '(?:' + lev + '[^\\n]*\\n)*'
|
87
|
+
end
|
88
|
+
if level == key.count - 1
|
89
|
+
if level == 0
|
90
|
+
# At level 0 we cheat and use a very simple regular expression to confirm the section exists.
|
91
|
+
rex_str = "^#{attr}:"
|
92
|
+
# If it doesn't exists, the prefix regex will usually want to put the new section at the end of the
|
93
|
+
# file. However if the :before_section option is set, and the other section exists, then we
|
94
|
+
# want to ensure that we are putting the new section before it.
|
95
|
+
#
|
96
|
+
# Down below we take care to reverse the replacement string when key.count == 1.
|
97
|
+
rex_prefix =
|
98
|
+
if value.is_a?(::Hash) && value[:before_section] && @content =~ /^#{value[:before_section]}:/
|
99
|
+
/(^#{value[:before_section]}:)/
|
100
|
+
else
|
101
|
+
/(\z)/ # match the end of the contents.
|
102
|
+
end
|
103
|
+
else
|
104
|
+
rex_str += ')'
|
105
|
+
rex_prefix = Regexp.new(rex_str)
|
106
|
+
rex_str += '(' + lev + attr + ':.*\\n)'
|
107
|
+
end
|
108
|
+
else
|
109
|
+
rex_str += lev + attr + ':.*\\n'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
rex = Regexp.new(rex_str)
|
114
|
+
|
115
|
+
if @content =~ rex
|
116
|
+
# all good.
|
117
|
+
elsif @content =~ rex_prefix
|
118
|
+
if make_safe_value
|
119
|
+
value = safe_value(value)
|
120
|
+
value = add_value_offset(key, value)
|
121
|
+
elsif value.is_a?(::Hash)
|
122
|
+
value = value[:value]
|
123
|
+
end
|
124
|
+
value = '' if value =~ /\A\s*\z/
|
125
|
+
# Should be true thanks to first step in this method.
|
126
|
+
# Capture 1 would be the parent group.
|
127
|
+
# When key.count == 1 then we want to put our new value before capture 1.
|
128
|
+
# Otherwise we put our new value after capture 1.
|
129
|
+
rep = if key.count == 1
|
130
|
+
"\n#{val_name}:#{value}\n\\1"
|
131
|
+
else
|
132
|
+
"\\1#{' ' * (key.count - 1)}#{val_name}:#{value}\n"
|
133
|
+
end
|
134
|
+
|
135
|
+
@content.gsub! rex_prefix, rep
|
136
|
+
else
|
137
|
+
raise ::Incline::CliHelpers::Yaml::YamlError, "Failed to create parent group for '#{key.join('/')}'."
|
138
|
+
end
|
139
|
+
|
140
|
+
self
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
##
|
145
|
+
# Adds a key to the YAML contents if it is missing.
|
146
|
+
# Does nothing to the key if it exists.
|
147
|
+
#
|
148
|
+
# add_key_with_comment [ "default", "name" ], "george", "this is the name of the default user"
|
149
|
+
#
|
150
|
+
# The 'key' should be an array defining the path.
|
151
|
+
# If the 'comment' is blank (nil or ''), then it will not modify the comment.
|
152
|
+
# Use a whitespace string (' ') to indicate that you want a blank comment added.
|
153
|
+
#
|
154
|
+
# Value can be nil, a string, a symbol, a number, or a boolean.
|
155
|
+
# Value can also be a hash according to #add_key.
|
156
|
+
#
|
157
|
+
# Returns the contents object.
|
158
|
+
def add_key_with_comment(key, value, comment)
|
159
|
+
if comment.to_s == ''
|
160
|
+
add_key key, value
|
161
|
+
else
|
162
|
+
add_key key, value_with_comment(key, value, comment), false
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
##
|
167
|
+
# Sets a key in the YAML contents.
|
168
|
+
# Adds the key if it is missing, replaces it if it already exists.
|
169
|
+
#
|
170
|
+
# set_key [ "default", "name" ], "george"
|
171
|
+
#
|
172
|
+
# The 'key' should be an array defining the path.
|
173
|
+
#
|
174
|
+
# Value can be nil, a string, a symbol, a number, or a boolean.
|
175
|
+
# Value can also be a hash according to #add_key.
|
176
|
+
#
|
177
|
+
# The 'make_safe_value' option can be used to provide an explicit text value.
|
178
|
+
# This can be useful if you want to add a specific value, like an ERB command.
|
179
|
+
#
|
180
|
+
# set_key [ "default", "name" ], "<%= ENV[\"DEFAULT_USER\"] %>", false
|
181
|
+
#
|
182
|
+
# Returns the contents object.
|
183
|
+
def set_key(key, value, make_safe_value = true)
|
184
|
+
|
185
|
+
# construct a regular expression to find the value and not confuse it with any other value in the file.
|
186
|
+
rex_str = '^('
|
187
|
+
key.each_with_index do |attr,level|
|
188
|
+
lev = (level < 1 ? '' : ('\\s\\s' * (level)))
|
189
|
+
if lev != ''
|
190
|
+
rex_str += '(?:' + lev + '.*\\n)*'
|
191
|
+
end
|
192
|
+
if level == key.count - 1
|
193
|
+
rex_str += lev + attr + ':)\\s*([^#\\n]*)?(#[^\\n]*)?\\n'
|
194
|
+
else
|
195
|
+
rex_str += lev + attr + ':.*\\n'
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
rex = Regexp.new(rex_str)
|
200
|
+
|
201
|
+
if @content =~ rex
|
202
|
+
if make_safe_value
|
203
|
+
value = safe_value(value)
|
204
|
+
value = add_value_offset(key, value)
|
205
|
+
elsif value.is_a?(::Hash)
|
206
|
+
value = value[:value]
|
207
|
+
end
|
208
|
+
value = '' if value =~ /\A\s*\z/
|
209
|
+
# Capture 1 is everything before the value.
|
210
|
+
# Capture 2 is going to be just the value.
|
211
|
+
# Capture 3 is the comment (if any). This allows us to propagate comments if we change a value.
|
212
|
+
if $2 != value
|
213
|
+
rep = "\\1#{value}\\3\n"
|
214
|
+
@content.gsub! rex, rep
|
215
|
+
end
|
216
|
+
|
217
|
+
self
|
218
|
+
else
|
219
|
+
add_key(key, value, make_safe_value)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
##
|
224
|
+
# Sets a key in the YAML contents.
|
225
|
+
# Adds the key if it is missing, replaces it if it already exists.
|
226
|
+
#
|
227
|
+
# set_key_with_comment [ "default", "name" ], "george", "this is the name of the default user"
|
228
|
+
#
|
229
|
+
# The 'key' should be an array defining the path.
|
230
|
+
# If the 'comment' is blank (nil or ''), then it will not modify the comment.
|
231
|
+
# Use a whitespace string (' ') to indicate that you want a blank comment added.
|
232
|
+
#
|
233
|
+
# Value can be nil, a string, a symbol, a number, or a boolean.
|
234
|
+
# Value can also be a hash according to #add_key.
|
235
|
+
#
|
236
|
+
# Returns the contents object.
|
237
|
+
def set_key_with_comment(key, value, comment)
|
238
|
+
if comment.to_s == ''
|
239
|
+
set_key key, value
|
240
|
+
else
|
241
|
+
set_key key, value_with_comment(key, value, comment), false
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
##
|
246
|
+
# Removes the specified key from the contents.
|
247
|
+
#
|
248
|
+
# Returns an array containing the contents of the key.
|
249
|
+
# The first element will be for the key itself. If the
|
250
|
+
# key had child keys, then they will also be included in
|
251
|
+
# the array.
|
252
|
+
#
|
253
|
+
# The returned array will contain hashes for each removed
|
254
|
+
# key.
|
255
|
+
#
|
256
|
+
# data = remove_key %w(pet dog)
|
257
|
+
#
|
258
|
+
# [
|
259
|
+
# {
|
260
|
+
# :key => [ "pet", "dog" ],
|
261
|
+
# :value => "",
|
262
|
+
# :safe => true,
|
263
|
+
# :comment => "This list has the family dogs."
|
264
|
+
# },
|
265
|
+
# {
|
266
|
+
# :key => [ "pet", "dog", "sadie" ],
|
267
|
+
# :value => "",
|
268
|
+
# :safe => true,
|
269
|
+
# :comment => ""
|
270
|
+
# },
|
271
|
+
# {
|
272
|
+
# :key => [ "pet", "dog", "sadie", "breed" ],
|
273
|
+
# :value => "boxer",
|
274
|
+
# :safe => true,
|
275
|
+
# :comment => ""
|
276
|
+
# },
|
277
|
+
# {
|
278
|
+
# :key => [ "pet", "dog", "sadie", "dob" ],
|
279
|
+
# :value => "\"2016-06-01\"",
|
280
|
+
# :safe => true,
|
281
|
+
# :comment => "Estimated date of birth since she was a rescue."
|
282
|
+
# }
|
283
|
+
# ]
|
284
|
+
#
|
285
|
+
# The returned hashes can be fired right back into #add_key.
|
286
|
+
#
|
287
|
+
# data.each do |item|
|
288
|
+
# add_key_with_comment item[:key], item, item[:comment]
|
289
|
+
# end
|
290
|
+
#
|
291
|
+
# This method can be used to move a section within the file.
|
292
|
+
#
|
293
|
+
# # remove the 'familes' section from the file.
|
294
|
+
# section = remove_key [ "families" ]
|
295
|
+
# item = section.delete(section.first)
|
296
|
+
#
|
297
|
+
# # add the 'familes' section back in before the 'pets' section.
|
298
|
+
# add_key_with_comment item[:key], { before_section: "pets" }.merge(item), item[:comment]
|
299
|
+
#
|
300
|
+
# # add the data back into the 'familes' section.
|
301
|
+
# section.each do |item|
|
302
|
+
# add_key_with_comment item[:key], item, item[:comment]
|
303
|
+
# end
|
304
|
+
#
|
305
|
+
def remove_key(key)
|
306
|
+
rex_str = '(^'
|
307
|
+
key.each_with_index do |attr,level|
|
308
|
+
lev = (level < 1 ? '' : ('\\s\\s' * level))
|
309
|
+
if lev != ''
|
310
|
+
rex_str += '(?:' + lev + '.*\\n)*'
|
311
|
+
end
|
312
|
+
if level == key.count - 1
|
313
|
+
if level == 0
|
314
|
+
rex_str = '(^)(' + attr + ':[^\\n]*\\n(?:\\s\\s[^\\n]*\\n)*)'
|
315
|
+
else
|
316
|
+
rex_str += ')(' + lev + attr + ':[^\\n]*\\n(?:' + lev + '\\s\\s[^\\n]*\\n)*)'
|
317
|
+
end
|
318
|
+
else
|
319
|
+
rex_str += lev + attr + ':[^\\n]*\\n'
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# match result 1 is the parent key structure leading up to the key to be extracted.
|
324
|
+
# match result 2 is the key with all child elements to be extracted.
|
325
|
+
|
326
|
+
rex = Regexp.new(rex_str)
|
327
|
+
|
328
|
+
if @content =~ rex
|
329
|
+
|
330
|
+
# cache the key contents
|
331
|
+
key_content = $2
|
332
|
+
|
333
|
+
# remove the key from the main contents.
|
334
|
+
@content.gsub!(rex, "\\1")
|
335
|
+
|
336
|
+
# and separate into lines.
|
337
|
+
lines = extract_to_array(key_content)
|
338
|
+
|
339
|
+
ret = []
|
340
|
+
|
341
|
+
base_key = key.length == 1 ? [] : key[0...-1]
|
342
|
+
|
343
|
+
last_line = nil
|
344
|
+
|
345
|
+
lines.each do |line|
|
346
|
+
level = line[:level]
|
347
|
+
if level > 0 && line[:key]
|
348
|
+
# go from base 1 to base 0
|
349
|
+
level -= 1
|
350
|
+
|
351
|
+
# make sure the base key is the right length for the current key.
|
352
|
+
while level > base_key.length
|
353
|
+
base_key.push '?' # hopefully this never occurs.
|
354
|
+
end
|
355
|
+
while level < base_key.length
|
356
|
+
base_key.pop
|
357
|
+
end
|
358
|
+
|
359
|
+
# add our key to the base key.
|
360
|
+
# if the next key is below it, this ensures the parent structure is correct.
|
361
|
+
# if the next key is higher or at the same level the above loops should make it correct.
|
362
|
+
base_key << line[:key]
|
363
|
+
|
364
|
+
last_line = {
|
365
|
+
key: base_key,
|
366
|
+
value: line[:value].to_s,
|
367
|
+
comment: line[:comment],
|
368
|
+
safe: true
|
369
|
+
}
|
370
|
+
|
371
|
+
ret << last_line
|
372
|
+
elsif level > 0 && line[:comment]
|
373
|
+
if last_line && last_line[:key].length == level
|
374
|
+
if last_line[:comment]
|
375
|
+
last_line[:comment] += "\n" + line[:comment]
|
376
|
+
else
|
377
|
+
last_line[:comment] = "\n" + line[:comment]
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
ret
|
384
|
+
else
|
385
|
+
[]
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
##
|
390
|
+
# Realigns the file.
|
391
|
+
#
|
392
|
+
# All values and comments will line up at each level when complete.
|
393
|
+
def realign!
|
394
|
+
lines = extract_to_array(@content)
|
395
|
+
|
396
|
+
# reset the offsets.
|
397
|
+
value_offsets.clear
|
398
|
+
comment_offsets.clear
|
399
|
+
|
400
|
+
# get value offsets.
|
401
|
+
lines.each do |line|
|
402
|
+
level = line[:level]
|
403
|
+
if level > 0 && line[:key]
|
404
|
+
key_len = line[:key].length + 2 # include the colon and a space
|
405
|
+
if key_len > level_value_offset(level)
|
406
|
+
set_level_value_offset level, key_len
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
# get comment offsets.
|
412
|
+
lines.each do |line|
|
413
|
+
level = line[:level]
|
414
|
+
if level > 0 && line[:value]
|
415
|
+
voff = level_value_offset(level)
|
416
|
+
val_len = line[:value] ? line[:value].length : 0
|
417
|
+
coff = voff + val_len + 1 # add a space after the value.
|
418
|
+
if coff > level_comment_offset(level)
|
419
|
+
set_level_comment_offset level, coff
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
# convert the lines back into strings with proper spacing.
|
425
|
+
lines = lines.map do |line|
|
426
|
+
level = line[:level]
|
427
|
+
if level > 0
|
428
|
+
if line[:key]
|
429
|
+
# a key: value line.
|
430
|
+
key = line[:key] + ':'
|
431
|
+
key = key.ljust(level_value_offset(level), ' ') unless line[:value].to_s == '' && line[:comment].to_s == ''
|
432
|
+
val = line[:value].to_s
|
433
|
+
val = val.ljust(level_comment_offset(level) - level_value_offset(level), ' ') unless line[:comment].to_s == ''
|
434
|
+
comment = line[:comment] ? "# #{line[:comment]}" : ''
|
435
|
+
(' ' * (level - 1)) + key + val + comment
|
436
|
+
else
|
437
|
+
# just a comment line.
|
438
|
+
(' ' * (level - 1)) +
|
439
|
+
(' ' * level_comment_offset(level)) +
|
440
|
+
"# #{line[:comment]}"
|
441
|
+
end
|
442
|
+
else
|
443
|
+
line[:value] # return the original value
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
@content = lines.join("\n") + "\n"
|
448
|
+
end
|
449
|
+
|
450
|
+
##
|
451
|
+
# Gets the value offset for the specified level.
|
452
|
+
#
|
453
|
+
# The offset is based on the beginning of the level in question.
|
454
|
+
# There will always be at least one whitespace before the value.
|
455
|
+
# For instance an offset of 10 for level 2 would be like this:
|
456
|
+
#
|
457
|
+
# one:
|
458
|
+
# two: value
|
459
|
+
# some_long_name: value
|
460
|
+
# # 0123456789^
|
461
|
+
#
|
462
|
+
def level_value_offset(level)
|
463
|
+
value_offsets[level] || 0
|
464
|
+
end
|
465
|
+
|
466
|
+
##
|
467
|
+
# Sets the value offset for the specified level.
|
468
|
+
#
|
469
|
+
# The offset is based on the beginning of the level in question.
|
470
|
+
# There will always be at least one whitespace before the value.
|
471
|
+
# For instance an offset of 10 for level 2 would be like this:
|
472
|
+
#
|
473
|
+
# one:
|
474
|
+
# two: value
|
475
|
+
# some_long_name: value
|
476
|
+
# # 0123456789^
|
477
|
+
#
|
478
|
+
def set_level_value_offset(level, offset)
|
479
|
+
value_offsets[level] = offset
|
480
|
+
end
|
481
|
+
|
482
|
+
##
|
483
|
+
# Gets the comment offset for the specified level.
|
484
|
+
#
|
485
|
+
# The offset is based on the beginning of the level in question.
|
486
|
+
# There will always be at least one whitespace before the value
|
487
|
+
# and at least one whitespace between the value and a comment.
|
488
|
+
# For instance an offset of 15 for level 2 would be like this:
|
489
|
+
#
|
490
|
+
# one:
|
491
|
+
# two: value # comment
|
492
|
+
# some_long_name: value # comment
|
493
|
+
# # 012345678901234^
|
494
|
+
#
|
495
|
+
def level_comment_offset(level)
|
496
|
+
comment_offsets[level] || 0
|
497
|
+
end
|
498
|
+
|
499
|
+
##
|
500
|
+
# Sets the comment offset for the specified level.
|
501
|
+
#
|
502
|
+
# The offset is based on the beginning of the level in question.
|
503
|
+
# There will always be at least one whitespace before the value
|
504
|
+
# and at least one whitespace between the value and a comment.
|
505
|
+
# For instance an offset of 15 for level 2 would be like this:
|
506
|
+
#
|
507
|
+
# one:
|
508
|
+
# two: value # comment
|
509
|
+
# some_long_name: value # comment
|
510
|
+
# # 012345678901234^
|
511
|
+
#
|
512
|
+
def set_level_comment_offset(level, offset)
|
513
|
+
comment_offsets[level] = offset
|
514
|
+
end
|
515
|
+
|
516
|
+
##
|
517
|
+
# Allows comparing the contents against a regular expression.
|
518
|
+
def =~(regexp)
|
519
|
+
@content =~ regexp
|
520
|
+
end
|
521
|
+
|
522
|
+
private
|
523
|
+
|
524
|
+
def extract_to_array(data)
|
525
|
+
# match 1 = lead white
|
526
|
+
# ([ \t]*)
|
527
|
+
# match 2 = key name
|
528
|
+
# (\S+):
|
529
|
+
# match 3 = value with leading whitespace
|
530
|
+
# ((?:[ \t]*(?:"(?:[^"]*(?:(?:\\{1}|\\{3}|\\{5}|\\{7}|\\{9})")?)*"|'(?:[^']*(?:(?:\\{1}|\\{3}|\\{5}|\\{7}|\\{9})')?)*'|[^\s#"']+))*)
|
531
|
+
# ignore white between value and comment (if any).
|
532
|
+
# [ \t]*
|
533
|
+
# match 4 = comment (if any).
|
534
|
+
# (?:#([^\n]*))?
|
535
|
+
line_regex = /\A([ \t]*)(\S+):((?:[ \t]*(?:"(?:[^"]*(?:(?:\\{1}|\\{3}|\\{5}|\\{7}|\\{9})")?)*"|'(?:[^']*(?:(?:\\{1}|\\{3}|\\{5}|\\{7}|\\{9})')?)*'|[^\s#"']+))*)[ \t]*(?:#([^\n]*))?\z/
|
536
|
+
last_level = 0
|
537
|
+
data.split("\n").map do |raw_line|
|
538
|
+
# assuming the file is valid any lines not matching the regex should be comments or blank.
|
539
|
+
match = line_regex.match(raw_line)
|
540
|
+
if match # a key: value line
|
541
|
+
last_level = (match[1].length / 2).to_i + 1 # one level per 2 spaces.
|
542
|
+
{
|
543
|
+
level: last_level,
|
544
|
+
key: match[2].strip,
|
545
|
+
value: match[3] ? match[3].strip : nil,
|
546
|
+
comment: match[4] ? match[4].lstrip : nil
|
547
|
+
}
|
548
|
+
elsif raw_line =~ /\A(\s*)#(.*)\z/ # a comment
|
549
|
+
whitespace = $1
|
550
|
+
raw_line = $2
|
551
|
+
level =
|
552
|
+
if whitespace.length >= (last_level * 2)
|
553
|
+
last_level
|
554
|
+
else
|
555
|
+
(whitespace.length / 2).to_i + 1
|
556
|
+
end
|
557
|
+
raw_line = raw_line[1..-1] if raw_line[0] == ' '
|
558
|
+
{
|
559
|
+
level: level,
|
560
|
+
comment: raw_line
|
561
|
+
}
|
562
|
+
else
|
563
|
+
{
|
564
|
+
level: 0,
|
565
|
+
value: raw_line
|
566
|
+
}
|
567
|
+
end
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
def value_with_comment(key, value, comment)
|
572
|
+
vh = value.is_a?(::Hash) ? value : { safe: false, value: value, before_section: nil }
|
573
|
+
unless vh[:safe]
|
574
|
+
vh[:value] = add_comment(key, add_value_offset(key, safe_value(vh[:value])), comment)
|
575
|
+
vh[:safe] = true
|
576
|
+
end
|
577
|
+
vh
|
578
|
+
end
|
579
|
+
|
580
|
+
def add_value_offset(key, safe_value)
|
581
|
+
key_len = key.last.to_s.length + 1 # add one for the colon.
|
582
|
+
voff = level_value_offset(key.count) - key_len
|
583
|
+
voff = 1 if voff < 1
|
584
|
+
|
585
|
+
(' ' * voff) + safe_value
|
586
|
+
end
|
587
|
+
|
588
|
+
def add_comment(key, safe_value, comment)
|
589
|
+
key_len = key.last.to_s.length + 1 # add one for the colon.
|
590
|
+
coff = level_comment_offset(key.count) - key_len - safe_value.length
|
591
|
+
coff = 1 if coff < 1
|
592
|
+
coff_total = ((key.count - 1) * 2) + value.length + coff
|
593
|
+
|
594
|
+
safe_value + (' ' * coff) + '# ' + comment.to_s.gsub("\r\n", "\n").gsub("\n", "\n#{' ' * coff_total}# ")
|
595
|
+
end
|
596
|
+
|
597
|
+
def value_offsets
|
598
|
+
@value_offsets ||= [ ]
|
599
|
+
end
|
600
|
+
|
601
|
+
def comment_offsets
|
602
|
+
@comment_offsets ||= [ ]
|
603
|
+
end
|
604
|
+
|
605
|
+
# always returns a string, even for safe values.
|
606
|
+
def safe_value(value)
|
607
|
+
# Allows the user to specify the value as a hash option without marking it as safe.
|
608
|
+
if value.is_a?(::Hash) && value[:value] && !value[:safe]
|
609
|
+
value = value[:value]
|
610
|
+
end
|
611
|
+
|
612
|
+
if value.is_a?(::Hash) && value[:safe]
|
613
|
+
# If the user specifies a safe value, return it as-is.
|
614
|
+
(value[:value] || value[:safe]).to_s
|
615
|
+
elsif value.nil?
|
616
|
+
# If the value is nil, return an empty string.
|
617
|
+
''
|
618
|
+
else
|
619
|
+
# Otherwise process the value to make it YAML compliant.
|
620
|
+
unless value.is_a?(::String) || value.is_a?(::Symbol) || value.is_a?(::Numeric) || value.is_a?(::TrueClass) || value.is_a?(::FalseClass)
|
621
|
+
raise ArgumentError, "'value' must be a value type (string, symbol, number, boolean)"
|
622
|
+
end
|
623
|
+
|
624
|
+
if value.is_a?(::String)
|
625
|
+
if value =~ /\A\s*\z/m || # Empty or filled with whitespace
|
626
|
+
value =~ /\A\s/m || # Starts with whitespace
|
627
|
+
value =~ /\s\z/m || # Ends with whitespace
|
628
|
+
value =~ /\A[+-]?\d+(\.\d*)?\z/ || # Contains a probable number
|
629
|
+
value =~ /\A0(b[01]*|x[0-9a-f]*)\z/i # Another probable number in binary or hex format.
|
630
|
+
value.inspect
|
631
|
+
elsif value =~ /\A([0-9]*[a-z]|[a-z])([a-z0-9_ .,=-]*[a-z0-9_])*\z/i
|
632
|
+
value
|
633
|
+
else
|
634
|
+
value.inspect
|
635
|
+
end
|
636
|
+
else
|
637
|
+
value.inspect
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
end
|
642
|
+
|
643
|
+
end
|
644
|
+
|
645
|
+
protected
|
646
|
+
|
647
|
+
##
|
648
|
+
# Repairs a YAML file, creating it if necessary.
|
649
|
+
#
|
650
|
+
# The optional parameters define what all will be done by this method before yielding to a supplied block.
|
651
|
+
# The contents of the YAML file are yielded to the block and the block should return the modified contents.
|
652
|
+
#
|
653
|
+
# repair_yaml "config/database.yml" do |contents|
|
654
|
+
# contents.add_key [ "test", "database" ], "db/test.sqlite3"
|
655
|
+
# end
|
656
|
+
#
|
657
|
+
# If 'env_ref_set' is set to a non-empty value, then it defines the anchor providing default values
|
658
|
+
# for the environment sections. The default value is 'default'.
|
659
|
+
#
|
660
|
+
# If 'ensure_default' is set, then a 'default' section with a 'default' anchor will be created at the
|
661
|
+
# beginning of the file unless a 'default' section already exists in the file.
|
662
|
+
# The default value is 'true'.
|
663
|
+
#
|
664
|
+
# If 'ensure_env' is set, then the environment sections will be created if missing. This ensures that
|
665
|
+
# a 'development', 'test', and 'production' section all exist. The default value is 'true'.
|
666
|
+
#
|
667
|
+
# If 'realign' is set, then the entire file will be processed. All values will be aligned at each
|
668
|
+
# level and so will all comments. The default is 'true'.
|
669
|
+
#
|
670
|
+
# The default options on an empty file will generate a 'default' section before yielding and then
|
671
|
+
# fill in the environment sections after the block returns.
|
672
|
+
#
|
673
|
+
# default: &default
|
674
|
+
#
|
675
|
+
# development:
|
676
|
+
# <<: *default
|
677
|
+
#
|
678
|
+
# test:
|
679
|
+
# <<: *default
|
680
|
+
#
|
681
|
+
# production:
|
682
|
+
# <<: *default
|
683
|
+
#
|
684
|
+
def repair_yaml(filename, env_ref_sect = 'default', ensure_default = true, ensure_env = true, realign = true) # :doc:
|
685
|
+
dirty = false
|
686
|
+
stat = :updated
|
687
|
+
|
688
|
+
# ensure the file exists.
|
689
|
+
unless File.exist?(filename)
|
690
|
+
File.write filename, "# File created by Incline v#{Incline::VERSION}.\n"
|
691
|
+
stat = :created
|
692
|
+
dirty = true
|
693
|
+
end
|
694
|
+
|
695
|
+
orig_contents = File.read(filename)
|
696
|
+
|
697
|
+
contents = YamlContents.new(orig_contents)
|
698
|
+
|
699
|
+
if ensure_default
|
700
|
+
contents.set_key %w(default), '&default', false
|
701
|
+
end
|
702
|
+
|
703
|
+
yield contents if block_given?
|
704
|
+
|
705
|
+
if ensure_env
|
706
|
+
%w(development test production).each do |sect|
|
707
|
+
contents.add_key [ sect ], nil
|
708
|
+
end
|
709
|
+
end
|
710
|
+
|
711
|
+
# Potential bug, and I'm not even sure if it would be or not.
|
712
|
+
# But since we use relatively simple regular expressions to perform the set action,
|
713
|
+
# the << "key" can only exist once and would be overridden.
|
714
|
+
# That means that a section would not be able to include multiple anchors.
|
715
|
+
# Like I said before, I'm not sure if that would actually be a bug or not.
|
716
|
+
#
|
717
|
+
# Luckily, the YAML files aren't meant to be overly complex so this shouldn't show
|
718
|
+
# up regularly if at all.
|
719
|
+
unless env_ref_sect.to_s.strip == ''
|
720
|
+
%w(development test production).each do |sect|
|
721
|
+
contents.set_key [ sect, '<<' ], '*' + env_ref_sect
|
722
|
+
end
|
723
|
+
end
|
724
|
+
|
725
|
+
contents.realign! if realign
|
726
|
+
|
727
|
+
unless dirty
|
728
|
+
dirty = (orig_contents != contents.to_s)
|
729
|
+
end
|
730
|
+
|
731
|
+
if dirty
|
732
|
+
File.write filename, contents.to_s
|
733
|
+
say_status stat, filename, :green
|
734
|
+
else
|
735
|
+
say_status :unchanged, filename, :blue
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
739
|
+
|
740
|
+
|
741
|
+
end
|
742
|
+
end
|
743
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
|
2
|
+
module Incline
|
3
|
+
class CLI
|
4
|
+
|
5
|
+
##
|
6
|
+
# Defines the 'usage' command for the CLI.
|
7
|
+
class Usage
|
8
|
+
|
9
|
+
##
|
10
|
+
# Creates a new 'usage' command for the CLI.
|
11
|
+
def initialize
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Shows usage information for the application.
|
17
|
+
def run
|
18
|
+
|
19
|
+
commands = Incline::CLI::valid_commands.sort{|a,b| a[0] <=> b[0]}
|
20
|
+
|
21
|
+
msg = ANSI.ansi(:bold) { "Usage: #{$PROGRAM_NAME} command <arguments>" }
|
22
|
+
msg += "\nValid Commands:\n"
|
23
|
+
|
24
|
+
commands.each do |(name,klass,params)|
|
25
|
+
comment = get_run_comment(klass)
|
26
|
+
comment = "(No description)" if comment.to_s.strip == ''
|
27
|
+
comment = ' ' + comment.gsub("\n", "\n ")
|
28
|
+
msg += " #{name}"
|
29
|
+
pend = ''
|
30
|
+
params.each do |t,p|
|
31
|
+
msg += ' '
|
32
|
+
if t == :req
|
33
|
+
msg += p.to_s
|
34
|
+
elsif t == :opt
|
35
|
+
msg += '[' + p.to_s
|
36
|
+
pend += ']'
|
37
|
+
else
|
38
|
+
msg += '<...>'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
msg += "\n" + comment + "\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
STDOUT.print msg
|
45
|
+
msg
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def get_run_comment(klass)
|
51
|
+
meth = klass.instance_method(:run)
|
52
|
+
return '' unless meth
|
53
|
+
file,line_num = meth.source_location
|
54
|
+
return '' unless file && line_num
|
55
|
+
return '' unless File.exist?(file)
|
56
|
+
|
57
|
+
source_lines = File.read(file).gsub("\r\n", "\n").split("\n")
|
58
|
+
|
59
|
+
comments = []
|
60
|
+
|
61
|
+
# line_num is 1 based so we need to subtract 1 to get the line with the method definition
|
62
|
+
# then we subtract 1 again to get the first line before the method definition.
|
63
|
+
line_num -= 2
|
64
|
+
|
65
|
+
while line_num >= 0 && source_lines[line_num] =~ /\A(?:\s*)(?:#(?:\s(.*))?)?\z/
|
66
|
+
line = $1.to_s
|
67
|
+
comments << line
|
68
|
+
line_num -= 1
|
69
|
+
end
|
70
|
+
|
71
|
+
comments.reverse.join("\n")
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
module Incline
|
3
|
+
class CLI
|
4
|
+
|
5
|
+
##
|
6
|
+
# Defines the 'version' command for the CLI.
|
7
|
+
class Version
|
8
|
+
|
9
|
+
##
|
10
|
+
# Creates a new 'version' command for the CLI.
|
11
|
+
def initialize
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Shows the version of the Incline library.
|
17
|
+
def run
|
18
|
+
STDOUT.puts "Incline v#{Incline::VERSION}"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/incline/cli.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'ansi/code'
|
2
|
+
require 'incline/version'
|
3
|
+
require 'incline/cli/errors'
|
4
|
+
|
5
|
+
require 'incline/cli/helpers/yaml'
|
6
|
+
|
7
|
+
require 'incline/cli/version'
|
8
|
+
require 'incline/cli/usage'
|
9
|
+
|
10
|
+
|
11
|
+
module Incline
|
12
|
+
class CLI
|
13
|
+
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute(*args)
|
20
|
+
begin
|
21
|
+
if args.empty? || %w(usage help /? -? -help --help).include?(args.first)
|
22
|
+
process_command(:usage)
|
23
|
+
else
|
24
|
+
process_command(*args)
|
25
|
+
end
|
26
|
+
rescue UsageError => err
|
27
|
+
STDERR.puts err.message
|
28
|
+
process_command(:usage)
|
29
|
+
rescue CliError => err
|
30
|
+
STDERR.puts ANSI.code(:red) { 'ERROR:' }
|
31
|
+
STDERR.puts err.message
|
32
|
+
rescue RuntimeError => err
|
33
|
+
STDERR.puts ANSI.code(:red) { 'FATAL ERROR:' }
|
34
|
+
STDERR.puts err.inspect
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def process_command(command, *args)
|
39
|
+
command = command.to_sym
|
40
|
+
cmd_info = self.class.command_list.find{|c| c[:method] == command}
|
41
|
+
if cmd_info
|
42
|
+
args = args.dup
|
43
|
+
args = []
|
44
|
+
cmd_info[:new_params].each do |(type,name)|
|
45
|
+
if type == :rest
|
46
|
+
args += args
|
47
|
+
break
|
48
|
+
elsif type == :req
|
49
|
+
if args.empty?
|
50
|
+
raise UsageError, "Missing required parameter '#{name}' for command '#{command}'."
|
51
|
+
end
|
52
|
+
args << args.delete_at(0)
|
53
|
+
elsif type == :opt
|
54
|
+
if args.empty?
|
55
|
+
break
|
56
|
+
else
|
57
|
+
args << args.delete_at(0)
|
58
|
+
end
|
59
|
+
else
|
60
|
+
raise UsageError, "Unknown parameter type '#{type}' for command '#{command}'."
|
61
|
+
end
|
62
|
+
end
|
63
|
+
cmd_object = cmd_info[:klass].new(*args)
|
64
|
+
cmd_object.send(:run)
|
65
|
+
else
|
66
|
+
raise UsageError, "Unknown command '#{command}'."
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.valid_commands
|
71
|
+
command_list.map do |cmd_info|
|
72
|
+
[ cmd_info[:method], cmd_info[:klass], cmd_info[:new_params] ]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def self.command_list
|
79
|
+
@command_list ||=
|
80
|
+
begin
|
81
|
+
constants.map do |c|
|
82
|
+
klass = const_get c
|
83
|
+
# class must have a :new class method and a :run instance method.
|
84
|
+
if klass.instance_methods.include?(:run) && klass.methods.include?(:new)
|
85
|
+
m = klass.method(:new)
|
86
|
+
new_params = m.parameters.select{ |p| p[1] && (p[0] == :req || p[0] == :opt || p[0] == :rest) }.freeze
|
87
|
+
m = klass.instance_method(:run)
|
88
|
+
run_params = m.parameters.select{ |p| p[1] && (p[0] == :req || p[0] == :opt || p[0] == :rest) }.freeze
|
89
|
+
if run_params.count == 0
|
90
|
+
{
|
91
|
+
name: c.to_s,
|
92
|
+
method: c.to_s.gsub(/(.)([A-Z])/,"\\1_\\2").downcase.to_sym,
|
93
|
+
new_params: new_params,
|
94
|
+
klass: klass,
|
95
|
+
valid: true
|
96
|
+
}
|
97
|
+
else
|
98
|
+
{
|
99
|
+
name: c.to_s,
|
100
|
+
valid: false,
|
101
|
+
failure_reason: 'Method :run expects parameters.'
|
102
|
+
}
|
103
|
+
end
|
104
|
+
else
|
105
|
+
{
|
106
|
+
name: c,
|
107
|
+
valid: false,
|
108
|
+
failure_reason: 'Missing :new or :run.'
|
109
|
+
}
|
110
|
+
end
|
111
|
+
end.select do |c|
|
112
|
+
c[:valid]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -216,11 +216,17 @@ module Incline::Extensions
|
|
216
216
|
end
|
217
217
|
|
218
218
|
##
|
219
|
-
# Make sure main_app is available and working correctly.
|
219
|
+
# Make sure main_app is available and working correctly in tests.
|
220
220
|
def main_app
|
221
221
|
Rails.application.class.routes.url_helpers
|
222
222
|
end
|
223
|
-
|
223
|
+
|
224
|
+
##
|
225
|
+
# Make sure incline is available and working correctly in tests.
|
226
|
+
def incline
|
227
|
+
Incline::Engine.routes.url_helpers
|
228
|
+
end
|
229
|
+
|
224
230
|
##
|
225
231
|
# Determines if a user is logged into the test session
|
226
232
|
def is_logged_in?
|
data/lib/incline/version.rb
CHANGED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'incline/cli'
|
3
|
+
|
4
|
+
class YamlContentsTest < ActiveSupport::TestCase
|
5
|
+
|
6
|
+
ALIGNED_YAML = <<-YAML
|
7
|
+
default: &default
|
8
|
+
alpha: # comment for alpha
|
9
|
+
bravo: # comment for bravo
|
10
|
+
one: &one
|
11
|
+
charlie: # comment for charlie
|
12
|
+
item_1: :simple # Level 3 remains aligned in all sections.
|
13
|
+
two: &two
|
14
|
+
delta:
|
15
|
+
item_1: "This is a \\"string\\" with a '#' character in it." # A nice long string on level 3.
|
16
|
+
item_2: true # Just true.
|
17
|
+
# This comment is aligned with the comment above.
|
18
|
+
three: &three
|
19
|
+
echo:
|
20
|
+
item_1: <%= Rails.application.secrets["db"]["password"] %> # Ironically, the same length as the string above.
|
21
|
+
YAML
|
22
|
+
|
23
|
+
TOP_OF_FILE = "# Top of file.\n"
|
24
|
+
|
25
|
+
SIMPLE_ADD_KEY_RESULT = <<-YAML.strip
|
26
|
+
# Top of file.
|
27
|
+
|
28
|
+
default:
|
29
|
+
one:
|
30
|
+
alpha: true
|
31
|
+
YAML
|
32
|
+
|
33
|
+
SIMPLE_REPLACE_RESULT = <<-YAML.strip
|
34
|
+
# Top of file.
|
35
|
+
|
36
|
+
default:
|
37
|
+
one:
|
38
|
+
alpha: false
|
39
|
+
YAML
|
40
|
+
|
41
|
+
MULTIPLE_ADD_RESULT = <<-YAML.strip
|
42
|
+
# Top of file.
|
43
|
+
|
44
|
+
one:
|
45
|
+
alpha: 1
|
46
|
+
delta: 4
|
47
|
+
|
48
|
+
two:
|
49
|
+
bravo: 2
|
50
|
+
echo: 5
|
51
|
+
|
52
|
+
three:
|
53
|
+
charlie: 3
|
54
|
+
foxtrot: 6
|
55
|
+
YAML
|
56
|
+
|
57
|
+
test 'does not modify unnecessarily on realign' do
|
58
|
+
contents = Incline::CliHelpers::Yaml::YamlContents.new(ALIGNED_YAML)
|
59
|
+
contents.realign!
|
60
|
+
assert_equal ALIGNED_YAML, contents.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
test 'should insert as appropriate' do
|
64
|
+
contents = Incline::CliHelpers::Yaml::YamlContents.new(TOP_OF_FILE)
|
65
|
+
contents.add_key %w(default one alpha), true
|
66
|
+
assert_equal SIMPLE_ADD_KEY_RESULT, contents.to_s.strip
|
67
|
+
end
|
68
|
+
|
69
|
+
test 'should replace as appropriate' do
|
70
|
+
contents = Incline::CliHelpers::Yaml::YamlContents.new(SIMPLE_ADD_KEY_RESULT)
|
71
|
+
contents.set_key %w(default one alpha), false
|
72
|
+
assert_equal SIMPLE_REPLACE_RESULT, contents.to_s.strip
|
73
|
+
end
|
74
|
+
|
75
|
+
test 'multiple add works as expected' do
|
76
|
+
contents = Incline::CliHelpers::Yaml::YamlContents.new(TOP_OF_FILE)
|
77
|
+
|
78
|
+
contents.add_key %w(one alpha), 1
|
79
|
+
contents.add_key %w(two bravo), 2
|
80
|
+
contents.add_key %w(three charlie), 3
|
81
|
+
contents.add_key %w(one delta), 4
|
82
|
+
contents.add_key %w(two echo), 5
|
83
|
+
contents.add_key %w(three foxtrot), 6
|
84
|
+
|
85
|
+
assert_equal MULTIPLE_ADD_RESULT, contents.to_s.strip
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
class WelcomeControllerTest < ActionDispatch::IntegrationTest
|
4
|
-
|
4
|
+
|
5
|
+
# 2017-08-03: root path should be changeable by actual app. remove jumbotron check and instead do some link checks that should always be present.
|
5
6
|
test 'should get root_path' do
|
6
7
|
get root_path
|
7
8
|
assert_response :success
|
8
9
|
assert_select 'title', full_title
|
9
|
-
assert_select '
|
10
|
-
assert_select 'a[href=?]', root_path, count: 2
|
10
|
+
assert_select 'a[href=?]', main_app.root_path, count: 2
|
11
11
|
assert_select 'a[href=?]', incline.login_path, count: 1
|
12
12
|
assert_select 'a[href=?]', incline.contact_path, count: 1
|
13
13
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>Congrats, this is the DUMMY root.</h1>
|
data/test/dummy/config/routes.rb
CHANGED
@@ -131,12 +131,12 @@ module Incline
|
|
131
131
|
assert_not is_logged_in?
|
132
132
|
|
133
133
|
# invalid activation token
|
134
|
-
get incline.
|
134
|
+
get incline.edit_account_activation_path('invalid-token', email: user.email)
|
135
135
|
assert_not is_logged_in?
|
136
136
|
assert_not user.reload.activated?
|
137
137
|
|
138
138
|
# valid activation token
|
139
|
-
get incline.
|
139
|
+
get incline.edit_account_activation_path(token, email: user.email)
|
140
140
|
assert user.reload.activated?
|
141
141
|
follow_redirect!
|
142
142
|
assert_template 'incline/users/show'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: incline
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Beau Barker
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -378,6 +378,7 @@ files:
|
|
378
378
|
- db/migrate/20170622195742_add_non_standard_to_action_security.rb
|
379
379
|
- db/migrate/20170622230422_add_visible_to_action_security.rb
|
380
380
|
- db/seeds.rb
|
381
|
+
- exe/incline
|
381
382
|
- exe/new_incline_app
|
382
383
|
- lib/generators/incline/install_generator.rb
|
383
384
|
- lib/generators/incline/templates/_app_menu_anon.html.erb
|
@@ -393,6 +394,11 @@ files:
|
|
393
394
|
- lib/generators/incline/templates/incline_version.rb
|
394
395
|
- lib/incline.rb
|
395
396
|
- lib/incline/auth_engine_base.rb
|
397
|
+
- lib/incline/cli.rb
|
398
|
+
- lib/incline/cli/errors.rb
|
399
|
+
- lib/incline/cli/helpers/yaml.rb
|
400
|
+
- lib/incline/cli/usage.rb
|
401
|
+
- lib/incline/cli/version.rb
|
396
402
|
- lib/incline/data_tables_request.rb
|
397
403
|
- lib/incline/date_time_formats.rb
|
398
404
|
- lib/incline/engine.rb
|
@@ -446,6 +452,7 @@ files:
|
|
446
452
|
- lib/templates/jbuilder/scaffold/index.json.jbuilder
|
447
453
|
- lib/templates/jbuilder/scaffold/show.json.jbuilder
|
448
454
|
- lib/templates/rails/scaffold_controller/controller.rb
|
455
|
+
- test/cli/yaml_contents_test.rb
|
449
456
|
- test/controllers/incline/access_groups_controller_test.rb
|
450
457
|
- test/controllers/incline/access_test_controller_test.rb
|
451
458
|
- test/controllers/incline/contact_controller_test.rb
|
@@ -458,10 +465,12 @@ files:
|
|
458
465
|
- test/dummy/app/assets/stylesheets/application.css
|
459
466
|
- test/dummy/app/controllers/application_controller.rb
|
460
467
|
- test/dummy/app/controllers/concerns/.keep
|
468
|
+
- test/dummy/app/controllers/dummy_controller.rb
|
461
469
|
- test/dummy/app/helpers/application_helper.rb
|
462
470
|
- test/dummy/app/mailers/.keep
|
463
471
|
- test/dummy/app/models/.keep
|
464
472
|
- test/dummy/app/models/concerns/.keep
|
473
|
+
- test/dummy/app/views/dummy/hello.html.erb
|
465
474
|
- test/dummy/app/views/layouts/application.html.erb
|
466
475
|
- test/dummy/app/views/layouts/incline/_app_menu_anon.html.erb
|
467
476
|
- test/dummy/app/views/layouts/incline/_app_menu_authenticated.html.erb
|