ftbpro_sitemap_generator 5.0.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.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +13 -0
  3. data/Gemfile.lock +35 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +1139 -0
  6. data/Rakefile +43 -0
  7. data/VERSION +1 -0
  8. data/lib/capistrano/sitemap_generator.rb +1 -0
  9. data/lib/capistrano/tasks/sitemap_generator.cap +36 -0
  10. data/lib/sitemap_generator.rb +85 -0
  11. data/lib/sitemap_generator/adapters.rb +0 -0
  12. data/lib/sitemap_generator/adapters/file_adapter.rb +43 -0
  13. data/lib/sitemap_generator/adapters/fog_adapter.rb +28 -0
  14. data/lib/sitemap_generator/adapters/s3_adapter.rb +41 -0
  15. data/lib/sitemap_generator/adapters/wave_adapter.rb +21 -0
  16. data/lib/sitemap_generator/application.rb +49 -0
  17. data/lib/sitemap_generator/builder.rb +8 -0
  18. data/lib/sitemap_generator/builder/sitemap_file.rb +172 -0
  19. data/lib/sitemap_generator/builder/sitemap_index_file.rb +149 -0
  20. data/lib/sitemap_generator/builder/sitemap_index_url.rb +28 -0
  21. data/lib/sitemap_generator/builder/sitemap_url.rb +250 -0
  22. data/lib/sitemap_generator/core_ext.rb +3 -0
  23. data/lib/sitemap_generator/core_ext/big_decimal.rb +45 -0
  24. data/lib/sitemap_generator/core_ext/numeric.rb +48 -0
  25. data/lib/sitemap_generator/helpers/number_helper.rb +237 -0
  26. data/lib/sitemap_generator/interpreter.rb +80 -0
  27. data/lib/sitemap_generator/link_set.rb +677 -0
  28. data/lib/sitemap_generator/railtie.rb +7 -0
  29. data/lib/sitemap_generator/sitemap_location.rb +192 -0
  30. data/lib/sitemap_generator/sitemap_namer.rb +75 -0
  31. data/lib/sitemap_generator/tasks.rb +53 -0
  32. data/lib/sitemap_generator/templates.rb +41 -0
  33. data/lib/sitemap_generator/utilities.rb +181 -0
  34. data/lib/tasks/sitemap_generator_tasks.rake +1 -0
  35. data/rails/install.rb +2 -0
  36. data/rails/uninstall.rb +2 -0
  37. data/spec/blueprint.rb +15 -0
  38. data/spec/files/sitemap.create.rb +12 -0
  39. data/spec/files/sitemap.groups.rb +49 -0
  40. data/spec/sitemap_generator/adapters/s3_adapter_spec.rb +23 -0
  41. data/spec/sitemap_generator/alternate_sitemap_spec.rb +79 -0
  42. data/spec/sitemap_generator/application_spec.rb +69 -0
  43. data/spec/sitemap_generator/builder/sitemap_file_spec.rb +110 -0
  44. data/spec/sitemap_generator/builder/sitemap_index_file_spec.rb +124 -0
  45. data/spec/sitemap_generator/builder/sitemap_index_url_spec.rb +28 -0
  46. data/spec/sitemap_generator/builder/sitemap_url_spec.rb +186 -0
  47. data/spec/sitemap_generator/core_ext/bigdecimal_spec.rb +20 -0
  48. data/spec/sitemap_generator/core_ext/numeric_spec.rb +43 -0
  49. data/spec/sitemap_generator/file_adaptor_spec.rb +20 -0
  50. data/spec/sitemap_generator/geo_sitemap_spec.rb +30 -0
  51. data/spec/sitemap_generator/helpers/number_helper_spec.rb +196 -0
  52. data/spec/sitemap_generator/interpreter_spec.rb +90 -0
  53. data/spec/sitemap_generator/link_set_spec.rb +864 -0
  54. data/spec/sitemap_generator/mobile_sitemap_spec.rb +27 -0
  55. data/spec/sitemap_generator/news_sitemap_spec.rb +42 -0
  56. data/spec/sitemap_generator/pagemap_sitemap_spec.rb +57 -0
  57. data/spec/sitemap_generator/sitemap_generator_spec.rb +582 -0
  58. data/spec/sitemap_generator/sitemap_groups_spec.rb +144 -0
  59. data/spec/sitemap_generator/sitemap_location_spec.rb +210 -0
  60. data/spec/sitemap_generator/sitemap_namer_spec.rb +96 -0
  61. data/spec/sitemap_generator/templates_spec.rb +24 -0
  62. data/spec/sitemap_generator/utilities/existence_spec.rb +26 -0
  63. data/spec/sitemap_generator/utilities/hash_spec.rb +57 -0
  64. data/spec/sitemap_generator/utilities/rounding_spec.rb +31 -0
  65. data/spec/sitemap_generator/utilities_spec.rb +101 -0
  66. data/spec/sitemap_generator/video_sitemap_spec.rb +117 -0
  67. data/spec/spec_helper.rb +24 -0
  68. data/spec/support/file_macros.rb +39 -0
  69. data/spec/support/schemas/siteindex.xsd +73 -0
  70. data/spec/support/schemas/sitemap-geo.xsd +41 -0
  71. data/spec/support/schemas/sitemap-mobile.xsd +32 -0
  72. data/spec/support/schemas/sitemap-news.xsd +159 -0
  73. data/spec/support/schemas/sitemap-pagemap.xsd +97 -0
  74. data/spec/support/schemas/sitemap-video.xsd +643 -0
  75. data/spec/support/schemas/sitemap.xsd +115 -0
  76. data/spec/support/xml_macros.rb +67 -0
  77. data/templates/sitemap.rb +27 -0
  78. metadata +226 -0
@@ -0,0 +1,7 @@
1
+ module SitemapGenerator
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ require File.expand_path('../tasks', __FILE__)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,192 @@
1
+ require 'sitemap_generator/helpers/number_helper'
2
+
3
+ module SitemapGenerator
4
+ # A class for determining the exact location at which to write sitemap data.
5
+ # Handles reserving filenames from namers, constructing paths and sending
6
+ # data to the adapter to be written out.
7
+ class SitemapLocation < Hash
8
+ include SitemapGenerator::Helpers::NumberHelper
9
+
10
+ PATH_OUTPUT_WIDTH = 47 # Character width of the path in the summary lines
11
+
12
+ [:host, :adapter].each do |method|
13
+ define_method(method) do
14
+ raise SitemapGenerator::SitemapError, "No value set for #{method}" unless self[method]
15
+ self[method]
16
+ end
17
+ end
18
+
19
+ [:public_path, :sitemaps_path].each do |method|
20
+ define_method(method) do
21
+ Pathname.new(SitemapGenerator::Utilities.append_slash(self[method]))
22
+ end
23
+ end
24
+
25
+ # If no +filename+ or +namer+ is provided, the default namer is used, which
26
+ # generates names like <tt>sitemap.xml.gz</tt>, <tt>sitemap1.xml.gz</tt>, <tt>sitemap2.xml.gz</tt> and so on.
27
+ #
28
+ # === Options
29
+ # * <tt>:adapter</tt> - SitemapGenerator::Adapter subclass
30
+ # * <tt>:filename</tt> - full name of the file e.g. <tt>'sitemap1.xml.gz'<tt>
31
+ # * <tt>:host</tt> - host name for URLs. The full URL to the file is then constructed from
32
+ # the <tt>host</tt>, <tt>sitemaps_path</tt> and <tt>filename</tt>
33
+ # * <tt>:namer</tt> - a SitemapGenerator::SimpleNamer instance for generating file names.
34
+ # Should be passed if no +filename+ is provided.
35
+ # * <tt>:public_path</tt> - path to the "public" directory, or the directory you want to
36
+ # write sitemaps in. Default is a directory <tt>public/</tt>
37
+ # in the current working directory, or relative to the Rails root
38
+ # directory if running under Rails.
39
+ # * <tt>:sitemaps_path</tt> - gives the path relative to the <tt>public_path</tt> in which to
40
+ # write sitemaps e.g. <tt>sitemaps/</tt>.
41
+ # * <tt>:verbose</tt> - whether to output summary into to STDOUT. Default +false+.
42
+ # * <tt>:create_index</tt> - whether to create a sitemap index. Default `:auto`. See <tt>LinkSet::create_index=</tt>
43
+ # for possible values. Only applies to the SitemapIndexLocation object.
44
+ # * <tt>compress</tt> - The LinkSet compress setting. Default: true. If `false` any `.gz` extension is
45
+ # stripped from the filename. If `:all_but_first`, only the `.gz` extension of the first
46
+ # filename is stripped off. If `true` the extensions are left unchanged.
47
+ def initialize(opts={})
48
+ SitemapGenerator::Utilities.assert_valid_keys(opts, [:adapter, :public_path, :sitemaps_path, :host, :filename, :namer, :verbose, :create_index, :compress])
49
+ opts[:adapter] ||= SitemapGenerator::FileAdapter.new
50
+ opts[:public_path] ||= SitemapGenerator.app.root + 'public/'
51
+ # This is a bit of a hack to make the SimpleNamer act like the old SitemapNamer.
52
+ # It doesn't really make sense to create a default namer like this because the
53
+ # namer instance should be shared by the location objects of the sitemaps and
54
+ # sitemap index files. However, this greatly eases testing, so I'm leaving it in
55
+ # for now.
56
+ if !opts[:filename] && !opts[:namer]
57
+ opts[:namer] = SitemapGenerator::SimpleNamer.new(:sitemap, :start => 2, :zero => 1)
58
+ end
59
+ opts[:verbose] = !!opts[:verbose]
60
+ self.merge!(opts)
61
+ end
62
+
63
+ # Return a new Location instance with the given options merged in
64
+ def with(opts={})
65
+ self.merge(opts)
66
+ end
67
+
68
+ # Full path to the directory of the file.
69
+ def directory
70
+ (public_path + sitemaps_path).expand_path.to_s
71
+ end
72
+
73
+ # Full path of the file including the filename.
74
+ def path
75
+ (public_path + sitemaps_path + filename).expand_path.to_s
76
+ end
77
+
78
+ # Relative path of the file (including the filename) relative to <tt>public_path</tt>
79
+ def path_in_public
80
+ (sitemaps_path + filename).to_s
81
+ end
82
+
83
+ # Full URL of the file.
84
+ def url
85
+ URI.join(host, sitemaps_path.to_s, filename.to_s).to_s
86
+ end
87
+
88
+ # Return the size of the file at <tt>path</tt>
89
+ def filesize
90
+ File.size?(path)
91
+ end
92
+
93
+ # Return the filename. Raises an exception if no filename or namer is set.
94
+ # If using a namer once the filename has been retrieved from the namer its
95
+ # value is locked so that it is unaffected by further changes to the namer.
96
+ def filename
97
+ raise SitemapGenerator::SitemapError, "No filename or namer set" unless self[:filename] || self[:namer]
98
+ unless self[:filename]
99
+ self.send(:[]=, :filename, self[:namer].to_s, :super => true)
100
+
101
+ # Post-process the filename for our compression settings.
102
+ # Strip the `.gz` from the extension if we aren't compressing this file.
103
+ # If you're setting the filename manually, :all_but_first won't work as
104
+ # expected. Ultimately I should force using a namer in all circumstances.
105
+ # Changing the filename here will affect how the FileAdapter writes out the file.
106
+ if self[:compress] == false ||
107
+ (self[:namer] && self[:namer].start? && self[:compress] == :all_but_first)
108
+ self[:filename].gsub!(/\.gz$/, '')
109
+ end
110
+ end
111
+ self[:filename]
112
+ end
113
+
114
+ # If a namer is set, reserve the filename and increment the namer.
115
+ # Returns the reserved name.
116
+ def reserve_name
117
+ if self[:namer]
118
+ filename
119
+ self[:namer].next
120
+ end
121
+ self[:filename]
122
+ end
123
+
124
+ # Return true if this location has a fixed filename. If no name has been
125
+ # reserved from the namer, for instance, returns false.
126
+ def reserved_name?
127
+ !!self[:filename]
128
+ end
129
+
130
+ def namer
131
+ self[:namer]
132
+ end
133
+
134
+ def verbose?
135
+ self[:verbose]
136
+ end
137
+
138
+ # If you set the filename, clear the namer and vice versa.
139
+ def []=(key, value, opts={})
140
+ if !opts[:super]
141
+ case key
142
+ when :namer
143
+ super(:filename, nil)
144
+ when :filename
145
+ super(:namer, nil)
146
+ end
147
+ end
148
+ super(key, value)
149
+ end
150
+
151
+ # Write `data` out to a file.
152
+ # Output a summary line if verbose is true.
153
+ def write(data, link_count)
154
+ adapter.write(self, data)
155
+ puts summary(link_count) if verbose?
156
+ end
157
+
158
+ # Return a summary string
159
+ def summary(link_count)
160
+ filesize = number_to_human_size(self.filesize)
161
+ width = self.class::PATH_OUTPUT_WIDTH
162
+ path = SitemapGenerator::Utilities.ellipsis(self.path_in_public, width)
163
+ "+ #{('%-'+width.to_s+'s') % path} #{'%10s' % link_count} links / #{'%10s' % filesize}"
164
+ end
165
+ end
166
+
167
+ class SitemapIndexLocation < SitemapLocation
168
+ def initialize(opts={})
169
+ if !opts[:filename] && !opts[:namer]
170
+ opts[:namer] = SitemapGenerator::SimpleNamer.new(:sitemap)
171
+ end
172
+ super(opts)
173
+ end
174
+
175
+ # Whether to create a sitemap index. Default `:auto`. See <tt>LinkSet::create_index=</tt>
176
+ # for possible values.
177
+ #
178
+ # A placeholder for an option which should really go into some
179
+ # kind of options class.
180
+ def create_index
181
+ self[:create_index]
182
+ end
183
+
184
+ # Return a summary string
185
+ def summary(link_count)
186
+ filesize = number_to_human_size(self.filesize)
187
+ width = self.class::PATH_OUTPUT_WIDTH - 3
188
+ path = SitemapGenerator::Utilities.ellipsis(self.path_in_public, width)
189
+ "+ #{('%-'+width.to_s+'s') % path} #{'%10s' % link_count} sitemaps / #{'%10s' % filesize}"
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,75 @@
1
+ module SitemapGenerator
2
+ # A class for generating sitemap filenames.
3
+ #
4
+ # The SimpleNamer uses the same namer instance for the sitemap index and the sitemaps.
5
+ # If no index is needed, the first sitemap gets the first name. However, if
6
+ # an index is needed, the index gets the first name.
7
+ #
8
+ # A typical sequence would looks like this:
9
+ # * sitemap.xml.gz
10
+ # * sitemap1.xml.gz
11
+ # * sitemap2.xml.gz
12
+ # * sitemap3.xml.gz
13
+ # * ...
14
+ #
15
+ # Arguments:
16
+ # base - string or symbol that forms the base of the generated filename e.g.
17
+ # if `:geo` files are generated like `geo.xml.gz`, `geo1.xml.gz`, `geo2.xml.gz` etc.
18
+ #
19
+ # Options:
20
+ # :extension - Default: '.xml.gz'. File extension to append.
21
+ # :start - Default: 1. Numerical index at which to start counting.
22
+ # :zero - Default: nil. A string or number that is appended to +base+
23
+ # to create the first name in the sequence. So setting this
24
+ # to '_index' would produce 'sitemap_index.xml.gz' as
25
+ # the first name. Thereafter, the numerical index defined by +start+
26
+ # is used, and subsequent names would be 'sitemap1.xml.gz', 'sitemap2.xml.gz', etc.
27
+ # In these examples the `base` string is assumed to be 'sitemap'.
28
+ class SimpleNamer
29
+ def initialize(base, options={})
30
+ @options = SitemapGenerator::Utilities.reverse_merge(options,
31
+ :zero => nil, # identifies the marker for the start of the series
32
+ :extension => '.xml.gz',
33
+ :start => 1
34
+ )
35
+ @base = base
36
+ reset
37
+ end
38
+
39
+ def to_s
40
+ extension = @options[:extension]
41
+ "#{@base}#{@count}#{extension}"
42
+ end
43
+
44
+ # Reset to the first name
45
+ def reset
46
+ @count = @options[:zero]
47
+ end
48
+
49
+ # True if on the first name
50
+ def start?
51
+ @count == @options[:zero]
52
+ end
53
+
54
+ # Return this instance set to the next name
55
+ def next
56
+ if start?
57
+ @count = @options[:start]
58
+ else
59
+ @count += 1
60
+ end
61
+ self
62
+ end
63
+
64
+ # Return this instance set to the previous name
65
+ def previous
66
+ raise NameError, "Already at the start of the series" if start?
67
+ if @count <= @options[:start]
68
+ @count = @options[:zero]
69
+ else
70
+ @count -= 1
71
+ end
72
+ self
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,53 @@
1
+ # require this file to load the tasks
2
+ require 'rake'
3
+
4
+ # Require sitemap_generator at runtime. If we don't do this the ActionView helpers are included
5
+ # before the Rails environment can be loaded by other Rake tasks, which causes problems
6
+ # for those tasks when rendering using ActionView.
7
+ namespace :sitemap do
8
+ # Require sitemap_generator only. When installed as a plugin the require will fail, so in
9
+ # that case, load the environment first.
10
+ task :require do
11
+ begin
12
+ require 'sitemap_generator'
13
+ rescue LoadError => e
14
+ if defined?(Rails)
15
+ Rake::Task['sitemap:require_environment'].invoke
16
+ else
17
+ raise e
18
+ end
19
+ end
20
+ end
21
+
22
+ # Require sitemap_generator after loading the Rails environment. We still need the require
23
+ # in case we are installed as a gem and are setup to not automatically be required.
24
+ task :require_environment do
25
+ if defined?(Rails)
26
+ Rake::Task['environment'].invoke
27
+ end
28
+ require 'sitemap_generator'
29
+ end
30
+
31
+ desc "Install a default config/sitemap.rb file"
32
+ task :install => ['sitemap:require'] do
33
+ SitemapGenerator::Utilities.install_sitemap_rb(verbose)
34
+ end
35
+
36
+ desc "Delete all Sitemap files in public/ directory"
37
+ task :clean => ['sitemap:require'] do
38
+ SitemapGenerator::Utilities.clean_files
39
+ end
40
+
41
+ desc "Generate sitemaps and ping search engines."
42
+ task :refresh => ['sitemap:create'] do
43
+ SitemapGenerator::Sitemap.ping_search_engines
44
+ end
45
+
46
+ desc "Generate sitemaps but don't ping search engines."
47
+ task 'refresh:no_ping' => ['sitemap:create']
48
+
49
+ desc "Generate sitemaps but don't ping search engines. Alias for refresh:no_ping."
50
+ task :create => ['sitemap:require_environment'] do
51
+ SitemapGenerator::Interpreter.run(:config_file => ENV["CONFIG_FILE"], :verbose => verbose)
52
+ end
53
+ end
@@ -0,0 +1,41 @@
1
+ module SitemapGenerator
2
+ # Provide convenient access to template files. E.g.
3
+ #
4
+ # SitemapGenerator.templates.sitemap_index
5
+ #
6
+ # Lazy-load and cache for efficient access.
7
+ # Define an accessor method for each template file.
8
+ class Templates
9
+ FILES = {
10
+ :sitemap_sample => 'sitemap.rb',
11
+ }
12
+
13
+ # Dynamically define accessors for each key defined in <tt>FILES</tt>
14
+ attr_accessor *FILES.keys
15
+ FILES.keys.each do |name|
16
+ eval <<-END
17
+ define_method(:#{name}) do
18
+ @#{name} ||= read_template(:#{name})
19
+ end
20
+ END
21
+ end
22
+
23
+ def initialize(root = SitemapGenerator.root)
24
+ @root = root
25
+ end
26
+
27
+ # Return the full path to a template.
28
+ #
29
+ # <tt>file</tt> template symbol e.g. <tt>:sitemap_sample</tt>
30
+ def template_path(template)
31
+ File.join(@root, 'templates', self.class::FILES[template])
32
+ end
33
+
34
+ protected
35
+
36
+ # Read the template file and return its contents.
37
+ def read_template(template)
38
+ File.read(template_path(template))
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,181 @@
1
+ module SitemapGenerator
2
+ module Utilities
3
+ extend self
4
+
5
+ # Copy templates/sitemap.rb to config if not there yet.
6
+ def install_sitemap_rb(verbose=false)
7
+ if File.exist?(SitemapGenerator.app.root + 'config/sitemap.rb')
8
+ puts "already exists: config/sitemap.rb, file not copied" if verbose
9
+ else
10
+ FileUtils.cp(
11
+ SitemapGenerator.templates.template_path(:sitemap_sample),
12
+ SitemapGenerator.app.root + 'config/sitemap.rb')
13
+ puts "created: config/sitemap.rb" if verbose
14
+ end
15
+ end
16
+
17
+ # Remove config/sitemap.rb if exists.
18
+ def uninstall_sitemap_rb
19
+ if File.exist?(SitemapGenerator.app.root + 'config/sitemap.rb')
20
+ File.rm(SitemapGenerator.app.root + 'config/sitemap.rb')
21
+ end
22
+ end
23
+
24
+ # Clean sitemap files in output directory.
25
+ def clean_files
26
+ FileUtils.rm(Dir[SitemapGenerator.app.root + 'public/sitemap*.xml.gz'])
27
+ end
28
+
29
+ # Validate all keys in a hash match *valid keys, raising ArgumentError on a
30
+ # mismatch. Note that keys are NOT treated indifferently, meaning if you use
31
+ # strings for keys but assert symbols as keys, this will fail.
32
+ def assert_valid_keys(hash, *valid_keys)
33
+ unknown_keys = hash.keys - [valid_keys].flatten
34
+ raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
35
+ end
36
+
37
+ # Return a new hash with all keys converted to symbols, as long as
38
+ # they respond to +to_sym+.
39
+ def symbolize_keys(hash)
40
+ symbolize_keys!(hash.dup)
41
+ end
42
+
43
+ # Destructively convert all keys to symbols, as long as they respond
44
+ # to +to_sym+.
45
+ def symbolize_keys!(hash)
46
+ hash.keys.each do |key|
47
+ hash[(key.to_sym rescue key) || key] = hash.delete(key)
48
+ end
49
+ hash
50
+ end
51
+
52
+ # Make a list of `value` if it is not a list already. If `value` is
53
+ # nil, an empty list is returned. If `value` is already a list, return it unchanged.
54
+ def as_array(value)
55
+ if value.nil?
56
+ []
57
+ elsif value.is_a?(Array)
58
+ value
59
+ else
60
+ [value]
61
+ end
62
+ end
63
+
64
+ # Rounds the float with the specified precision.
65
+ #
66
+ # x = 1.337
67
+ # x.round # => 1
68
+ # x.round(1) # => 1.3
69
+ # x.round(2) # => 1.34
70
+ def round(float, precision = nil)
71
+ if precision
72
+ magnitude = 10.0 ** precision
73
+ (float * magnitude).round / magnitude
74
+ else
75
+ float.round
76
+ end
77
+ end
78
+
79
+ # Allows for reverse merging two hashes where the keys in the calling hash take precedence over those
80
+ # in the <tt>other_hash</tt>. This is particularly useful for initializing an option hash with default values:
81
+ #
82
+ # def setup(options = {})
83
+ # options.reverse_merge! :size => 25, :velocity => 10
84
+ # end
85
+ #
86
+ # Using <tt>merge</tt>, the above example would look as follows:
87
+ #
88
+ # def setup(options = {})
89
+ # { :size => 25, :velocity => 10 }.merge(options)
90
+ # end
91
+ #
92
+ # The default <tt>:size</tt> and <tt>:velocity</tt> are only set if the +options+ hash passed in doesn't already
93
+ # have the respective key.
94
+ def reverse_merge(hash, other_hash)
95
+ other_hash.merge(hash)
96
+ end
97
+
98
+ # Performs the opposite of <tt>merge</tt>, with the keys and values from the first hash taking precedence over the second.
99
+ # Modifies the receiver in place.
100
+ def reverse_merge!(hash, other_hash)
101
+ hash.merge!( other_hash ){|k,o,n| o }
102
+ end
103
+
104
+ # An object is blank if it's false, empty, or a whitespace string.
105
+ # For example, "", " ", +nil+, [], and {} are blank.
106
+ #
107
+ # This simplifies:
108
+ #
109
+ # if !address.nil? && !address.empty?
110
+ #
111
+ # ...to:
112
+ #
113
+ # if !address.blank?
114
+ def blank?(object)
115
+ case object
116
+ when NilClass, FalseClass
117
+ true
118
+ when TrueClass, Numeric
119
+ false
120
+ when String
121
+ object !~ /\S/
122
+ when Hash, Array
123
+ object.empty?
124
+ when Object
125
+ object.respond_to?(:empty?) ? object.empty? : !object
126
+ end
127
+ end
128
+
129
+ # An object is present if it's not blank.
130
+ def present?(object)
131
+ !blank?(object)
132
+ end
133
+
134
+ # Sets $VERBOSE for the duration of the block and back to its original value afterwards.
135
+ def with_warnings(flag)
136
+ old_verbose, $VERBOSE = $VERBOSE, flag
137
+ yield
138
+ ensure
139
+ $VERBOSE = old_verbose
140
+ end
141
+
142
+ def titleize(string)
143
+ string.gsub!(/_/, ' ')
144
+ string.split(/(\W)/).map(&:capitalize).join
145
+ end
146
+
147
+ def truthy?(value)
148
+ ['1', 1, 't', 'true', true].include?(value)
149
+ end
150
+
151
+ def falsy?(value)
152
+ ['0', 0, 'f', 'false', false].include?(value)
153
+ end
154
+
155
+ # Append a slash to `path` if it does not already end in a slash.
156
+ # Returns a string. Expects a string or Pathname object.
157
+ def append_slash(path)
158
+ strpath = path.to_s
159
+ if strpath[-1] != nil && strpath[-1].chr != '/'
160
+ strpath + '/'
161
+ else
162
+ strpath
163
+ end
164
+ end
165
+
166
+ # Replace the last 3 characters of string with ... if the string is as big
167
+ # or bigger than max.
168
+ def ellipsis(string, max)
169
+ if string.size > max
170
+ (string[0, max - 3] || '') + '...'
171
+ else
172
+ string
173
+ end
174
+ end
175
+
176
+ # Return the bytesize length of the string. Ruby 1.8.6 compatible.
177
+ def bytesize(string)
178
+ string.respond_to?(:bytesize) ? string.bytesize : string.length
179
+ end
180
+ end
181
+ end