ftbpro_sitemap_generator 5.0.8

Sign up to get free protection for your applications and to get access to all the features.
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,149 @@
1
+ module SitemapGenerator
2
+ module Builder
3
+ class SitemapIndexFile < SitemapFile
4
+
5
+ # === Options
6
+ #
7
+ # * <tt>location</tt> - a SitemapGenerator::SitemapIndexLocation instance or a Hash of options
8
+ # from which a SitemapLocation will be created for you.
9
+ def initialize(opts={})
10
+ @location = opts.is_a?(Hash) ? SitemapGenerator::SitemapIndexLocation.new(opts) : opts
11
+ @link_count = 0
12
+ @sitemaps_link_count = 0
13
+ @xml_content = '' # XML urlset content
14
+ @xml_wrapper_start = <<-HTML
15
+ <?xml version="1.0" encoding="UTF-8"?>
16
+ <sitemapindex
17
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
18
+ xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
19
+ http://www.sitemaps.org/schemas/sitemap/0.9/siteindex.xsd"
20
+ xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
21
+ >
22
+ HTML
23
+ @xml_wrapper_start.gsub!(/\s+/, ' ').gsub!(/ *> */, '>').strip!
24
+ @xml_wrapper_end = %q[</sitemapindex>]
25
+ @filesize = SitemapGenerator::Utilities.bytesize(@xml_wrapper_start) + SitemapGenerator::Utilities.bytesize(@xml_wrapper_end)
26
+ @written = false
27
+ @reserved_name = nil # holds the name reserved from the namer
28
+ @frozen = false # rather than actually freeze, use this boolean
29
+ @first_sitemap = nil # reference to the first thing added to this index
30
+ # Store the URL of the first sitemap added because if create_index is
31
+ # false this is the "index" URL
32
+ @first_sitemap_url = nil
33
+ end
34
+
35
+ # Finalize sitemaps as they are added to the index.
36
+ # If it's the first sitemap, finalize it but don't
37
+ # write it out, because we don't yet know if we need an index. If it's
38
+ # the second sitemap, we know we need an index, so reserve a name for the
39
+ # index, and go and write out the first sitemap. If it's the third or
40
+ # greater sitemap, just finalize and write it out as usual, nothing more
41
+ # needs to be done.
42
+ #
43
+ # If a link is being added to the index manually as a string, then we
44
+ # can assume that the index is required (unless create_index is false of course).
45
+ # This seems like the logical thing to do.
46
+ alias_method :super_add, :add
47
+ def add(link, options={})
48
+ if file = link.is_a?(SitemapFile) && link
49
+ @sitemaps_link_count += file.link_count
50
+ file.finalize! unless file.finalized?
51
+
52
+ # First link. If it's a SitemapFile store a reference to it and the options
53
+ # so that we can create a URL from it later. We can't create the URL yet
54
+ # because doing so fixes the sitemap file's name, and we have to wait to see
55
+ # if we have more than one link in the index before we can know who gets the
56
+ # first name (the index, or the sitemap). If the item is not a SitemapFile,
57
+ # then it has been manually added and we can be sure that the user intends
58
+ # for there to be an index.
59
+ if @link_count == 0
60
+ @first_sitemap = SitemapGenerator::Builder::LinkHolder.new(file, options)
61
+ @link_count += 1 # pretend it's added, but don't add it yet
62
+ else
63
+ # need an index so make sure name is reserved and first sitemap is written out
64
+ reserve_name unless @location.create_index == false
65
+ write_first_sitemap
66
+ file.write
67
+ super(SitemapGenerator::Builder::SitemapIndexUrl.new(file, options))
68
+ end
69
+ else
70
+ # A link is being added manually. Obviously the user wants an index.
71
+ # This overrides the create_index setting.
72
+ unless @location.create_index == false
73
+ @create_index = true
74
+ reserve_name
75
+ end
76
+
77
+ # Use the host from the location if none provided
78
+ options[:host] ||= @location.host
79
+ super(SitemapGenerator::Builder::SitemapIndexUrl.new(link, options))
80
+ end
81
+ end
82
+
83
+ # Return a boolean indicating whether the sitemap file can fit another link
84
+ # of <tt>bytes</tt> bytes in size. You can also pass a string and the
85
+ # bytesize will be calculated for you.
86
+ def file_can_fit?(bytes)
87
+ bytes = bytes.is_a?(String) ? SitemapGenerator::Utilities.bytesize(bytes) : bytes
88
+ (@filesize + bytes) < SitemapGenerator::MAX_SITEMAP_FILESIZE && @link_count < SitemapGenerator::MAX_SITEMAP_FILES
89
+ end
90
+
91
+ # Return the total number of links in all sitemaps reference by this index file
92
+ def total_link_count
93
+ @sitemaps_link_count
94
+ end
95
+
96
+ def stats_summary(opts={})
97
+ str = "Sitemap stats: #{number_with_delimiter(@sitemaps_link_count)} links / #{@link_count} sitemaps"
98
+ str += " / %dm%02ds" % opts[:time_taken].divmod(60) if opts[:time_taken]
99
+ end
100
+
101
+ def finalize!
102
+ raise SitemapGenerator::SitemapFinalizedError if finalized?
103
+ reserve_name if create_index?
104
+ write_first_sitemap
105
+ @frozen = true
106
+ end
107
+
108
+ # Write out the index if an index is needed
109
+ def write
110
+ super if create_index?
111
+ end
112
+
113
+ # Whether or not we need to create an index file. True if create_index is true
114
+ # or if create_index is :auto and we have more than one link in the index.
115
+ # If a link is added manually and create_index is not false, we force index
116
+ # creation because they obviously intend for there to be an index. False otherwise.
117
+ def create_index?
118
+ @create_index || @location.create_index == true || @location.create_index == :auto && @link_count > 1
119
+ end
120
+
121
+ # Return the index file URL. If create_index is true, this is the URL
122
+ # of the actual index file. If create_index is false, this is the URL
123
+ # of the first sitemap that was written out. Only call this method
124
+ # *after* the files have been finalized.
125
+ def index_url
126
+ if create_index? || !@first_sitemap_url
127
+ @location.url
128
+ else
129
+ @first_sitemap_url
130
+ end
131
+ end
132
+
133
+ protected
134
+
135
+ # Make sure the first sitemap has been written out and added to the index
136
+ def write_first_sitemap
137
+ if @first_sitemap
138
+ @first_sitemap.link.write unless @first_sitemap.link.written?
139
+ super_add(SitemapGenerator::Builder::SitemapIndexUrl.new(@first_sitemap.link, @first_sitemap.options))
140
+ @link_count -= 1 # we already counted it, don't count it twice
141
+ # Store the URL because if create_index is false, this is the
142
+ # "index" URL
143
+ @first_sitemap_url = @first_sitemap.link.location.url
144
+ @first_sitemap = nil
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,28 @@
1
+ require 'builder'
2
+
3
+ module SitemapGenerator
4
+ module Builder
5
+ class SitemapIndexUrl < SitemapUrl
6
+
7
+ def initialize(path, options={})
8
+ if index = path.is_a?(SitemapGenerator::Builder::SitemapIndexFile) && path
9
+ options = SitemapGenerator::Utilities.reverse_merge(options, :host => index.location.host, :lastmod => Time.now, :changefreq => 'always', :priority => 1.0)
10
+ path = index.location.path_in_public
11
+ super(path, options)
12
+ else
13
+ super
14
+ end
15
+ end
16
+
17
+ # Return the URL as XML
18
+ def to_xml(builder=nil)
19
+ builder = ::Builder::XmlMarkup.new if builder.nil?
20
+ builder.sitemap do
21
+ builder.loc self[:loc]
22
+ builder.lastmod w3c_date(self[:lastmod]) if self[:lastmod]
23
+ end
24
+ builder << '' # force to string
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,250 @@
1
+ require 'builder'
2
+ require 'uri'
3
+ require 'time'
4
+ require 'date'
5
+
6
+ module SitemapGenerator
7
+ module Builder
8
+ # A Hash-like class for holding information about a sitemap URL and
9
+ # generating an XML <url> element suitable for sitemaps.
10
+ class SitemapUrl < Hash
11
+
12
+ # Return a new instance with options configured on it.
13
+ #
14
+ # == Arguments
15
+ # * sitemap - a Sitemap instance, or
16
+ # * path, options - a path string and options hash
17
+ #
18
+ # == Options
19
+ # Requires a host to be set. If passing a sitemap, the sitemap must have a +default_host+
20
+ # configured. If calling with a path and options, you must include the <tt>:host</tt> option.
21
+ #
22
+ # * +host+
23
+ # * +priority+
24
+ # * +changefreq+
25
+ # * +lastmod+
26
+ # * +images+
27
+ # * +video+/+videos+
28
+ # * +geo+
29
+ # * +news+
30
+ # * +mobile+
31
+ # * +alternate+/+alternates+
32
+ # * +pagemap+
33
+ def initialize(path, options={})
34
+ options = options.dup
35
+ if sitemap = path.is_a?(SitemapGenerator::Builder::SitemapFile) && path
36
+ SitemapGenerator::Utilities.reverse_merge!(options, :host => sitemap.location.host, :lastmod => sitemap.lastmod)
37
+ path = sitemap.location.path_in_public
38
+ end
39
+
40
+ SitemapGenerator::Utilities.assert_valid_keys(options, :priority, :changefreq, :lastmod, :expires, :host, :images, :video, :geo, :news, :videos, :mobile, :alternate, :alternates, :pagemap)
41
+ SitemapGenerator::Utilities.reverse_merge!(options, :priority => 0.5, :changefreq => 'weekly', :lastmod => Time.now, :images => [], :news => {}, :videos => [], :mobile => false, :alternates => [])
42
+ raise "Cannot generate a url without a host" unless SitemapGenerator::Utilities.present?(options[:host])
43
+
44
+ if video = options.delete(:video)
45
+ options[:videos] = video.is_a?(Array) ? options[:videos].concat(video) : options[:videos] << video
46
+ end
47
+ if alternate = options.delete(:alternate)
48
+ options[:alternates] = alternate.is_a?(Array) ? options[:alternates].concat(alternate) : options[:alternates] << alternate
49
+ end
50
+
51
+ path = path.to_s.sub(/^\//, '')
52
+ loc = path.empty? ? options[:host] : (options[:host].to_s.sub(/\/$/, '') + '/' + path)
53
+ self.merge!(
54
+ :priority => options[:priority],
55
+ :changefreq => options[:changefreq],
56
+ :lastmod => options[:lastmod],
57
+ :expires => options[:expires],
58
+ :host => options[:host],
59
+ :loc => loc,
60
+ :images => prepare_images(options[:images], options[:host]),
61
+ :news => prepare_news(options[:news]),
62
+ :videos => options[:videos],
63
+ :geo => options[:geo],
64
+ :mobile => options[:mobile],
65
+ :alternates => options[:alternates],
66
+ :pagemap => options[:pagemap]
67
+ )
68
+ end
69
+
70
+ # Return the URL as XML
71
+ def to_xml(builder=nil)
72
+ builder = ::Builder::XmlMarkup.new if builder.nil?
73
+ builder.url do
74
+ builder.loc self[:loc]
75
+ builder.lastmod w3c_date(self[:lastmod]) if self[:lastmod]
76
+ builder.expires w3c_date(self[:expires]) if self[:expires]
77
+ builder.changefreq self[:changefreq].to_s if self[:changefreq]
78
+ builder.priority format_float(self[:priority]) if self[:priority]
79
+
80
+ unless SitemapGenerator::Utilities.blank?(self[:news])
81
+ news_data = self[:news]
82
+ builder.news:news do
83
+ builder.news:publication do
84
+ builder.news :name, news_data[:publication_name].to_s if news_data[:publication_name]
85
+ builder.news :language, news_data[:publication_language].to_s if news_data[:publication_language]
86
+ end
87
+
88
+ builder.news :access, news_data[:access].to_s if news_data[:access]
89
+ builder.news :genres, news_data[:genres].to_s if news_data[:genres]
90
+ builder.news :publication_date, w3c_date(news_data[:publication_date]) if news_data[:publication_date]
91
+ builder.news :title, news_data[:title].to_s if news_data[:title]
92
+ builder.news :keywords, news_data[:keywords].to_s if news_data[:keywords]
93
+ builder.news :stock_tickers, news_data[:stock_tickers].to_s if news_data[:stock_tickers]
94
+ end
95
+ end
96
+
97
+ self[:images].each do |image|
98
+ builder.image:image do
99
+ builder.image :loc, image[:loc]
100
+ builder.image :caption, image[:caption].to_s if image[:caption]
101
+ builder.image :geo_location, image[:geo_location].to_s if image[:geo_location]
102
+ builder.image :title, image[:title].to_s if image[:title]
103
+ builder.image :license, image[:license].to_s if image[:license]
104
+ end
105
+ end
106
+
107
+ self[:videos].each do |video|
108
+ builder.video :video do
109
+ builder.video :thumbnail_loc, video[:thumbnail_loc].to_s
110
+ builder.video :title, video[:title].to_s
111
+ builder.video :description, video[:description].to_s
112
+ builder.video :content_loc, video[:content_loc].to_s if video[:content_loc]
113
+ if video[:player_loc]
114
+ loc_attributes = { :allow_embed => yes_or_no_with_default(video[:allow_embed], true) }
115
+ loc_attributes[:autoplay] = video[:autoplay].to_s if SitemapGenerator::Utilities.present?(video[:autoplay])
116
+ builder.video :player_loc, video[:player_loc].to_s, loc_attributes
117
+ end
118
+ builder.video :duration, video[:duration].to_s if video[:duration]
119
+ builder.video :expiration_date, w3c_date(video[:expiration_date]) if video[:expiration_date]
120
+ builder.video :rating, format_float(video[:rating]) if video[:rating]
121
+ builder.video :view_count, video[:view_count].to_s if video[:view_count]
122
+ builder.video :publication_date, w3c_date(video[:publication_date]) if video[:publication_date]
123
+ video[:tags].each {|tag| builder.video :tag, tag.to_s } if video[:tags]
124
+ builder.video :tag, video[:tag].to_s if video[:tag]
125
+ builder.video :category, video[:category].to_s if video[:category]
126
+ builder.video :family_friendly, yes_or_no_with_default(video[:family_friendly], true) if video.has_key?(:family_friendly)
127
+ builder.video :gallery_loc, video[:gallery_loc].to_s, :title => video[:gallery_title].to_s if video[:gallery_loc]
128
+ builder.video :price, video[:price].to_s, prepare_video_price_attribs(video) if SitemapGenerator::Utilities.present?(video[:price])
129
+ if video[:uploader]
130
+ builder.video :uploader, video[:uploader].to_s, video[:uploader_info] ? { :info => video[:uploader_info].to_s } : {}
131
+ end
132
+ builder.video :live, yes_or_no_with_default(video[:live], true) if video.has_key?(:live)
133
+ builder.video :requires_subscription, yes_or_no_with_default(video[:requires_subscription], true) if video.has_key?(:requires_subscription)
134
+ end
135
+ end
136
+
137
+ self[:alternates].each do |alternate|
138
+ rel = alternate[:nofollow] ? 'alternate nofollow' : 'alternate'
139
+ attributes = { :rel => rel, :hreflang => alternate[:lang].to_s, :href => alternate[:href].to_s }
140
+ attributes[:media] = alternate[:media].to_s if SitemapGenerator::Utilities.present?(alternate[:media])
141
+ builder.xhtml :link, attributes
142
+ end
143
+
144
+ unless SitemapGenerator::Utilities.blank?(self[:geo])
145
+ geo = self[:geo]
146
+ builder.geo :geo do
147
+ builder.geo :format, geo[:format].to_s if geo[:format]
148
+ end
149
+ end
150
+
151
+ unless SitemapGenerator::Utilities.blank?(self[:mobile])
152
+ builder.mobile :mobile
153
+ end
154
+
155
+ unless SitemapGenerator::Utilities.blank?(self[:pagemap])
156
+ builder.pagemap :PageMap do
157
+ SitemapGenerator::Utilities.as_array(self[:pagemap][:dataobjects]).each do |dataobject|
158
+ builder.pagemap :DataObject, :type => dataobject[:type].to_s, :id => dataobject[:id].to_s do
159
+ SitemapGenerator::Utilities.as_array(dataobject[:attributes]).each do |attribute|
160
+ builder.pagemap :Attribute, attribute[:value].to_s, :name => attribute[:name].to_s
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ builder << '' # Force to string
168
+ end
169
+
170
+ def news?
171
+ SitemapGenerator::Utilities.present?(self[:news])
172
+ end
173
+
174
+ protected
175
+
176
+ def prepare_video_price_attribs(video)
177
+ attribs = {}
178
+ attribs[:currency] = video[:price_currency].to_s # required
179
+ attribs[:type] = video[:price_type] if SitemapGenerator::Utilities.present?(video[:price_type])
180
+ attribs[:resolution] = video[:price_resolution] if SitemapGenerator::Utilities.present?(video[:price_resolution])
181
+ attribs
182
+ end
183
+
184
+ def prepare_news(news)
185
+ SitemapGenerator::Utilities.assert_valid_keys(news, :publication_name, :publication_language, :publication_date, :genres, :access, :title, :keywords, :stock_tickers) unless news.empty?
186
+ news
187
+ end
188
+
189
+ # Return an Array of image option Hashes suitable to be parsed by SitemapGenerator::Builder::SitemapFile
190
+ def prepare_images(images, host)
191
+ images.delete_if { |key,value| key[:loc] == nil }
192
+ images.each do |r|
193
+ SitemapGenerator::Utilities.assert_valid_keys(r, :loc, :caption, :geo_location, :title, :license)
194
+ r[:loc] = URI.join(host, r[:loc]).to_s
195
+ end
196
+ images[0..(SitemapGenerator::MAX_SITEMAP_IMAGES-1)]
197
+ end
198
+
199
+ def w3c_date(date)
200
+ if date.is_a?(String)
201
+ date
202
+ elsif date.respond_to?(:iso8601)
203
+ date.iso8601.sub(/Z$/i, '+00:00')
204
+ elsif date.is_a?(Date) && !date.is_a?(DateTime)
205
+ date.strftime("%Y-%m-%d")
206
+ else
207
+ zulutime = if date.is_a?(DateTime)
208
+ date.new_offset(0)
209
+ elsif date.respond_to?(:utc)
210
+ date.utc
211
+ elsif date.is_a?(Integer)
212
+ Time.at(date).utc
213
+ else
214
+ nil
215
+ end
216
+
217
+ if zulutime
218
+ zulutime.strftime("%Y-%m-%dT%H:%M:%S+00:00")
219
+ else
220
+ zone = date.strftime('%z').insert(-3, ':')
221
+ date.strftime("%Y-%m-%dT%H:%M:%S") + zone
222
+ end
223
+ end
224
+ end
225
+
226
+ # Accept a string or boolean and return 'yes' or 'no'. If a string, the
227
+ # value must be 'yes' or 'no'. Pass the default value as a boolean using `default`.
228
+ def yes_or_no(value)
229
+ if value.is_a?(String)
230
+ raise ArgumentError.new("Unrecognized value for yes/no field: #{value.inspect}") unless value =~ /^(yes|no)$/i
231
+ value.downcase
232
+ else
233
+ value ? 'yes' : 'no'
234
+ end
235
+ end
236
+
237
+ # If the value is nil, return `default` converted to either 'yes' or 'no'.
238
+ # If the value is set, return its value converted to 'yes' or 'no'.
239
+ def yes_or_no_with_default(value, default)
240
+ yes_or_no(value.nil? ? default : value)
241
+ end
242
+
243
+ # Format a float to to one decimal precision.
244
+ # TODO: Use rounding with precision once merged with framework_agnostic.
245
+ def format_float(value)
246
+ value.is_a?(String) ? value : ('%0.1f' % value)
247
+ end
248
+ end
249
+ end
250
+ end
@@ -0,0 +1,3 @@
1
+ Dir["#{File.dirname(__FILE__)}/core_ext/**/*.rb"].sort.each do |path|
2
+ require path
3
+ end
@@ -0,0 +1,45 @@
1
+ require 'bigdecimal'
2
+
3
+ begin
4
+ require 'psych'
5
+ rescue LoadError
6
+ end
7
+
8
+ require 'yaml'
9
+
10
+ # Define our own class rather than modify the global class
11
+ class SitemapGenerator::BigDecimal < BigDecimal
12
+ YAML_TAG = 'tag:yaml.org,2002:float'
13
+ YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' }
14
+
15
+ yaml_as YAML_TAG
16
+
17
+ # This emits the number without any scientific notation.
18
+ # This is better than self.to_f.to_s since it doesn't lose precision.
19
+ #
20
+ # Note that reconstituting YAML floats to native floats may lose precision.
21
+ def to_yaml(opts = {})
22
+ return super if defined?(YAML::ENGINE) && !YAML::ENGINE.syck?
23
+
24
+ YAML.quick_emit(nil, opts) do |out|
25
+ string = to_s
26
+ out.scalar(YAML_TAG, YAML_MAPPING[string] || string, :plain)
27
+ end
28
+ end
29
+
30
+ def encode_with(coder)
31
+ string = to_s
32
+ coder.represent_scalar(nil, YAML_MAPPING[string] || string)
33
+ end
34
+
35
+ def to_d
36
+ self
37
+ end
38
+
39
+ DEFAULT_STRING_FORMAT = 'F'
40
+ def to_formatted_s(format = DEFAULT_STRING_FORMAT)
41
+ _original_to_s(format)
42
+ end
43
+ alias_method :_original_to_s, :to_s
44
+ alias_method :to_s, :to_formatted_s
45
+ end