sitemap_generator_ftbpro 5.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +35 -0
- data/MIT-LICENSE +20 -0
- data/README.md +1139 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/lib/capistrano/sitemap_generator.rb +1 -0
- data/lib/capistrano/tasks/sitemap_generator.cap +36 -0
- data/lib/sitemap_generator/adapters/file_adapter.rb +43 -0
- data/lib/sitemap_generator/adapters/fog_adapter.rb +28 -0
- data/lib/sitemap_generator/adapters/s3_adapter.rb +41 -0
- data/lib/sitemap_generator/adapters/wave_adapter.rb +21 -0
- data/lib/sitemap_generator/adapters.rb +0 -0
- data/lib/sitemap_generator/application.rb +49 -0
- data/lib/sitemap_generator/builder/sitemap_file.rb +171 -0
- data/lib/sitemap_generator/builder/sitemap_index_file.rb +149 -0
- data/lib/sitemap_generator/builder/sitemap_index_url.rb +28 -0
- data/lib/sitemap_generator/builder/sitemap_url.rb +250 -0
- data/lib/sitemap_generator/builder.rb +8 -0
- data/lib/sitemap_generator/core_ext/big_decimal.rb +45 -0
- data/lib/sitemap_generator/core_ext/numeric.rb +48 -0
- data/lib/sitemap_generator/core_ext.rb +3 -0
- data/lib/sitemap_generator/helpers/number_helper.rb +237 -0
- data/lib/sitemap_generator/interpreter.rb +80 -0
- data/lib/sitemap_generator/link_set.rb +665 -0
- data/lib/sitemap_generator/railtie.rb +7 -0
- data/lib/sitemap_generator/sitemap_location.rb +192 -0
- data/lib/sitemap_generator/sitemap_namer.rb +75 -0
- data/lib/sitemap_generator/tasks.rb +53 -0
- data/lib/sitemap_generator/templates.rb +41 -0
- data/lib/sitemap_generator/utilities.rb +181 -0
- data/lib/sitemap_generator.rb +82 -0
- data/lib/tasks/sitemap_generator_tasks.rake +1 -0
- data/rails/install.rb +2 -0
- data/rails/uninstall.rb +2 -0
- data/spec/blueprint.rb +15 -0
- data/spec/files/sitemap.create.rb +12 -0
- data/spec/files/sitemap.groups.rb +49 -0
- data/spec/sitemap_generator/adapters/s3_adapter_spec.rb +23 -0
- data/spec/sitemap_generator/alternate_sitemap_spec.rb +79 -0
- data/spec/sitemap_generator/application_spec.rb +69 -0
- data/spec/sitemap_generator/builder/sitemap_file_spec.rb +110 -0
- data/spec/sitemap_generator/builder/sitemap_index_file_spec.rb +124 -0
- data/spec/sitemap_generator/builder/sitemap_index_url_spec.rb +28 -0
- data/spec/sitemap_generator/builder/sitemap_url_spec.rb +186 -0
- data/spec/sitemap_generator/core_ext/bigdecimal_spec.rb +20 -0
- data/spec/sitemap_generator/core_ext/numeric_spec.rb +43 -0
- data/spec/sitemap_generator/file_adaptor_spec.rb +20 -0
- data/spec/sitemap_generator/geo_sitemap_spec.rb +30 -0
- data/spec/sitemap_generator/helpers/number_helper_spec.rb +196 -0
- data/spec/sitemap_generator/interpreter_spec.rb +90 -0
- data/spec/sitemap_generator/link_set_spec.rb +864 -0
- data/spec/sitemap_generator/mobile_sitemap_spec.rb +27 -0
- data/spec/sitemap_generator/news_sitemap_spec.rb +42 -0
- data/spec/sitemap_generator/pagemap_sitemap_spec.rb +57 -0
- data/spec/sitemap_generator/sitemap_generator_spec.rb +582 -0
- data/spec/sitemap_generator/sitemap_groups_spec.rb +144 -0
- data/spec/sitemap_generator/sitemap_location_spec.rb +210 -0
- data/spec/sitemap_generator/sitemap_namer_spec.rb +96 -0
- data/spec/sitemap_generator/templates_spec.rb +24 -0
- data/spec/sitemap_generator/utilities/existence_spec.rb +26 -0
- data/spec/sitemap_generator/utilities/hash_spec.rb +57 -0
- data/spec/sitemap_generator/utilities/rounding_spec.rb +31 -0
- data/spec/sitemap_generator/utilities_spec.rb +101 -0
- data/spec/sitemap_generator/video_sitemap_spec.rb +117 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/file_macros.rb +39 -0
- data/spec/support/schemas/siteindex.xsd +73 -0
- data/spec/support/schemas/sitemap-geo.xsd +41 -0
- data/spec/support/schemas/sitemap-mobile.xsd +32 -0
- data/spec/support/schemas/sitemap-news.xsd +159 -0
- data/spec/support/schemas/sitemap-pagemap.xsd +97 -0
- data/spec/support/schemas/sitemap-video.xsd +643 -0
- data/spec/support/schemas/sitemap.xsd +115 -0
- data/spec/support/xml_macros.rb +67 -0
- data/templates/sitemap.rb +27 -0
- 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,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
|