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
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'bundler/setup'
2
+ Bundler.require
3
+
4
+ desc 'Default: run spec tests.'
5
+ task :default => :spec
6
+
7
+ require "rspec/core/rake_task"
8
+ RSpec::Core::RakeTask.new(:spec) do |spec|
9
+ spec.pattern = Dir.glob(['spec/sitemap_generator/**/*'])
10
+ spec.rspec_opts = ['--backtrace']
11
+ end
12
+
13
+ #
14
+ # Helpers
15
+ #
16
+
17
+ def name; @name ||= Dir['*.gemspec'].first.split('.').first end
18
+ def version; File.read('VERSION').chomp end
19
+ def gemspec_file; "#{name}.gemspec" end
20
+ def gem_file; "#{name}-#{version}.gem" end
21
+
22
+ #
23
+ # Release Tasks
24
+ # @see https://github.com/mojombo/rakegem
25
+ #
26
+
27
+ desc "Create tag v#{version}, build the gem and push to Git"
28
+ task :release => :build do
29
+ unless `git branch` =~ /^\* master$/
30
+ puts "You must be on the master branch to release!"
31
+ exit!
32
+ end
33
+ sh "git tag v#{version}"
34
+ sh "git push origin master --tags"
35
+ end
36
+
37
+ desc "Build #{gem_file} into the pkg/ directory"
38
+ task :build do
39
+ sh "mkdir -p pkg"
40
+ sh "gem build #{gemspec_file}"
41
+ sh "mv #{gem_file} pkg"
42
+ sh "bundle --local"
43
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 5.0.4
@@ -0,0 +1 @@
1
+ load File.expand_path(File.join('..', 'tasks', 'sitemap_generator.cap'), __FILE__)
@@ -0,0 +1,36 @@
1
+ namespace :deploy do
2
+ namespace :sitemap do
3
+ desc 'Create sitemap and ping search engines'
4
+ task :refresh do
5
+ on roles :web do
6
+ within release_path do
7
+ with rails_env: fetch(:rails_env) do
8
+ execute :rake, "sitemap:refresh"
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ desc 'Create sitemap without pinging search engines'
15
+ task :create do
16
+ on roles :web do
17
+ within release_path do
18
+ with rails_env: fetch(:rails_env) do
19
+ execute :rake, "sitemap:create"
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ desc 'Clean up sitemaps in sitemap_generator path'
26
+ task :clean do
27
+ on roles :web do
28
+ within release_path do
29
+ with rails_env: fetch(:rails_env) do
30
+ execute :rake, "sitemap:clean"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,43 @@
1
+ module SitemapGenerator
2
+ # Class for writing out data to a file.
3
+ class FileAdapter
4
+
5
+ # Write data to a file.
6
+ # @param location - File object giving the full path and file name of the file.
7
+ # If the location specifies a directory(ies) which does not exist, the directory(ies)
8
+ # will be created for you. If the location path ends with `.gz` the data will be
9
+ # compressed prior to being written out. Otherwise the data will be written out
10
+ # unchanged.
11
+ # @param raw_data - data to write to the file.
12
+ def write(location, raw_data)
13
+ # Ensure that the directory exists
14
+ dir = location.directory
15
+ if !File.exists?(dir)
16
+ FileUtils.mkdir_p(dir)
17
+ elsif !File.directory?(dir)
18
+ raise SitemapError.new("#{dir} should be a directory!")
19
+ end
20
+
21
+ stream = open(location.path, 'wb')
22
+ if location.path.to_s =~ /.gz$/
23
+ gzip(stream, raw_data)
24
+ else
25
+ plain(stream, raw_data)
26
+ end
27
+ end
28
+
29
+ # Write `data` to a stream, passing the data through a GzipWriter
30
+ # to compress it.
31
+ def gzip(stream, data)
32
+ gz = Zlib::GzipWriter.new(stream)
33
+ gz.write data
34
+ gz.close
35
+ end
36
+
37
+ # Write `data` to a stream as is.
38
+ def plain(stream, data)
39
+ stream.write data
40
+ stream.close
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,28 @@
1
+ begin
2
+ require 'fog'
3
+ rescue LoadError
4
+ raise LoadError.new("Missing required 'fog'. Please 'gem install fog' and require it in your application.")
5
+ end
6
+
7
+ module SitemapGenerator
8
+ class FogAdapter
9
+
10
+ def initialize(opts = {})
11
+ @fog_credentials = opts[:fog_credentials]
12
+ @fog_directory = opts[:fog_directory]
13
+ end
14
+
15
+ # Call with a SitemapLocation and string data
16
+ def write(location, raw_data)
17
+ SitemapGenerator::FileAdapter.new.write(location, raw_data)
18
+
19
+ storage = Fog::Storage.new(@fog_credentials)
20
+ directory = storage.directories.new(:key => @fog_directory)
21
+ directory.files.create(
22
+ :key => location.path_in_public,
23
+ :body => File.open(location.path),
24
+ :public => true
25
+ )
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,41 @@
1
+ begin
2
+ require 'fog'
3
+ rescue LoadError
4
+ raise LoadError.new("Missing required 'fog'. Please 'gem install fog' and require it in your application.")
5
+ end
6
+
7
+ module SitemapGenerator
8
+ class S3Adapter
9
+
10
+ def initialize(opts = {})
11
+ @aws_access_key_id = opts[:aws_access_key_id] || ENV['AWS_ACCESS_KEY_ID']
12
+ @aws_secret_access_key = opts[:aws_secret_access_key] || ENV['AWS_SECRET_ACCESS_KEY']
13
+ @fog_provider = opts[:fog_provider] || ENV['FOG_PROVIDER']
14
+ @fog_directory = opts[:fog_directory] || ENV['FOG_DIRECTORY']
15
+ @fog_region = opts[:fog_region] || ENV['FOG_REGION']
16
+ @fog_path_style = opts[:fog_path_style] || ENV['FOG_PATH_STYLE']
17
+ end
18
+
19
+ # Call with a SitemapLocation and string data
20
+ def write(location, raw_data)
21
+ SitemapGenerator::FileAdapter.new.write(location, raw_data)
22
+
23
+ credentials = {
24
+ :aws_access_key_id => @aws_access_key_id,
25
+ :aws_secret_access_key => @aws_secret_access_key,
26
+ :provider => @fog_provider,
27
+ }
28
+ credentials[:region] = @fog_region if @fog_region
29
+ credentials[:path_style] = @fog_path_style if @fog_path_style
30
+
31
+ storage = Fog::Storage.new(credentials)
32
+ directory = storage.directories.new(:key => @fog_directory)
33
+ directory.files.create(
34
+ :key => location.path_in_public,
35
+ :body => File.open(location.path),
36
+ :public => true
37
+ )
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,21 @@
1
+ begin
2
+ require 'carrierwave'
3
+ rescue LoadError
4
+ raise LoadError.new("Missing required 'carrierwave'. Please 'gem install carrierwave' and require it in your application.")
5
+ end
6
+
7
+ module SitemapGenerator
8
+ class WaveAdapter < ::CarrierWave::Uploader::Base
9
+ attr_accessor :store_dir
10
+
11
+ # Call with a SitemapLocation and string data
12
+ def write(location, raw_data)
13
+ SitemapGenerator::FileAdapter.new.write(location, raw_data)
14
+ directory = File.dirname(location.path_in_public)
15
+ if directory != '.'
16
+ self.store_dir = directory
17
+ end
18
+ store!(open(location.path, 'rb'))
19
+ end
20
+ end
21
+ end
File without changes
@@ -0,0 +1,49 @@
1
+ require 'pathname'
2
+
3
+ module SitemapGenerator
4
+ class Application
5
+ def rails?
6
+ defined?(Rails)
7
+ end
8
+
9
+ # Returns a boolean indicating whether this environment is Rails 3
10
+ #
11
+ # @return [Boolean]
12
+ def rails3?
13
+ rails? && Rails.version.to_f >= 3
14
+ rescue
15
+ false # Rails.version defined in 2.1.0
16
+ end
17
+
18
+ def root
19
+ Pathname.new(rails_root || Dir.getwd)
20
+ end
21
+
22
+ protected
23
+
24
+ # Returns the root of the Rails application,
25
+ # if this is running in a Rails context.
26
+ # Returns `nil` if no such root is defined.
27
+ #
28
+ # @return [String, nil]
29
+ def rails_root
30
+ if defined?(::Rails.root)
31
+ return ::Rails.root.to_s if ::Rails.root
32
+ raise "ERROR: Rails.root is nil!"
33
+ end
34
+ return RAILS_ROOT.to_s if defined?(RAILS_ROOT)
35
+ return nil
36
+ end
37
+
38
+ # Returns the environment of the Rails application,
39
+ # if this is running in a Rails context.
40
+ # Returns `nil` if no such environment is defined.
41
+ #
42
+ # @return [String, nil]
43
+ def rails_env
44
+ return ::Rails.env.to_s if defined?(::Rails.env)
45
+ return RAILS_ENV.to_s if defined?(RAILS_ENV)
46
+ return nil
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,171 @@
1
+ require 'zlib'
2
+ require 'fileutils'
3
+ require 'sitemap_generator/helpers/number_helper'
4
+
5
+ module SitemapGenerator
6
+ module Builder
7
+ #
8
+ # General Usage:
9
+ #
10
+ # sitemap = SitemapFile.new(:location => SitemapLocation.new(...))
11
+ # sitemap.add('/', { ... }) <- add a link to the sitemap
12
+ # sitemap.finalize! <- write the sitemap file and freeze the object to protect it from further modification
13
+ #
14
+ class SitemapFile
15
+ include SitemapGenerator::Helpers::NumberHelper
16
+ attr_reader :link_count, :filesize, :location, :news_count
17
+
18
+ # === Options
19
+ #
20
+ # * <tt>location</tt> - a SitemapGenerator::SitemapLocation instance or a Hash of options
21
+ # from which a SitemapLocation will be created for you.
22
+ def initialize(opts={}, schemas)
23
+ @location = opts.is_a?(Hash) ? SitemapGenerator::SitemapLocation.new(opts) : opts
24
+ @link_count = 0
25
+ @news_count = 0
26
+ @xml_content = '' # XML urlset content
27
+ @schemas = schemas
28
+ @xml_wrapper_start = <<-HTML
29
+ "#{all_schemas}"
30
+ HTML
31
+ @xml_wrapper_start.gsub!(/\s+/, ' ').gsub!(/ *> */, '>').strip!
32
+ @xml_wrapper_start = @xml_wrapper_start.slice(1..-2)
33
+ @xml_wrapper_end = %q[</urlset>]
34
+ @filesize = SitemapGenerator::Utilities.bytesize(@xml_wrapper_start) + SitemapGenerator::Utilities.bytesize(@xml_wrapper_end)
35
+ @written = false
36
+ @reserved_name = nil # holds the name reserved from the namer
37
+ @frozen = false # rather than actually freeze, use this boolean
38
+ end
39
+
40
+ def all_schemas
41
+ xml_start = '<?xml version="1.0" encoding="UTF-8"?>
42
+ <urlset
43
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
44
+ xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
45
+ http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
46
+ xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" '
47
+ xml_schemas = (@schemas.collect do |schema, content| "xmlns:#{schema}=\"#{content}\"\n" end).join(" ")
48
+ xml_end = 'xmlns:xhtml="http://www.w3.org/1999/xhtml" >'
49
+ return xml_start + xml_schemas + xml_end
50
+ end
51
+
52
+ # If a name has been reserved, use the last modified time from the file.
53
+ # Otherwise return nil. We don't want to prematurely assign a name
54
+ # for this sitemap if one has not yet been reserved, because we may
55
+ # mess up the name-assignment sequence.
56
+ def lastmod
57
+ File.mtime(location.path) if location.reserved_name?
58
+ rescue
59
+ nil
60
+ end
61
+
62
+ def empty?
63
+ @link_count == 0
64
+ end
65
+
66
+ # Return a boolean indicating whether the sitemap file can fit another link
67
+ # of <tt>bytes</tt> bytes in size. You can also pass a string and the
68
+ # bytesize will be calculated for you.
69
+ def file_can_fit?(bytes)
70
+ bytes = bytes.is_a?(String) ? SitemapGenerator::Utilities.bytesize(bytes) : bytes
71
+ (@filesize + bytes) < SitemapGenerator::MAX_SITEMAP_FILESIZE && @link_count < SitemapGenerator::MAX_SITEMAP_LINKS && @news_count < SitemapGenerator::MAX_SITEMAP_NEWS
72
+ end
73
+
74
+ # Add a link to the sitemap file.
75
+ #
76
+ # If a link cannot be added, for example if the file is too large or the link
77
+ # limit has been reached, a SitemapGenerator::SitemapFullError exception is raised
78
+ # and the sitemap is finalized.
79
+ #
80
+ # If the Sitemap has already been finalized a SitemapGenerator::SitemapFinalizedError
81
+ # exception is raised.
82
+ #
83
+ # Return the new link count.
84
+ #
85
+ # Call with:
86
+ # sitemap_url - a SitemapUrl instance
87
+ # sitemap, options - a Sitemap instance and options hash
88
+ # path, options - a path for the URL and options hash. For supported options
89
+ # see the SitemapGenerator::Builder::SitemapUrl class.
90
+ #
91
+ # The link added to the sitemap will use the host from its location object
92
+ # if no host has been specified.
93
+ def add(link, options={})
94
+ raise SitemapGenerator::SitemapFinalizedError if finalized?
95
+
96
+ sitemap_url = if link.is_a?(SitemapUrl)
97
+ link
98
+ else
99
+ options[:host] ||= @location.host
100
+ SitemapUrl.new(link, options)
101
+ end
102
+
103
+ xml = sitemap_url.to_xml
104
+ raise SitemapGenerator::SitemapFullError if !file_can_fit?(xml)
105
+
106
+ if sitemap_url.news?
107
+ @news_count += 1
108
+ end
109
+
110
+ # Add the XML to the sitemap
111
+ @xml_content << xml
112
+ @filesize += SitemapGenerator::Utilities.bytesize(xml)
113
+ @link_count += 1
114
+ end
115
+
116
+ # "Freeze" this object. Actually just flags it as frozen.
117
+ #
118
+ # A SitemapGenerator::SitemapFinalizedError exception is raised if the Sitemap
119
+ # has already been finalized.
120
+ def finalize!
121
+ raise SitemapGenerator::SitemapFinalizedError if finalized?
122
+ @frozen = true
123
+ end
124
+
125
+ def finalized?
126
+ @frozen
127
+ end
128
+
129
+ # Write out the sitemap and free up memory.
130
+ #
131
+ # All the xml content in the instance is cleared, but attributes like
132
+ # <tt>filesize</tt> are still available.
133
+ #
134
+ # A SitemapGenerator::SitemapError exception is raised if the file has
135
+ # already been written.
136
+ def write
137
+ raise SitemapGenerator::SitemapError.new("Sitemap already written!") if written?
138
+ finalize! unless finalized?
139
+ reserve_name
140
+ @location.write(@xml_wrapper_start + @xml_content + @xml_wrapper_end, link_count)
141
+ @xml_content = @xml_wrapper_start = @xml_wrapper_end = ''
142
+ @written = true
143
+ end
144
+
145
+ # Return true if this file has been written out to disk
146
+ def written?
147
+ @written
148
+ end
149
+
150
+ # Reserve a name from the namer unless one has already been reserved.
151
+ # Safe to call more than once.
152
+ def reserve_name
153
+ @reserved_name ||= @location.reserve_name
154
+ end
155
+
156
+ # Return a boolean indicating whether a name has been reserved
157
+ def reserved_name?
158
+ !!@reserved_name
159
+ end
160
+
161
+ # Return a new instance of the sitemap file with the same options,
162
+ # and the next name in the sequence.
163
+ def new
164
+ location = @location.dup
165
+ location.delete(:filename) if location.namer
166
+ self.class.new(location)
167
+ end
168
+ end
169
+ end
170
+ end
171
+
@@ -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