web-utils 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 406c990b91a61d30f76d4ff065013eaf1ec741ea
4
+ data.tar.gz: 4788cfe256f7e536de8067c79bef7193a35ed0e7
5
+ SHA512:
6
+ metadata.gz: 2a675233f99d1b6b7299e830c6d80fbf46637b2d9c43c1a35753887ec1f254b126f4e0cfcb439d080a1c11eb80bd527f0807c4030f352365d44644b23ddea476
7
+ data.tar.gz: 08f910f903304d445e32a66f20ad8bb9c025d6b5516f4437b83ae8f18654d0633d9c05b40275033b2cd2b451fc636ab41a0496e8ae978e4215fe86d69d18edb4
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ .DS_STORE
2
+ *.swp
3
+ *.sass-cache
4
+ pkg/
5
+ Gemfile.lock
6
+ .bundle/
7
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+ gem 'minitest', '>=5.8.0'
4
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2016 Mickael Riga
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
data/README.md ADDED
@@ -0,0 +1,242 @@
1
+ WebUtils
2
+ ========
3
+
4
+ `WebUtils` is basically a collection of useful helper
5
+ methods that are quite common to have in a web project.
6
+ It is organized like `Rack::Utils` and actually extends it.
7
+
8
+ Some of the methods are similar to methods you would have
9
+ in `Active::Support` but without monkey patching. Although it
10
+ is only a coincidence. The purpose is not to build an alternative.
11
+
12
+ Here is how you would use it with `Sinatra`:
13
+
14
+ ```ruby
15
+ require 'sinatra/base'
16
+ require 'web_utils'
17
+
18
+ class Main < Sinatra::Base
19
+
20
+ # Your frontend code...
21
+
22
+ helpers do
23
+ include WebUtils
24
+ # Your other helpers...
25
+ end
26
+
27
+ end
28
+ ```
29
+
30
+ Some methods are also useful on the model side.
31
+
32
+ Here is a list of the available methods and what they do:
33
+
34
+ `blank?(string)`
35
+ ----------------
36
+
37
+ Just tells you if the string is blank or not.
38
+
39
+ `pluralize(string)`
40
+ -------------------
41
+
42
+ Pluralize simple words. Just override when necessary.
43
+
44
+ `singularize(string)`
45
+ -------------------
46
+
47
+ Singularize simple words. Just override when necessary.
48
+
49
+ `dasherize_class_name(string)`
50
+ ------------------------------
51
+
52
+ Dasherize class names. Module separator is a double dash.
53
+ So `BlogArticle::Comment` becomes `blog-article--comment`.
54
+ Also for simplicity, it does not gather accronyms. e.g. `net--f-t-p`.
55
+ This is useful for urls or creating CSS class names or IDs.
56
+
57
+ `undasherize_class_name(string)`
58
+ --------------------------------
59
+
60
+ Basically the opposite.
61
+
62
+ `resolve_class_name(string, context=Kernel)`
63
+ --------------------------------------------
64
+
65
+ It takes the class name as a string and returns the class.
66
+ You can pass a class name with modules as well (e.g. "Net::FTP").
67
+ This is actually the main reason why there is a `context`
68
+ argument, because it uses recursion to do this.
69
+ But `context` is still useful otherwise.
70
+
71
+ `resolve_dasherized_class_name(string)`
72
+ ---------------------------------------
73
+
74
+ Same except that it takes the dasherized version of the
75
+ class name as an argument and returns the class itself (not a string).
76
+ Useful for resolving a class from a URL param.
77
+
78
+ `guess_related_class_name(parent_class, string)`
79
+ ------------------------------------------------
80
+
81
+ It is mainly used for guessing the class name of a
82
+ children class with a plural name.
83
+ So `guess_related_class_name(BlogArticle, :comments)`
84
+ will return `'BlogArticle::Comment'`.
85
+
86
+ `get_value(value, target=Kernel)`
87
+ ----------------------------------
88
+
89
+ It is used for example for getting a default value for something
90
+ and it is passed either as:
91
+
92
+ - A direct value (e.g. `"John Doe"`)
93
+ - A proc (e.g. `proc{ Time.now }`)
94
+ - A symbol (e.g. `:get_last_value`)
95
+
96
+ In the case of a symbol, the message is called on the target.
97
+ Therefore you would always give the target just in case it
98
+ is a symbol.
99
+
100
+ If the value can only be direct or a Proc, you can ignore the
101
+ second argument (target).
102
+
103
+ `deep_copy(object)`
104
+ -------------------
105
+
106
+ This makes a deeper copy of an object, since `dup` does not
107
+ duplicate nested objects in a hash for example. It uses a simple
108
+ marshal/unmarshal mechanism.
109
+
110
+ It is a bit of a hack and not that web-specific, but useful
111
+ if you want to avoid some nasty bugs.
112
+
113
+ `ensure_key!(hash, key, default_value)`
114
+ ---------------------------------------
115
+
116
+ If the hash does not have the key, it sets it with the
117
+ default value. And this value is also returned by the method.
118
+
119
+ `ensure_key(hash, key, default_value)`
120
+ --------------------------------------
121
+
122
+ Same as `ensure_key!` except that it does not change the original
123
+ hash. It returns a new one.
124
+
125
+ `slugify(string, force_lowercase=true)`
126
+ -----------------
127
+
128
+ This makes the strings ready to be used as a slug in a URL.
129
+ It removes the accents, replaces a lot of separators with
130
+ dashes and escapes it. By default it forces the output to
131
+ be lowercase, but if you pass `false` as a second argument,
132
+ it will not change the case of letters.
133
+
134
+ `label_for_field(string_or_symbol)`
135
+ -----------------------------------
136
+
137
+ Returns a human readable version of a field name.
138
+ It says `field`, but it could be any kind of symbol I guess.
139
+
140
+ `each_stub(nested_object) {|object,key_or_index,value| ... }`
141
+ -------------------------------------------------------------
142
+
143
+ It is used to run something on all the nested stubs of
144
+ an array or a hash. The second argument of the block is
145
+ either a key if the object is a hash, or an index if the
146
+ object is an array.
147
+
148
+ `automatic_typecast(string, casted=[:bool, :nil, :int, :float])`
149
+ ----------------------------
150
+
151
+ It tries to change a string value received by an HTML form
152
+ or a CSV file into an object when it can. So far it recognizes
153
+ simple things like `true`, `false`, integers and floats.
154
+ And an empty string is always `nil`.
155
+
156
+ The second argument is the list of things you want to typecast.
157
+ By default there is everything, but you only want to typecast
158
+ integers and floats, you can pass `[:int, :float]`.
159
+
160
+ `generate_random_id(size)`
161
+ --------------------------
162
+
163
+ Like the name suggests, it generates a random string of
164
+ only letters and numbers. If you don't provide a size,
165
+ it defaults to 16.
166
+
167
+ `nl2br(string, br="<br>")`
168
+ --------------------------
169
+
170
+ The classic `nl2br` which makes sure return lines are
171
+ turned into `<br>` tags. You can use the second argument if
172
+ you want to specify what the replacement tag should be.
173
+ Just in case you want self-closing tags.
174
+
175
+ `complete_link(string)`
176
+ -----------------------
177
+
178
+ This just makes sure that a link is complete. Very often
179
+ people tend to enter a URL like `www.google.com` which is a
180
+ controversial `href` for some browsers, so it changes it to
181
+ `//www.google.com`. Already seemingly complete links are not
182
+ affected by the method.
183
+
184
+ `external_link?(string)`
185
+ ------------------------
186
+
187
+ This tells you if a link is pointing to the current site or
188
+ an external one. This is useful when you want to create a link
189
+ tag and want to decide if target is `'_blank'` or `'_self'`.
190
+
191
+ `automatic_html(string, br="<br>")`
192
+ -----------------------------------
193
+
194
+ This automatically does `nl2br` and links recognizable things
195
+ like email addresses and URLs. Not as good as markdown, but it
196
+ is quite useful, should it be only for turning an email into a link.
197
+
198
+ `truncate(string, size=320, ellipsis="...")`
199
+ --------------------------------------------
200
+
201
+ It truncates a string like what you have in blog summaries.
202
+ It automatically removes tags and line breaks. The length is
203
+ 320 by default. When the original string was longer, it puts
204
+ an ellipsis at the end which can be replaced by whatever you put
205
+ as a 3rd argument. e.g. `'...and more'`.
206
+
207
+ `display_price(int)`
208
+ --------------------
209
+
210
+ It changes a price in cents/pence into a formated string
211
+ like `49,425.40` when you pass `4942540`.
212
+
213
+ `parse_price(string)`
214
+ ---------------------
215
+
216
+ It does the opposite of `display_price` and parses a string in
217
+ order to return a price in cents/pence.
218
+
219
+ `branded_filename(path, brand="WebUtils")`
220
+ ------------------------------------------
221
+
222
+ It takes the path to a file and add the brand/prefix and a dash
223
+ before the file name (really the file name, not the path).
224
+ By default, the brand/prefix is `WebUtils`.
225
+
226
+ `filename_variation(path, variation, ext)`
227
+ ------------------------------------------
228
+
229
+ For example you have a file `/path/to/image.jpg` and you want
230
+ to create its `thumbnail` in `png`, you can create the thumnail
231
+ path with `filename_variation(path, :thumbnail, :png)` and it
232
+ will return `/path/to/image.thumbnail.png`.
233
+
234
+ `initial_request?(request)`
235
+ ---------------------------
236
+
237
+ You basically pass the `Request` object to the method and it
238
+ looks at the referrer and returns true if it was not on the same
239
+ domain. Essentially tells you if the visitor just arrived.
240
+
241
+
242
+
data/lib/web_utils.rb ADDED
@@ -0,0 +1,232 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rack/utils'
4
+ require 'uri'
5
+
6
+ module WebUtils
7
+
8
+ # Most methods are supposed to be as simple as possible
9
+ # and just cover most cases.
10
+ # I would rather override specific cases rather than making
11
+ # complicated methods.
12
+
13
+ extend Rack::Utils
14
+
15
+ def blank? s
16
+ s.to_s.strip==''
17
+ end
18
+ module_function :blank?
19
+
20
+ def pluralize s
21
+ s<<'e' if s[-1,1]=='x'
22
+ s<<'s'
23
+ s.sub(/([b-df-hj-np-tv-z])ys$/,'\1ies')
24
+ end
25
+ module_function :pluralize
26
+
27
+ def singularize s
28
+ case s
29
+ when /xes$/
30
+ s[0..-3]
31
+ when /ies$/
32
+ s.sub(/ies$/, 'y')
33
+ when /s$/
34
+ s[0..-2]
35
+ else
36
+ s
37
+ end
38
+ end
39
+ module_function :singularize
40
+
41
+ def dasherize_class_name s
42
+ s.gsub(/([A-Z]|\d+)/){|str|"-#{str.downcase}"}[1..-1].gsub('::','-')
43
+ end
44
+ module_function :dasherize_class_name
45
+
46
+ def undasherize_class_name s
47
+ s.capitalize.gsub(/\-([a-z0-9])/){|str|$1.upcase}.gsub('-','::')
48
+ end
49
+ module_function :undasherize_class_name
50
+
51
+ def resolve_class_name s, context=Kernel
52
+ current, *payload = s.to_s.split('::')
53
+ raise(NameError) if current.nil?
54
+ const = context.const_get(current)
55
+ if payload.empty?
56
+ const
57
+ else
58
+ resolve_class_name(payload.join('::'),const)
59
+ end
60
+ end
61
+ module_function :resolve_class_name
62
+
63
+ def resolve_dasherized_class_name s
64
+ resolve_class_name(undasherize_class_name(s.to_s))
65
+ end
66
+ module_function :resolve_dasherized_class_name
67
+
68
+ def guess_related_class_name context, clue
69
+ context.respond_to?(:name) ? context.name : context.to_s
70
+ clue = clue.to_s
71
+ return clue if clue=~/^[A-Z]/
72
+ if clue=~/^[a-z]/
73
+ clue = undasherize_class_name singularize(clue).gsub('_','-')
74
+ clue = "::#{clue}"
75
+ end
76
+ "#{context}#{clue}"
77
+ end
78
+ module_function :guess_related_class_name
79
+
80
+ def get_value raw, context=Kernel
81
+ if raw.is_a? Proc
82
+ raw.call
83
+ elsif raw.is_a? Symbol
84
+ context.__send__ raw
85
+ else
86
+ raw
87
+ end
88
+ end
89
+ module_function :get_value
90
+
91
+ def deep_copy original
92
+ Marshal.load(Marshal.dump(original))
93
+ end
94
+ module_function :deep_copy
95
+
96
+ def ensure_key! h, k, v
97
+ h[k] = v unless h.key?(k)
98
+ h[k]
99
+ end
100
+ module_function :ensure_key!
101
+
102
+ def ensure_key h, k, v
103
+ new_h = h.dup
104
+ self.ensure_key! new_h, k, v
105
+ new_h
106
+ end
107
+ module_function :ensure_key
108
+
109
+ ACCENTS_FROM =
110
+ "ÀÁÂÃÄÅàáâãäåĀāĂ㥹ÇçĆćĈĉĊċČčÐðĎďĐđÈÉÊËèéêëĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħÌÍÎÏìíîïĨĩĪīĬĭĮįİıĴĵĶķĸĹĺĻļĽľĿŀŁłÑñŃńŅņŇňʼnŊŋÒÓÔÕÖØòóôõöøŌōŎŏŐőŔŕŖŗŘřŚśŜŝŞ"
111
+ ACCENTS_TO =
112
+ "AAAAAAaaaaaaAaAaAaCcCcCcCcCcDdDdDdEEEEeeeeEeEeEeEeEeGgGgGgGgHhHhIIIIiiiiIiIiIiIiIiJjKkkLlLlLlLlLlNnNnNnNnnNnOOOOOOooooooOoOoOoRrRrRrSsSsSsSssT"
113
+ def slugify s, force_lower=true
114
+ s = s.to_s.tr(ACCENTS_FROM,ACCENTS_TO).tr(' .,;:?!/\'"()[]{}<>','-').gsub(/&/, 'and').gsub(/-+/,'-').gsub(/(^-|-$)/,'')
115
+ s = s.downcase if force_lower
116
+ escape(s)
117
+ end
118
+ module_function :slugify
119
+
120
+ def label_for_field field_name
121
+ field_name.to_s.scan(/[a-zA-Z0-9]+/).map(&:capitalize).join(' ')
122
+ end
123
+ module_function :label_for_field
124
+
125
+ def each_stub obj, &block
126
+ raise TypeError, 'WebUtils.each_stub expects an object which respond to each_with_index' unless obj.respond_to?(:each_with_index)
127
+ obj.each_with_index do |(k,v),i|
128
+ value = v || k
129
+ if value.is_a?(Hash) || value.is_a?(Array)
130
+ each_stub(value,&block)
131
+ else
132
+ block.call(obj, (v.nil? ? i : k), value)
133
+ end
134
+ end
135
+ end
136
+ module_function :each_stub
137
+
138
+ def automatic_typecast str, casted=[:bool,:nil,:int,:float]
139
+ return str unless str.is_a?(String)
140
+ if casted.include?(:bool) and str=='true'
141
+ true
142
+ elsif casted.include?(:bool) and str=='false'
143
+ false
144
+ elsif casted.include?(:nil) and str==''
145
+ nil
146
+ elsif casted.include?(:int) and str=~/^-?\d+$/
147
+ str.to_i
148
+ elsif casted.include?(:float) and str=~/^-?\d*\.\d+$/
149
+ str.to_f
150
+ else
151
+ str
152
+ end
153
+ end
154
+ module_function :automatic_typecast
155
+
156
+ ID_CHARS = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
157
+ ID_SIZE = 16
158
+ def generate_random_id size=ID_SIZE
159
+ id = ''
160
+ size.times{id << ID_CHARS[rand(ID_CHARS.size)]}
161
+ id
162
+ end
163
+ module_function :generate_random_id
164
+
165
+ def nl2br s, br='<br>'
166
+ s.to_s.gsub(/\n/,br)
167
+ end
168
+ module_function :nl2br
169
+
170
+ def complete_link link
171
+ if blank?(link) or link=~/^(\/|[a-z]*:)/
172
+ link
173
+ else
174
+ "//#{link}"
175
+ end
176
+ end
177
+ module_function :complete_link
178
+
179
+ def external_link? link
180
+ !!(link =~ /^[a-z]*:?\/\//)
181
+ end
182
+ module_function :external_link?
183
+
184
+ def automatic_html s, br='<br>'
185
+ replaced = s.to_s.
186
+ gsub(/\b((https?:\/\/|ftps?:\/\/|www\.)([A-Za-z0-9\-_=%&@\?\.\/]+))\b/) do |str|
187
+ url = complete_link $1
188
+ "<a href='#{url}' target='_blank'>#{$1}</a>"
189
+ end.
190
+ gsub(/([^\s]+@[^\s]*[a-zA-Z])/) do |str|
191
+ "<a href='mailto:#{$1.downcase}'>#{$1}</a>"
192
+ end
193
+ nl2br(replaced,br).gsub("@", "&#64;")
194
+ end
195
+ module_function :automatic_html
196
+
197
+ def truncate s,c=320,ellipsis='...'
198
+ s.to_s.gsub(/<[^>]*>/, '').gsub(/\n/, ' ').sub(/^(.{#{c}}\w*).*$/m, '\1'+ellipsis)
199
+ end
200
+ module_function :truncate
201
+
202
+ def display_price int
203
+ raise(TypeError, 'The price needs to be the price in cents/pence as an integer') unless int.is_a?(Integer)
204
+ ("%.2f" % (int/100.0)).sub(/\.00/, '').reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
205
+ end
206
+ module_function :display_price
207
+
208
+ def parse_price string
209
+ raise(TypeError, 'The price needs to be parsed from a String') unless string.is_a?(String)
210
+ ("%.2f" % string.gsub(/[^\d\.\-]/, '')).gsub(/\./,'').to_i
211
+ end
212
+ module_function :parse_price
213
+
214
+ def branded_filename path, brand='WebUtils'
215
+ "#{File.dirname(path)}/#{brand}-#{File.basename(path)}".sub(/^\.\//,'')
216
+ end
217
+ module_function :branded_filename
218
+
219
+ def filename_variation path, variation, ext
220
+ old_ext = File.extname(path)
221
+ path.sub(/#{Regexp.escape old_ext}$/, ".#{variation}.#{ext}")
222
+ end
223
+ module_function :filename_variation
224
+
225
+ def initial_request? request
226
+ return true unless request.referer=~URI.regexp
227
+ URI.parse(request.referer).host!=request.host
228
+ end
229
+ module_function :initial_request?
230
+
231
+ end
232
+
@@ -0,0 +1,524 @@
1
+ require 'minitest/autorun'
2
+ require 'web_utils'
3
+
4
+ require 'rack/request'
5
+ require 'rack/mock'
6
+
7
+ module FakeModule
8
+ module FakeSubModule
9
+ end
10
+ end
11
+
12
+ describe WebUtils do
13
+
14
+ parallelize_me!
15
+
16
+ let(:utils) { WebUtils }
17
+
18
+ describe '#blank?' do
19
+ describe 'with blank strings' do
20
+ it 'is true' do
21
+ ['',' '," \n \t"].each do |s|
22
+ assert utils.blank?(s)
23
+ end
24
+ end
25
+ end
26
+ describe 'with nil' do
27
+ it('is true') { assert utils.blank?(nil) }
28
+ end
29
+ describe 'with non-blank strings' do
30
+ it 'is false' do
31
+ ['a','abc', ' abc '].each do |s|
32
+ refute utils.blank?(s)
33
+ end
34
+ end
35
+ end
36
+ describe 'with integer' do
37
+ it('is false') { refute utils.blank?(1234) }
38
+ end
39
+ end
40
+
41
+ describe '#pluralize' do
42
+
43
+ it "Just adds an 's' at the end" do
44
+ assert_equal 'bags', utils.pluralize('bag')
45
+ assert_equal 'days', utils.pluralize('day')
46
+ end
47
+ describe "The word ends with 'x'" do
48
+ it "Adds 'es' instead" do
49
+ assert_equal 'foxes', utils.pluralize('fox')
50
+ end
51
+ end
52
+ describe "The word ends with a consonant and 'y'" do
53
+ it "Replaces 'y' with 'ie'" do
54
+ assert_equal 'copies', utils.pluralize('copy')
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ describe '#singularize' do
61
+
62
+ it "Removes the trailing 's'" do
63
+ assert_equal 'bag', utils.singularize('bags')
64
+ end
65
+ describe "The word ends with 'xes'" do
66
+ it "Removes the 'e' as well" do
67
+ assert_equal 'fox', utils.singularize('foxes')
68
+ end
69
+ end
70
+ describe "The word ends with 'ies'" do
71
+ it "Replaces 'ie' with 'y'" do
72
+ assert_equal 'copy', utils.singularize('copies')
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ describe 'dasherize/undasherize class name' do
79
+
80
+ let(:cases) {
81
+ [
82
+ ['Hello','hello'],
83
+ ['HelloWorld','hello-world'],
84
+ ['HTTPRequest','h-t-t-p-request'],
85
+ ['RestAPI','rest-a-p-i'],
86
+ ['AlteredSMTPAttachment','altered-s-m-t-p-attachment'],
87
+ ['View360', 'view-360'],
88
+ ['View360Degree', 'view-360-degree'],
89
+ ['Degree360::View', 'degree-360--view'],
90
+ ['RestAPI::Request::Post','rest-a-p-i--request--post'],
91
+ ]
92
+ }
93
+
94
+ describe '#dasherize_class_name' do
95
+ it "Translates correctly" do
96
+ cases.each do |(classname,dashname)|
97
+ assert_equal dashname, utils.dasherize_class_name(classname)
98
+ end
99
+ end
100
+ end
101
+
102
+ describe '#undasherize_class_name' do
103
+ it "Translates correctly" do
104
+ cases.each do |(classname,dashname)|
105
+ assert_equal classname, utils.undasherize_class_name(dashname)
106
+ end
107
+ end
108
+ end
109
+
110
+ end
111
+
112
+ describe '#resolve_class_name' do
113
+ describe 'when the constant exists' do
114
+ it 'Returns the constant' do
115
+ [
116
+ ['String',String],
117
+ ['FakeModule::FakeSubModule',FakeModule::FakeSubModule],
118
+ ].each do |(classname,constant)|
119
+ assert_equal constant, utils.resolve_class_name(classname)
120
+ end
121
+ end
122
+ end
123
+ describe 'when the constant does not exist' do
124
+ it 'Raise if the constant does not exist' do
125
+ ['Strang','WebUtils::Yootils','',nil].each do |classname|
126
+ assert_raises(NameError) do
127
+ utils.resolve_class_name(classname)
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+ describe '#resolve_dasherized_class_name' do
135
+ it 'Chains both methods for sugar' do
136
+ assert_equal FakeModule::FakeSubModule, utils.resolve_dasherized_class_name('fake-module--fake-sub-module')
137
+ assert_equal FakeModule::FakeSubModule, utils.resolve_dasherized_class_name(:'fake-module--fake-sub-module')
138
+ end
139
+ end
140
+
141
+ describe '#guess_related_class_name' do
142
+
143
+ # Used for many things but mainly relationship classes
144
+
145
+ describe 'when it starts with a lowercase letter' do
146
+ it 'Should guess a singular class_name in the context' do
147
+ assert_equal 'FakeModule::FakeSubModule::RelatedThing', utils.guess_related_class_name(FakeModule::FakeSubModule, :related_things)
148
+ assert_equal 'FakeModule::FakeSubModule::RelatedThing', utils.guess_related_class_name(FakeModule::FakeSubModule, :related_thing)
149
+ end
150
+ end
151
+ describe 'when it starts with an uppercase letter' do
152
+ it 'Should return the class_name as-is' do
153
+ assert_equal 'Class::Given', utils.guess_related_class_name(FakeModule::FakeSubModule, 'Class::Given')
154
+ end
155
+ end
156
+ describe 'when it starts with ::' do
157
+ it 'Should prepend the class_name whith the context' do
158
+ assert_equal 'FakeModule::FakeSubModule::RelatedThing', utils.guess_related_class_name(FakeModule::FakeSubModule, '::RelatedThing')
159
+ end
160
+ end
161
+ end
162
+
163
+ describe '#get_value' do
164
+ describe 'when arg is a simple object' do
165
+ it 'Returns it as-is' do
166
+ assert_equal 'Hello', utils.get_value('Hello')
167
+ end
168
+ end
169
+ describe 'when arg is a proc' do
170
+ it 'Returns after calling the proc' do
171
+ assert_equal 'Hello', utils.get_value(proc{'Hello'})
172
+ end
173
+ end
174
+ describe 'when arg is a lambda' do
175
+ it 'Returns after calling the lambda' do
176
+ assert_equal 'Hello', utils.get_value(lambda{'Hello'})
177
+ end
178
+ end
179
+ describe 'when arg is a symbol' do
180
+ describe 'and a context is passed as a second argument' do
181
+ it 'Sends the message to the context' do
182
+ assert_equal 'Hello', utils.get_value(:capitalize,'hello')
183
+ end
184
+ end
185
+ describe 'and no context is passed' do
186
+ it 'Sends the message to Kernel' do
187
+ assert_equal 'Kernel', utils.get_value(:to_s)
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ describe '#deep_copy' do
194
+ it 'Duplicates the nested objects' do
195
+ original = {nested_hash: {one: 1}, nested_array: [1]}
196
+ copy = utils.deep_copy(original)
197
+ copy[:nested_hash][:one] = 2
198
+ copy[:nested_array] << 2
199
+ assert_equal({one: 1}, original[:nested_hash])
200
+ assert_equal [1], original[:nested_array]
201
+ end
202
+ end
203
+
204
+ describe '#ensure_key!' do
205
+ let(:arg) { {a: 3} }
206
+ it 'Sets the key if it did not exist' do
207
+ utils.ensure_key!(arg,:b,4)
208
+ assert_equal 4, arg[:b]
209
+ end
210
+ it 'Leaves the key untouched if it already existed' do
211
+ utils.ensure_key!(arg,:a,4)
212
+ assert_equal 3, arg[:a]
213
+ end
214
+ it 'Returns the value of the key' do
215
+ assert_equal 4, utils.ensure_key!(arg,:b,4)
216
+ assert_equal 3, utils.ensure_key!(arg,:a,4)
217
+ end
218
+ end
219
+
220
+ describe '#ensure_key' do
221
+ let(:arg) { {a: 3} }
222
+ it 'Does not change the original' do
223
+ new_hash = utils.ensure_key(arg,:b,4)
224
+ refute_equal 4, arg[:b]
225
+ assert_equal 4, new_hash[:b]
226
+ end
227
+ end
228
+
229
+ describe '#slugify' do
230
+
231
+ # For making slug for a document
232
+ # Possibly used instead of the id
233
+
234
+ let(:arg) { "Así es la vida by Daniel Bär & Mickaël ? (100%)" }
235
+ it 'Builds a string made of lowercase URL-friendly chars' do
236
+ assert_equal 'asi-es-la-vida-by-daniel-bar-and-mickael-100%25', utils.slugify(arg)
237
+ end
238
+ describe 'when second argument is false' do
239
+ it 'Does not force to lowercase' do
240
+ assert_equal 'Asi-es-la-vida-by-Daniel-Bar-and-Mickael-100%25', utils.slugify(arg,false)
241
+ end
242
+ end
243
+ describe 'when argument is nil' do
244
+ let(:arg) { nil }
245
+ it 'Does not break' do
246
+ assert_equal '', utils.slugify(arg)
247
+ assert_equal '', utils.slugify(arg,false)
248
+ end
249
+ end
250
+ end
251
+
252
+ describe '#label_for_field' do
253
+
254
+ # Returns a friendly name for a field name
255
+
256
+ it 'Returns an ideal title case version of the field name' do
257
+ [
258
+ ['hello', 'Hello'],
259
+ ['hello-world_1234', 'Hello World 1234'],
260
+ [:hello_world, 'Hello World'],
261
+ ].each do |(arg,result)|
262
+ assert_equal result, utils.label_for_field(arg)
263
+ end
264
+ end
265
+ end
266
+
267
+ describe '#each_stub' do
268
+
269
+ # For iterating through end objects of a nested hash/array
270
+ # It would be used for updating values, typecasting them...
271
+
272
+ it 'Yields a block for every stub of a complex object and make changes possible' do
273
+ before = {
274
+ 'name'=>"BoBBy",
275
+ 'numbers'=>['One','Two'],
276
+ 'meta'=>{'type'=>'Dev','tags'=>['Top','Bottom']}
277
+ }
278
+ after = {
279
+ 'name'=>"bobby",
280
+ 'numbers'=>['one','two'],
281
+ 'meta'=>{'type'=>'dev','tags'=>['top','bottom']}
282
+ }
283
+ utils.each_stub(before) do |object,key_index,value|
284
+ object[key_index] = value.to_s.downcase
285
+ end
286
+ assert_equal after, before
287
+ end
288
+ it 'Raises a TypeError if The object is not appropriate' do
289
+ [nil,'yo',4].each do |obj|
290
+ assert_raises(TypeError) do
291
+ utils.each_stub(obj)
292
+ end
293
+ end
294
+ end
295
+ end
296
+
297
+ describe '#automatic_typecast' do
298
+
299
+ # Tries to do automatic typecasting of values
300
+ # that are received as strings, so most likely
301
+ # coming from a web form or from a CSV table.
302
+ #
303
+ # The purpose is to use it in combination with
304
+ # #each_stub when a form is received by the API.
305
+
306
+ describe 'when a string' do
307
+ it 'Knows how to convert recognizable datatypes' do
308
+ [
309
+ ['true',true],
310
+ ['false',false],
311
+ ['',nil],
312
+ ['fack','fack'],
313
+ ['5', 5],
314
+ ['-5', -5],
315
+ ['42', 42],
316
+ ['-42', -42],
317
+ ['99.99', 99.99],
318
+ ['5.0', 5.0],
319
+ ['.4', 0.4],
320
+ ['-99.99', -99.99],
321
+ ['-5.0', -5.0],
322
+ ['-.4', -0.4],
323
+ ['42hello', '42hello'],
324
+ ['5.0hello', '5.0hello']
325
+ ].each do |(arg,result)|
326
+ assert_equal result, utils.automatic_typecast(arg)
327
+ end
328
+ end
329
+ it 'Can change what is typecasted' do
330
+ assert_equal '10', utils.automatic_typecast('10', [:bool,:nil])
331
+ assert_equal true, utils.automatic_typecast('true', [:bool,:nil])
332
+ end
333
+ end
334
+ describe 'when not a string' do
335
+ it 'Should leave it untouched' do
336
+ [Time.now,1.0].each do |obj|
337
+ assert_equal obj, utils.automatic_typecast(obj)
338
+ end
339
+ end
340
+ end
341
+ end
342
+
343
+ describe '#generate_random_id' do
344
+ it 'Has the correct format' do
345
+ assert_match /[a-zA-Z0-9]{16}/, utils.generate_random_id
346
+ end
347
+ it 'Can have a specific length' do
348
+ assert_match /[a-zA-Z0-9]{32}/, utils.generate_random_id(32)
349
+ end
350
+ end
351
+
352
+ describe '#nl2br' do
353
+ it 'Puts unclosed tags by default' do
354
+ assert_equal '<br>Hello<br>world<br>', utils.nl2br("\nHello\nworld\n")
355
+ end
356
+ describe 'with 2nd argument' do
357
+ it 'Replaces the tag' do
358
+ assert_equal '<br/>Hello<br/>world<br/>', utils.nl2br("\nHello\nworld\n",'<br/>')
359
+ end
360
+ end
361
+ end
362
+
363
+ describe '#complete_link' do
364
+ it 'Adds the external double slash when missing' do
365
+ [
366
+ ['www.web-utils.com','//www.web-utils.com'],
367
+ ['web-utils.com','//web-utils.com'],
368
+ ['please.web-utils.com','//please.web-utils.com'],
369
+ ].each do |(arg,result)|
370
+ assert_equal result, utils.complete_link(arg)
371
+ end
372
+ end
373
+ it 'Does not alter the url when it does not need double slash' do
374
+ [
375
+ ['//www.web-utils.com','//www.web-utils.com'],
376
+ ['://www.web-utils.com','://www.web-utils.com'],
377
+ ['http://www.web-utils.com','http://www.web-utils.com'],
378
+ ['ftp://www.web-utils.com','ftp://www.web-utils.com'],
379
+ ['mailto:web&#64;utils.com','mailto:web&#64;utils.com'],
380
+ ['',''],
381
+ [' ',' '],
382
+ ].each do |(arg,result)|
383
+ assert_equal result, utils.complete_link(arg)
384
+ end
385
+ end
386
+ end
387
+
388
+ describe '#external_link?' do
389
+ it 'Returns true when the link would need target=blank' do
390
+ [
391
+ ['http://web-utils.com', true],
392
+ ['https://web-utils.com', true],
393
+ ['ftp://web-utils.com', true],
394
+ ['://web-utils.com', true],
395
+ ['//web-utils.com', true],
396
+ ['mailto:user@web-utils.com', false],
397
+ ['mailto:user&#64;web-utils.com', false],
398
+ ['/web/utils', false],
399
+ ['web-utils.html', false],
400
+ ].each do |(url,bool)|
401
+ assert_equal bool, utils.external_link?(url)
402
+ end
403
+ end
404
+ end
405
+
406
+ describe '#automatic_html' do
407
+ it 'Automates links and line breaks' do
408
+ input = "Hello\nme@site.co.uk\nNot the begining me@site.co.uk\nme@site.co.uk not the end\nwww.site.co.uk\nVisit www.site.co.uk\nwww.site.co.uk rules\nhttp://www.site.co.uk\nVisit http://www.site.co.uk\nhttp://www.site.co.uk rules"
409
+ output = "Hello<br><a href='mailto:me&#64;site.co.uk'>me&#64;site.co.uk</a><br>Not the begining <a href='mailto:me&#64;site.co.uk'>me&#64;site.co.uk</a><br><a href='mailto:me&#64;site.co.uk'>me&#64;site.co.uk</a> not the end<br><a href='//www.site.co.uk' target='_blank'>www.site.co.uk</a><br>Visit <a href='//www.site.co.uk' target='_blank'>www.site.co.uk</a><br><a href='//www.site.co.uk' target='_blank'>www.site.co.uk</a> rules<br><a href='http://www.site.co.uk' target='_blank'>http://www.site.co.uk</a><br>Visit <a href='http://www.site.co.uk' target='_blank'>http://www.site.co.uk</a><br><a href='http://www.site.co.uk' target='_blank'>http://www.site.co.uk</a> rules"
410
+ assert_equal output, utils.automatic_html(input)
411
+ end
412
+ end
413
+
414
+ describe '#truncate' do
415
+ it 'Truncates to the right amount of letters' do
416
+ assert_equal 'abc...', utils.truncate('abc defg hijklmnopqrstuvwxyz',3)
417
+ end
418
+ it 'Does not cut words' do
419
+ assert_equal 'abcdefg...', utils.truncate('abcdefg hijklmnopqrstuvwxyz',3)
420
+ end
421
+ it 'Removes HTML tags' do
422
+ assert_equal 'abcdefg...', utils.truncate('<br>abc<a href=#>def</a>g hijklmnopqrstuvwxyz',3)
423
+ end
424
+ it 'Does not print the ellipsis if the string is already short enough' do
425
+ assert_equal 'abc def', utils.truncate('abc def',50)
426
+ end
427
+ describe 'with a 3rd argument' do
428
+ it 'Replaces the ellipsis' do
429
+ assert_equal 'abc!', utils.truncate('abc defg hijklmnopqrstuvwxyz',3,'!')
430
+ assert_equal 'abc', utils.truncate('abc defg hijklmnopqrstuvwxyz',3,'')
431
+ end
432
+ end
433
+ end
434
+
435
+ describe '#display_price' do
436
+ it 'Turns a price number in cents/pence into a displayable one' do
437
+ assert_equal '45.95', utils.display_price(4595)
438
+ end
439
+ it 'Removes cents if it is 00' do
440
+ assert_equal '70', utils.display_price(7000)
441
+ end
442
+ it 'Adds comma delimiters on thousands' do
443
+ assert_equal '12,345,678.90', utils.display_price(1234567890)
444
+ end
445
+ it 'Works with negative numbers' do
446
+ assert_equal '-1,400', utils.display_price(-140000)
447
+ end
448
+ it 'Raises when argument is not int' do
449
+ assert_raises(TypeError) do
450
+ utils.display_price('abc')
451
+ end
452
+ end
453
+ end
454
+
455
+ describe '#parse_price' do
456
+ it 'Parses a string and find the price in cents/pence' do
457
+ assert_equal 4595, utils.parse_price('45.95')
458
+ end
459
+ it 'Works when you omit the cents/pence' do
460
+ assert_equal 2800, utils.parse_price('28')
461
+ end
462
+ it 'Ignores visual help but works with negative prices' do
463
+ assert_equal -1234567890, utils.parse_price(' £-12,345,678.90 ')
464
+ end
465
+ it 'Raises when argument is not string' do
466
+ assert_raises(TypeError) do
467
+ utils.parse_price(42)
468
+ end
469
+ end
470
+ end
471
+
472
+ describe '#branded_filename' do
473
+ it 'Adds WebUtils to the file name' do
474
+ assert_equal "/path/to/WebUtils-file.png", utils.branded_filename("/path/to/file.png")
475
+ end
476
+ it 'Works when there is just a file name' do
477
+ assert_equal "WebUtils-file.png", utils.branded_filename("file.png")
478
+ end
479
+ it 'Can change the brand' do
480
+ assert_equal "/path/to/Brand-file.png", utils.branded_filename("/path/to/file.png",'Brand')
481
+ end
482
+ end
483
+
484
+ describe '#filename_variation' do
485
+ it 'Replaces the ext with variation name and new ext' do
486
+ assert_equal "/path/to/file.thumb.gif", utils.filename_variation("/path/to/file.png", :thumb, :gif)
487
+ end
488
+ it 'Works when there is just a filename' do
489
+ assert_equal "file.thumb.gif", utils.filename_variation("file.png", :thumb, :gif)
490
+ end
491
+ it "Works when there is no ext to start with" do
492
+ assert_equal "/path/to/file.thumb.gif", utils.filename_variation("/path/to/file", :thumb, :gif)
493
+ end
494
+ end
495
+
496
+ describe '#initial_request?' do
497
+ let(:req) {
498
+ Rack::Request.new(
499
+ Rack::MockRequest.env_for(
500
+ '/path',
501
+ {'HTTP_REFERER'=>referer}
502
+ )
503
+ )
504
+ }
505
+ let(:referer) { nil }
506
+ it 'Returns true' do
507
+ assert utils.initial_request?(req)
508
+ end
509
+ describe 'Request comes from another domain' do
510
+ let(:referer) { 'https://www.google.com/path' }
511
+ it 'Returns true' do
512
+ assert utils.initial_request?(req)
513
+ end
514
+ end
515
+ describe 'Request comes from same domain' do
516
+ let(:referer) { 'http://example.org' }
517
+ it 'Returns false' do
518
+ refute utils.initial_request?(req)
519
+ end
520
+ end
521
+ end
522
+
523
+ end
524
+
data/web-utils.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ Gem::Specification.new do |s|
2
+
3
+ s.authors = ["Mickael Riga"]
4
+ s.email = ["mig@mypeplum.com"]
5
+ s.homepage = "https://github.com/mig-hub/web-utils"
6
+ s.licenses = ['MIT']
7
+
8
+ s.name = 'web-utils'
9
+ s.version = '0.0.1'
10
+ s.summary = "Web Utils"
11
+ s.description = "Useful web-related helper methods for models, views or controllers."
12
+
13
+ s.platform = Gem::Platform::RUBY
14
+ s.files = `git ls-files`.split("\n").sort
15
+ s.test_files = s.files.grep(/^test\//)
16
+ s.require_paths = ['lib']
17
+ s.add_dependency('rack', '~> 0')
18
+
19
+ end
20
+
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: web-utils
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Mickael Riga
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Useful web-related helper methods for models, views or controllers.
28
+ email:
29
+ - mig@mypeplum.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".gitignore"
35
+ - Gemfile
36
+ - LICENSE
37
+ - README.md
38
+ - lib/web_utils.rb
39
+ - test/test_web_utils.rb
40
+ - web-utils.gemspec
41
+ homepage: https://github.com/mig-hub/web-utils
42
+ licenses:
43
+ - MIT
44
+ metadata: {}
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 2.5.1
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Web Utils
65
+ test_files:
66
+ - test/test_web_utils.rb