sitemap_generator_ftbpro 5.0.4

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/adapters/file_adapter.rb +43 -0
  11. data/lib/sitemap_generator/adapters/fog_adapter.rb +28 -0
  12. data/lib/sitemap_generator/adapters/s3_adapter.rb +41 -0
  13. data/lib/sitemap_generator/adapters/wave_adapter.rb +21 -0
  14. data/lib/sitemap_generator/adapters.rb +0 -0
  15. data/lib/sitemap_generator/application.rb +49 -0
  16. data/lib/sitemap_generator/builder/sitemap_file.rb +171 -0
  17. data/lib/sitemap_generator/builder/sitemap_index_file.rb +149 -0
  18. data/lib/sitemap_generator/builder/sitemap_index_url.rb +28 -0
  19. data/lib/sitemap_generator/builder/sitemap_url.rb +250 -0
  20. data/lib/sitemap_generator/builder.rb +8 -0
  21. data/lib/sitemap_generator/core_ext/big_decimal.rb +45 -0
  22. data/lib/sitemap_generator/core_ext/numeric.rb +48 -0
  23. data/lib/sitemap_generator/core_ext.rb +3 -0
  24. data/lib/sitemap_generator/helpers/number_helper.rb +237 -0
  25. data/lib/sitemap_generator/interpreter.rb +80 -0
  26. data/lib/sitemap_generator/link_set.rb +665 -0
  27. data/lib/sitemap_generator/railtie.rb +7 -0
  28. data/lib/sitemap_generator/sitemap_location.rb +192 -0
  29. data/lib/sitemap_generator/sitemap_namer.rb +75 -0
  30. data/lib/sitemap_generator/tasks.rb +53 -0
  31. data/lib/sitemap_generator/templates.rb +41 -0
  32. data/lib/sitemap_generator/utilities.rb +181 -0
  33. data/lib/sitemap_generator.rb +82 -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,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,8 @@
1
+ require 'sitemap_generator/builder/sitemap_file'
2
+ require 'sitemap_generator/builder/sitemap_index_file'
3
+ require 'sitemap_generator/builder/sitemap_url'
4
+ require 'sitemap_generator/builder/sitemap_index_url'
5
+
6
+ module SitemapGenerator::Builder
7
+ LinkHolder = Struct.new(:link, :options)
8
+ 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
@@ -0,0 +1,48 @@
1
+ class SitemapGenerator::Numeric
2
+ KILOBYTE = 1024
3
+ MEGABYTE = KILOBYTE * 1024
4
+ GIGABYTE = MEGABYTE * 1024
5
+ TERABYTE = GIGABYTE * 1024
6
+ PETABYTE = TERABYTE * 1024
7
+ EXABYTE = PETABYTE * 1024
8
+
9
+ def initialize(number)
10
+ @number = number
11
+ end
12
+
13
+ # Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
14
+ def bytes
15
+ @number
16
+ end
17
+ alias :byte :bytes
18
+
19
+ def kilobytes
20
+ @number * KILOBYTE
21
+ end
22
+ alias :kilobyte :kilobytes
23
+
24
+ def megabytes
25
+ @number * MEGABYTE
26
+ end
27
+ alias :megabyte :megabytes
28
+
29
+ def gigabytes
30
+ @number * GIGABYTE
31
+ end
32
+ alias :gigabyte :gigabytes
33
+
34
+ def terabytes
35
+ @number * TERABYTE
36
+ end
37
+ alias :terabyte :terabytes
38
+
39
+ def petabytes
40
+ @number * PETABYTE
41
+ end
42
+ alias :petabyte :petabytes
43
+
44
+ def exabytes
45
+ @number * EXABYTE
46
+ end
47
+ alias :exabyte :exabytes
48
+ 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,237 @@
1
+ # require "sitemap_generator/core_ext/big_decimal/conversions"
2
+ require "sitemap_generator/utilities"
3
+
4
+ module SitemapGenerator
5
+ # = SitemapGenerator Number Helpers
6
+ module Helpers #:nodoc:
7
+
8
+ # Provides methods for converting numbers into formatted strings.
9
+ # Methods are provided for precision, positional notation and file size
10
+ # and pretty printing.
11
+ #
12
+ # Most methods expect a +number+ argument, and will return it
13
+ # unchanged if can't be converted into a valid number.
14
+ module NumberHelper
15
+
16
+ # Raised when argument +number+ param given to the helpers is invalid and
17
+ # the option :raise is set to +true+.
18
+ class InvalidNumberError < StandardError
19
+ attr_accessor :number
20
+ def initialize(number)
21
+ @number = number
22
+ end
23
+ end
24
+
25
+ # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can
26
+ # customize the format in the +options+ hash.
27
+ #
28
+ # ==== Options
29
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
30
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
31
+ # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
32
+ #
33
+ # ==== Examples
34
+ # number_with_delimiter(12345678) # => 12,345,678
35
+ # number_with_delimiter(12345678.05) # => 12,345,678.05
36
+ # number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678
37
+ # number_with_delimiter(12345678, :separator => ",") # => 12,345,678
38
+ # number_with_delimiter(12345678.05, :locale => :fr) # => 12 345 678,05
39
+ # number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",")
40
+ # # => 98 765 432,98
41
+ def number_with_delimiter(number, options = {})
42
+ SitemapGenerator::Utilities.symbolize_keys!(options)
43
+
44
+ begin
45
+ Float(number)
46
+ rescue ArgumentError, TypeError
47
+ if options[:raise]
48
+ raise InvalidNumberError, number
49
+ else
50
+ return number
51
+ end
52
+ end
53
+
54
+ defaults = {
55
+ :separator => ".",
56
+ :delimiter => ",",
57
+ :precision => 3,
58
+ :significant => false,
59
+ :strip_insignificant_zeros => false
60
+ }
61
+ options = SitemapGenerator::Utilities.reverse_merge(options, defaults)
62
+
63
+ parts = number.to_s.to_str.split('.')
64
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
65
+ parts.join(options[:separator])
66
+ end
67
+
68
+ # Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision
69
+ # of 2 if +:significant+ is +false+, and 5 if +:significant+ is +true+).
70
+ # You can customize the format in the +options+ hash.
71
+ #
72
+ # ==== Options
73
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
74
+ # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
75
+ # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +false+)
76
+ # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
77
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
78
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +false+)
79
+ #
80
+ # ==== Examples
81
+ # number_with_precision(111.2345) # => 111.235
82
+ # number_with_precision(111.2345, :precision => 2) # => 111.23
83
+ # number_with_precision(13, :precision => 5) # => 13.00000
84
+ # number_with_precision(389.32314, :precision => 0) # => 389
85
+ # number_with_precision(111.2345, :significant => true) # => 111
86
+ # number_with_precision(111.2345, :precision => 1, :significant => true) # => 100
87
+ # number_with_precision(13, :precision => 5, :significant => true) # => 13.000
88
+ # number_with_precision(111.234, :locale => :fr) # => 111,234
89
+ # number_with_precision(13, :precision => 5, :significant => true, strip_insignificant_zeros => true)
90
+ # # => 13
91
+ # number_with_precision(389.32314, :precision => 4, :significant => true) # => 389.3
92
+ # number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
93
+ # # => 1.111,23
94
+ def number_with_precision(number, options = {})
95
+ SitemapGenerator::Utilities.symbolize_keys!(options)
96
+
97
+ number = begin
98
+ Float(number)
99
+ rescue ArgumentError, TypeError
100
+ if options[:raise]
101
+ raise InvalidNumberError, number
102
+ else
103
+ return number
104
+ end
105
+ end
106
+
107
+ defaults = {
108
+ :separator => ".",
109
+ :delimiter => ",",
110
+ :precision => 3,
111
+ :significant => false,
112
+ :strip_insignificant_zeros => false
113
+ }
114
+ precision_defaults = {
115
+ :delimiter => ""
116
+ }
117
+ defaults = defaults.merge(precision_defaults)
118
+
119
+ options = SitemapGenerator::Utilities.reverse_merge(options, defaults) # Allow the user to unset default values: Eg.: :significant => false
120
+ precision = options.delete :precision
121
+ significant = options.delete :significant
122
+ strip_insignificant_zeros = options.delete :strip_insignificant_zeros
123
+
124
+ if significant and precision > 0
125
+ if number == 0
126
+ digits, rounded_number = 1, 0
127
+ else
128
+ digits = (Math.log10(number.abs) + 1).floor
129
+ rounded_number = (SitemapGenerator::BigDecimal.new(number.to_s) / SitemapGenerator::BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision)
130
+ digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
131
+ end
132
+ precision = precision - digits
133
+ precision = precision > 0 ? precision : 0 #don't let it be negative
134
+ else
135
+ rounded_number = SitemapGenerator::Utilities.round(SitemapGenerator::BigDecimal.new(number.to_s), precision).to_f
136
+ end
137
+ formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options)
138
+ if strip_insignificant_zeros
139
+ escaped_separator = Regexp.escape(options[:separator])
140
+ formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
141
+ else
142
+ formatted_number
143
+ end
144
+
145
+ end
146
+
147
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
148
+ DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
149
+ -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze
150
+
151
+ # Formats the bytes in +number+ into a more understandable representation
152
+ # (e.g., giving it 1500 yields 1.5 KB). This method is useful for
153
+ # reporting file sizes to users. You can customize the
154
+ # format in the +options+ hash.
155
+ #
156
+ # See <tt>number_to_human</tt> if you want to pretty-print a generic number.
157
+ #
158
+ # ==== Options
159
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
160
+ # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
161
+ # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+)
162
+ # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
163
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
164
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+)
165
+ # ==== Examples
166
+ # number_to_human_size(123) # => 123 Bytes
167
+ # number_to_human_size(1234) # => 1.21 KB
168
+ # number_to_human_size(12345) # => 12.1 KB
169
+ # number_to_human_size(1234567) # => 1.18 MB
170
+ # number_to_human_size(1234567890) # => 1.15 GB
171
+ # number_to_human_size(1234567890123) # => 1.12 TB
172
+ # number_to_human_size(1234567, :precision => 2) # => 1.2 MB
173
+ # number_to_human_size(483989, :precision => 2) # => 470 KB
174
+ # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB
175
+ #
176
+ # Non-significant zeros after the fractional separator are stripped out by default (set
177
+ # <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
178
+ # number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
179
+ # number_to_human_size(524288000, :precision=>5) # => "500 MB"
180
+ def number_to_human_size(number, options = {})
181
+ SitemapGenerator::Utilities.symbolize_keys!(options)
182
+
183
+ number = begin
184
+ Float(number)
185
+ rescue ArgumentError, TypeError
186
+ if options[:raise]
187
+ raise InvalidNumberError, number
188
+ else
189
+ return number
190
+ end
191
+ end
192
+
193
+ defaults = {
194
+ :separator => ".",
195
+ :delimiter => ",",
196
+ :precision => 3,
197
+ :significant => false,
198
+ :strip_insignificant_zeros => false
199
+ }
200
+ human = {
201
+ :delimiter => "",
202
+ :precision => 3,
203
+ :significant => true,
204
+ :strip_insignificant_zeros => true
205
+ }
206
+ defaults = defaults.merge(human)
207
+ options = SitemapGenerator::Utilities.reverse_merge(options, defaults)
208
+ #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
209
+ options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
210
+
211
+ storage_units_format = "%n %u"
212
+
213
+ if number.to_i < 1024
214
+ unit = number.to_i > 1 || number.to_i == 0 ? 'Bytes' : 'Byte'
215
+ storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
216
+ else
217
+ max_exp = STORAGE_UNITS.size - 1
218
+ exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
219
+ exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
220
+ number /= 1024 ** exponent
221
+
222
+ unit_key = STORAGE_UNITS[exponent]
223
+ units = {
224
+ :byte => "Bytes",
225
+ :kb => "KB",
226
+ :mb => "MB",
227
+ :gb => "GB",
228
+ :tb => "TB"
229
+ }
230
+ unit = units[unit_key]
231
+ formatted_number = number_with_precision(number, options)
232
+ storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,80 @@
1
+ require 'sitemap_generator'
2
+
3
+ module SitemapGenerator
4
+
5
+ # Provide a class for evaluating blocks, making the URL helpers from the framework
6
+ # and API methods available to it.
7
+ class Interpreter
8
+
9
+ if SitemapGenerator.app.rails3?
10
+ include ::Rails.application.routes.url_helpers
11
+ elsif SitemapGenerator.app.rails?
12
+ require 'action_controller'
13
+ include ActionController::UrlWriter
14
+ end
15
+
16
+ # Call with a block to evaluate a dynamic config. The only method exposed for you is
17
+ # `add` to add a link to the sitemap object attached to this interpreter.
18
+ #
19
+ # === Options
20
+ # * <tt>link_set</tt> - a LinkSet instance to use. Default is SitemapGenerator::Sitemap.
21
+ #
22
+ # All other options are passed to the LinkSet by setting them using accessor methods.
23
+ def initialize(opts={}, &block)
24
+ opts = SitemapGenerator::Utilities.reverse_merge(opts, :link_set => SitemapGenerator::Sitemap)
25
+ @linkset = opts.delete :link_set
26
+ @linkset.send(:set_options, opts)
27
+ eval(&block) if block_given?
28
+ end
29
+
30
+ def add(*args)
31
+ @linkset.add(*args)
32
+ end
33
+
34
+ def add_to_index(*args)
35
+ @linkset.add_to_index(*args)
36
+ end
37
+
38
+ # Start a new group of sitemaps. Any of the options to SitemapGenerator.new may
39
+ # be passed. Pass a block with calls to +add+ to add links to the sitemaps.
40
+ #
41
+ # All groups use the same sitemap index.
42
+ def group(*args, &block)
43
+ @linkset.group(*args, &block)
44
+ end
45
+
46
+ # Return the LinkSet instance so that you can access it from within the `create` block
47
+ # without having to use the yield_sitemap option.
48
+ def sitemap
49
+ @linkset
50
+ end
51
+
52
+ # Evaluate the block in the interpreter. Pass :yield_sitemap => true to
53
+ # yield the Interpreter instance to the block...for old-style calling.
54
+ def eval(opts={}, &block)
55
+ if block_given?
56
+ if opts[:yield_sitemap]
57
+ yield @linkset
58
+ else
59
+ instance_eval(&block)
60
+ end
61
+ end
62
+ end
63
+
64
+ # Run the interpreter on a config file using
65
+ # the default <tt>SitemapGenerator::Sitemap</tt> sitemap object.
66
+ #
67
+ # === Options
68
+ # * <tt>:config_file</tt> - full path to the config file to evaluate.
69
+ # Default is config/sitemap.rb in your application's root directory.
70
+ # All other options are passed to +new+.
71
+ def self.run(opts={}, &block)
72
+ opts = opts.dup
73
+ config_file = opts.delete(:config_file)
74
+ config_file ||= SitemapGenerator.app.root + 'config/sitemap.rb'
75
+ interpreter = self.new(opts)
76
+ interpreter.instance_eval(File.read(config_file), config_file.to_s)
77
+ interpreter
78
+ end
79
+ end
80
+ end