duck_map 0.8.0

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.
@@ -0,0 +1,146 @@
1
+ module DuckMap
2
+
3
+ class SitemapStaticRequest < ActionDispatch::Request
4
+ end
5
+
6
+ class SitemapStaticResponse < ActionDispatch::Response
7
+ end
8
+
9
+ class ActionViewTestObject < ActionView::Base
10
+ include DuckMap::ActionViewHelpers
11
+ include DuckMap::SitemapControllerHelpers
12
+ include DuckMap::Model
13
+ include DuckMap::SitemapHelpers
14
+
15
+ end
16
+
17
+ ##################################################################################
18
+ class Static
19
+
20
+ ##################################################################################
21
+ def build(options = {})
22
+ key = options[:key].blank? ? :all : options[:key].to_s.downcase.to_sym
23
+
24
+ begin
25
+
26
+ attributes = DuckMap::Config.attributes
27
+
28
+ compressed = attributes[:compression].blank? ? :compressed : attributes[:compression]
29
+
30
+ # command line override config
31
+ compressed = options[:compression].blank? ? compressed : options[:compression]
32
+
33
+ compressed = compressed.blank? ? :compressed : compressed.to_s.downcase.to_sym
34
+ compressed = compressed.eql?(:compressed) ? true : false
35
+
36
+ static_host = attributes[:static_host].blank? ? attributes[:canonical_host] : attributes[:static_host]
37
+
38
+ # command line override config
39
+ static_host = options[:static_host].blank? ? static_host : options[:static_host]
40
+
41
+ static_port = attributes[:static_port].blank? ? attributes[:canonical_port] : attributes[:static_port]
42
+
43
+ # command line override config
44
+ static_port = options[:static_port].blank? ? static_port : options[:static_port]
45
+
46
+ static_target = attributes[:static_target].blank? ? File.join(Rails.root, "public") : attributes[:static_target]
47
+
48
+ # command line override config
49
+ static_target = options[:static_target].blank? ? static_target : options[:static_target]
50
+
51
+ if !static_host.blank?
52
+
53
+ request_options = {
54
+ "rack.input" => StringIO.new,
55
+ "rack.errors" => StringIO.new,
56
+ "rack.multithread" => true,
57
+ "rack.multiprocess" => true,
58
+ "rack.run_once" => false,
59
+ "CONTENT_TYPE" => "application/xml",
60
+ "HTTP_HOST" => static_host,
61
+ "SERVER_PORT" => static_port}
62
+
63
+ DuckMap.console "Searching for route: #{key}"
64
+
65
+ routes = []
66
+ if key.eql?(:all)
67
+
68
+ routes = Rails.application.routes.sitemap_routes_only
69
+
70
+ else
71
+
72
+ route = Rails.application.routes.find_route_via_name("#{key}_sitemap")
73
+ if route.blank?
74
+ DuckMap.console "Unable to find route: #{key}"
75
+ else
76
+ routes.push(route)
77
+ end
78
+
79
+ end
80
+
81
+ routes.each do |route|
82
+
83
+ DuckMap.console "Processing route: #{route.name}"
84
+
85
+ clazz = ClassHelpers.get_controller_class(route.controller_name)
86
+
87
+ if clazz.blank?
88
+ DuckMap.logger.debug "sorry, could not determine controller class...: route name: #{route.name} controller: #{route.controller_name} action: #{route.action_name}"
89
+ else
90
+
91
+ controller = clazz.new
92
+
93
+ controller.request = SitemapStaticRequest.new(request_options)
94
+ controller.response = SitemapStaticResponse.new
95
+
96
+ # man, I am getting lazy...
97
+ request_path = route.path.spec.to_s.gsub(":format", "xml")
98
+
99
+ controller.sitemap_build(request_path)
100
+
101
+ view = ActionViewTestObject.new(File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "app", "views")))
102
+
103
+ view.controller = controller
104
+
105
+ view.assign(sitemap_model: controller.sitemap_model)
106
+
107
+ parts = request_path.split("/")
108
+ parts.insert(0, static_target)
109
+
110
+ file_spec = File.join(parts)
111
+
112
+ File.open(file_spec, "w") do |file|
113
+
114
+ file.write view.render(:template => "sitemap/default_template.xml.erb")
115
+
116
+ end
117
+
118
+ if compressed
119
+
120
+ Zlib::GzipWriter.open("#{file_spec}.gz") do |gz|
121
+ gz.mtime = File.mtime(file_spec)
122
+ gz.orig_name = file_spec
123
+ gz.write IO.binread(file_spec)
124
+ end
125
+
126
+ FileUtils.rm_rf file_spec
127
+
128
+ end
129
+
130
+ end
131
+
132
+ end
133
+
134
+ else
135
+ DuckMap.console "Sorry, cannot build sitemap. You need to set static_host in config/routes.rb or on the command-line..."
136
+ end
137
+
138
+ rescue Exception => e
139
+ puts "#{e.message}"
140
+ puts e.backtrace
141
+ end
142
+
143
+ end
144
+
145
+ end
146
+ end
@@ -0,0 +1,192 @@
1
+ module DuckMap
2
+
3
+ ##################################################################################
4
+ # Extracts the timestamp of each action for all of the controllers within the application
5
+ # and stores it in config/locales/sitemap.yml
6
+ class Sync
7
+
8
+ ##################################################################################
9
+ # Extract and store timestamps for all of the actions in the in app.
10
+ # @param [Hash] options An options Hash passed to the generator via the command line.
11
+ # returns [Nil]
12
+ def build(options = {})
13
+ DuckMap.logger.info "\r\n\r\n====================================================================="
14
+ DuckMap.logger.info "#{self.class.name} session begin at: #{Time.now}"
15
+ DuckMap.logger.info "options: #{options}"
16
+
17
+ verbose = options[:verbose].blank? ? false : true
18
+
19
+ # always replace ALL of the existing content, so, the list is always current.
20
+ # actions / views may have been deleted, renamed, etc.
21
+ content = {sitemap: {}}
22
+ grand_total = 0
23
+
24
+ # controllers full physical path for the current app.
25
+ base_dir = File.join(Rails.root, "app", "controllers").to_s
26
+
27
+ # this is a little bit complex.
28
+ # - traverse the entire directory structure for controllers.
29
+ # - for each
30
+ # - inline gsub! to remove the base_dir.
31
+ # - inline gsub! to remove _controller.
32
+ # - then, prevent the application controller from making it into the list as
33
+ # there should not be any actions / views for the application controller.
34
+
35
+
36
+ list = Dir["#{File.join(base_dir, "**", "*.rb")}"].each {|f| f.gsub!("#{base_dir}/", "").gsub!("_controller.rb", "")}.map.find_all {|x| x.eql?("application") ? false : true}
37
+
38
+ DuckMap.console "Total number of controllers found within the app: #{list.length}"
39
+ DuckMap.console "Views Controller"
40
+ DuckMap.console "-------------------------------------------------------------"
41
+
42
+ DuckMap.logger.show_console = verbose ? true : false
43
+
44
+ list.each do |controller|
45
+
46
+ file_spec = File.join("app", "views", controller)
47
+
48
+ clazz = "#{controller}_controller".camelize.constantize
49
+
50
+ DuckMap.console "===================================================================================="
51
+ DuckMap.console "controller: #{controller.ljust(30)} path: #{file_spec}"
52
+
53
+ actions = {}
54
+ methods = clazz.action_methods
55
+
56
+ methods.each do |action|
57
+
58
+ latest_timestamp = nil
59
+ winner = nil
60
+
61
+ DuckMap.console " action: #{action}"
62
+
63
+ Dir["#{File.join(file_spec, action)}.html.*"].each do |view|
64
+
65
+ begin
66
+ view_timestamp = File.stat(File.join(Rails.root, view)).mtime
67
+ if view_timestamp.blank?
68
+ DuckMap.console " : cannot stat file: #{view}"
69
+ else
70
+
71
+ if latest_timestamp.blank? || view_timestamp > latest_timestamp
72
+ latest_timestamp = view_timestamp
73
+ winner = "timestamp from file"
74
+ DuckMap.console " : Using file timestamp? YES #{view}"
75
+ DuckMap.console " : file timestamp #{view_timestamp} greater than: #{latest_timestamp}"
76
+ else
77
+ DuckMap.console " : Using file timestamp? NO #{view}"
78
+ DuckMap.console " : file timestamp #{view_timestamp} less than: #{latest_timestamp}"
79
+ end
80
+
81
+ end
82
+ rescue Exception => e
83
+ DuckMap.logger.debug "#{e}"
84
+ end
85
+
86
+ begin
87
+ output = %x(git log --format='%ci' -- #{view} | head -n 1)
88
+ if output.blank?
89
+ DuckMap.console " : cannot get date from GIT for file: #{view}"
90
+ else
91
+
92
+ view_timestamp = LastMod.to_date(output)
93
+ if latest_timestamp.blank? || view_timestamp > latest_timestamp
94
+ latest_timestamp = view_timestamp
95
+ winner = "timestamp from GIT"
96
+ DuckMap.console " : Using GIT timestamp? YES #{view}"
97
+ DuckMap.console " : file timestamp #{view_timestamp} greater than: #{latest_timestamp}"
98
+ else
99
+ DuckMap.console " : Using GIT timestamp? NO #{view}"
100
+ DuckMap.console " : file timestamp #{view_timestamp} less than: #{latest_timestamp}"
101
+ end
102
+
103
+ end
104
+ rescue Exception => e
105
+ DuckMap.logger.debug "#{e}"
106
+ end
107
+
108
+ begin
109
+ view_timestamp = LastMod.to_date(I18n.t("#{controller.gsub("/", ".")}.#{action}", :locale => :sitemap))
110
+
111
+ if view_timestamp.blank?
112
+ DuckMap.console " : cannot find item in locale sitemap.yml for file: #{view}"
113
+ else
114
+
115
+ view_timestamp = LastMod.to_date(output)
116
+ if latest_timestamp.blank? || view_timestamp > latest_timestamp
117
+ latest_timestamp = view_timestamp
118
+ winner = "timestamp from locale sitemap.yml"
119
+ DuckMap.console " : Using locale timestamp? YES #{view}"
120
+ DuckMap.console " : file timestamp #{view_timestamp} greater than: #{latest_timestamp}"
121
+ else
122
+ DuckMap.console " : Using locale timestamp? NO #{view}"
123
+ DuckMap.console " : file timestamp #{view_timestamp} less than: #{latest_timestamp}"
124
+ end
125
+
126
+ end
127
+
128
+ rescue Exception => e
129
+ DuckMap.logger.debug "#{e}"
130
+ end
131
+
132
+ DuckMap.console " : winner #{winner}"
133
+
134
+ begin
135
+ actions.merge!(action => latest_timestamp.strftime("%m/%d/%Y %H:%M:%S"))
136
+ rescue Exception => e
137
+ DuckMap.logger.debug "#{e}"
138
+ end
139
+
140
+ end
141
+ end
142
+
143
+ # add all of the actions found for the current controller to the :sitemap section of the yaml file.
144
+ if actions.length > 0
145
+ grand_total += actions.length
146
+ namespaces = controller.split("/")
147
+ content[:sitemap] = merge_controller(content[:sitemap], namespaces, 0, actions)
148
+
149
+ DuckMap.logger.show_console = true
150
+ DuckMap.console "#{actions.length.to_s.rjust(5)} #{controller}"
151
+ DuckMap.logger.show_console = verbose ? true : false
152
+
153
+ end
154
+
155
+ end
156
+
157
+ DuckMap.logger.show_console = true
158
+ DuckMap.console "\r\nTotal number of views synchronized: #{grand_total}\r\n"
159
+ DuckMap.logger.info "#{self.class.name} session end at: #{Time.now}"
160
+ DuckMap.logger.info "---------------------------------------------------------------------"
161
+
162
+ # done.
163
+ write_config(content)
164
+ DuckMap.console "done."
165
+
166
+ end
167
+
168
+ ##################################################################################
169
+ # Recursive method
170
+ def merge_controller(content, namespaces, index, values = {})
171
+
172
+ if (index == (namespaces.length - 1))
173
+ content[namespaces[index]] = {} unless content.has_key?(namespaces[index])
174
+ content[namespaces[index]].merge!(values)
175
+ else
176
+ content[namespaces[index]] = {} unless content.has_key?(namespaces[index])
177
+ content[namespaces[index]] = merge_controller(content[namespaces[index]], namespaces, index + 1, values)
178
+ end
179
+ return content
180
+ end
181
+
182
+ ##################################################################################
183
+ def write_config(values = {})
184
+ File.open(File.join(Rails.root, "config", "locales", "sitemap.yml"), "w") do |file|
185
+ file.puts "# Although this file is automatically generated, it is ok to edit."
186
+ YAML.dump(values, file)
187
+ end
188
+ end
189
+
190
+ end
191
+
192
+ end
@@ -0,0 +1,3 @@
1
+ module DuckMap
2
+ VERSION = "0.8.0"
3
+ end
@@ -0,0 +1,107 @@
1
+ # DONE
2
+ require 'active_support/concern'
3
+
4
+ module DuckMap
5
+
6
+ ##################################################################################
7
+ # Contains methods for generating the contants of a sitemap.
8
+ module SitemapHelpers
9
+ extend ActiveSupport::Concern
10
+
11
+ ##################################################################################
12
+ # View helper method to generate the content of a sitemap. Loops through all of the current
13
+ # Hashes contained in {DuckMap::Model#sitemap_model}.
14
+ # @param [Block] A block to execute for each row contained in {DuckMap::Model#sitemap_model}.
15
+ #
16
+ #
17
+ # To see it in action, have a look at the file.
18
+ #
19
+ # /app/views/sitemap/default_template.xml.erb
20
+ #
21
+ # @return [NilClass]
22
+ def sitemap_content(&block)
23
+
24
+ if defined?(self.sitemap_model) && self.sitemap_model.kind_of?(Array)
25
+
26
+ self.sitemap_model.each do |row|
27
+ block.call(row)
28
+ end
29
+
30
+ end
31
+
32
+ return nil
33
+ end
34
+
35
+ end
36
+
37
+ ##################################################################################
38
+ # Support for seo related meta tags in page headers.
39
+ module ActionViewHelpers
40
+ extend ActiveSupport::Concern
41
+
42
+ ##################################################################################
43
+ # Generates a title tag for use inside HTML header area.
44
+ # @return [String] HTML safe title tag.
45
+ def sitemap_meta_title
46
+ return controller.sitemap_meta_data[:title].blank? ? nil : content_tag(:title, controller.sitemap_meta_data[:title], false)
47
+ end
48
+
49
+ def sitemap_meta_title=(value)
50
+ controller.sitemap_meta_data[:title] = value
51
+ end
52
+
53
+ ##################################################################################
54
+ # Generates a keywords meta tag for use inside HTML header area.
55
+ # @return [String] HTML safe keywords meta tag.
56
+ def sitemap_meta_keywords
57
+ return controller.sitemap_meta_data[:keywords].blank? ? nil : tag(:meta, {name: :keywords, content: controller.sitemap_meta_data[:keywords]}, false, false)
58
+ end
59
+
60
+ def sitemap_meta_keywords=(value)
61
+ controller.sitemap_meta_data[:keywords] = value
62
+ end
63
+
64
+ ##################################################################################
65
+ # Generates a description meta tag for use inside HTML header area.
66
+ # @return [String] HTML safe description meta tag.
67
+ def sitemap_meta_description
68
+ return controller.sitemap_meta_data[:description].blank? ? nil : tag(:meta, {name: :description, content: controller.sitemap_meta_data[:description]}, false, false)
69
+ end
70
+
71
+ def sitemap_meta_description=(value)
72
+ controller.sitemap_meta_data[:description] = value
73
+ end
74
+
75
+ ##################################################################################
76
+ # Generates a Last-Modified meta tag for use inside HTML header area.
77
+ # @return [String] HTML safe Last-Modified meta tag.
78
+ def sitemap_meta_lastmod
79
+ return controller.sitemap_meta_data[:lastmod].blank? ? nil : tag(:meta, {name: "Last-Modified", content: controller.sitemap_meta_data[:lastmod]}, false, false)
80
+ end
81
+
82
+ def sitemap_meta_lastmod=(value)
83
+ controller.sitemap_meta_data[:lastmod] = value
84
+ end
85
+
86
+ ##################################################################################
87
+ # Generates a canonical link tag for use inside HTML header area.
88
+ # @return [String] HTML safe canonical link tag.
89
+ def sitemap_meta_canonical
90
+ return controller.sitemap_meta_data[:canonical].blank? ? nil : tag(:link, {rel: :canonical, href: controller.sitemap_meta_data[:canonical]}, false, false)
91
+ end
92
+
93
+ def sitemap_meta_canonical=(value)
94
+ controller.sitemap_meta_data[:canonical] = value
95
+ end
96
+
97
+ ##################################################################################
98
+ # Generates a meta tags title, keywords, description and Last-Modified for use inside HTML header area.
99
+ # @return [String] HTML safe title tag.
100
+ def sitemap_meta_tag
101
+ buffer = "#{self.sitemap_meta_title}\r\n #{self.sitemap_meta_keywords}\r\n #{self.sitemap_meta_description}\r\n #{self.sitemap_meta_lastmod}\r\n #{self.sitemap_meta_canonical}\r\n".html_safe
102
+ return buffer.html_safe
103
+ end
104
+
105
+ end
106
+
107
+ end
data/lib/duck_map.rb ADDED
@@ -0,0 +1,3 @@
1
+ module DuckMap
2
+ require "duck_map/engine" if defined?(Rails)
3
+ end
@@ -0,0 +1,23 @@
1
+ Description:
2
+
3
+ Lists all of the sitemap route(s) configured via config/routes.rb
4
+
5
+ You have the option of including the route(s) contained within a sitemap. This can be helpful for debugging.
6
+
7
+ If you specify a single route name, then, displaying the route(s) contained within the sitemap is implied and
8
+
9
+ overrides the --contents option.
10
+
11
+ Example:
12
+
13
+ # Lists all of the sitemap route(s) configured via config/routes.rb
14
+
15
+ rails g duckmap:sitemaps
16
+
17
+ # Lists all of the sitemap route(s) configured via config/routes.rb including route(s) contained in each sitemap route.
18
+
19
+ rails g duckmap:sitemaps --contents
20
+
21
+ # Lists the route(s) contained within sitemap route named mysitemap.
22
+
23
+ rails g duckmap:sitemaps mysitemap
@@ -0,0 +1,36 @@
1
+ require "rails/generators"
2
+
3
+ module Duckmap
4
+ module Generators
5
+
6
+ class SitemapsGenerator < Rails::Generators::Base
7
+
8
+ argument :key,
9
+ banner: "[sitemap route name]",
10
+ desc: "[sitemap route name]",
11
+ default: :all,
12
+ required: false,
13
+ optional: true
14
+
15
+ class_option :contents, desc: "Include the routes contained in a sitemap"
16
+
17
+ def self.source_root
18
+ File.join(File.dirname(__FILE__), "templates")
19
+ end
20
+
21
+ def build
22
+
23
+ config = {key: key}
24
+
25
+ unless options[:contents].blank?
26
+ config[:contents] = options[:contents]
27
+ end
28
+
29
+ DuckMap::List.new.build(config)
30
+
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,47 @@
1
+ Description:
2
+
3
+ Generates a sitemap(s) based on :key. The default key is: all
4
+
5
+ Key is the route name of a sitemap route defined in config/routes.rb
6
+
7
+ To list all of the available sitemaps including name and contents use: rails g duckmap:sitemaps
8
+
9
+ You can override the static_host and static_port settings in config/routes.rb by passing them
10
+
11
+ to the generator on the command line.
12
+
13
+ Also, you can specify the target base directory to use when writing files. The default target is the public
14
+
15
+ directory of your Rails app.
16
+
17
+ #{Rails.root}/public/sitemap.xml
18
+
19
+ Example:
20
+
21
+ # generates all sitemaps configured via config/routes.rb
22
+
23
+ rails g duckmap:static
24
+
25
+ - or -
26
+
27
+ rails g duckmap:static all
28
+
29
+ # generates a specific sitemap using the route name.
30
+
31
+ rails g duckmap:static mysitemap
32
+
33
+ # overrides static_host and static_port defined in config/routes.rb
34
+
35
+ rails g duckmap:static mysitemap --static-host=example.com --static-port=8080
36
+
37
+ # generates and compresses the output file(s). Overrides setting in config/routes.rb
38
+
39
+ rails g duckmap:static mysitemap --compression=compressed
40
+
41
+ # disables compression for the output file(s). Overrides setting in config/routes.rb
42
+
43
+ rails g duckmap:static mysitemap --compression=none
44
+
45
+ # generates sitemap files in a specific target directory
46
+
47
+ rails g duckmap:static mysitemap --static-target=/tmp/mysitemaps
@@ -0,0 +1,51 @@
1
+ require "rails/generators"
2
+
3
+ module Duckmap
4
+ module Generators
5
+
6
+ class StaticGenerator < Rails::Generators::Base
7
+
8
+ argument :key,
9
+ banner: "[sitemap route name]",
10
+ desc: "[sitemap route name]",
11
+ default: :all,
12
+ required: false,
13
+ optional: true
14
+
15
+ class_option :compression, desc: "Indicates if the generated file(s) should be compressed"
16
+ class_option :static_host, desc: "Static hostname to use when building <url><loc> nodes"
17
+ class_option :static_port, desc: "Static port to use when building <url><loc> nodes"
18
+ class_option :static_target, desc: "Target base directory to write sitemap files."
19
+
20
+ def self.source_root
21
+ File.join(File.dirname(__FILE__), "templates")
22
+ end
23
+
24
+ def build
25
+
26
+ config = {key: key}
27
+
28
+ unless options[:compression].blank?
29
+ config[:compression] = options[:compression]
30
+ end
31
+
32
+ unless options[:static_host].blank?
33
+ config[:static_host] = options[:static_host]
34
+ end
35
+
36
+ unless options[:static_port].blank?
37
+ config[:static_port] = options[:static_port]
38
+ end
39
+
40
+ unless options[:static_target].blank?
41
+ config[:static_target] = options[:static_target]
42
+ end
43
+
44
+ DuckMap::Static.new.build(config)
45
+
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ Description:
2
+
3
+ Synchronizes date/time stamps of all views for a Rails app.
4
+
5
+ There are three sources of timestamps for views.
6
+
7
+ - The physical timestamp of the view file on disk.
8
+
9
+ - The timestamp of the view found in the local Git repo for the app.
10
+
11
+ - The last known timestamp already stored in config/locales/sitemaps.yml
12
+
13
+ All three sources are compared and the source that has the latest timestamp wins and that value
14
+
15
+ is stored in config/locales/sitemaps.yml
16
+
17
+ Example:
18
+
19
+ # Performs a synchronization
20
+
21
+ rails g duckmap:sync
22
+
23
+ # Performs a synchronization and shows all activity
24
+
25
+ rails g duckmap:sync --verbose
@@ -0,0 +1,29 @@
1
+ require "rails/generators"
2
+
3
+ module Duckmap
4
+ module Generators
5
+
6
+ class SyncGenerator < Rails::Generators::Base
7
+
8
+ class_option :verbose, desc: "Show all operations"
9
+
10
+ def self.source_root
11
+ File.join(File.dirname(__FILE__), "templates")
12
+ end
13
+
14
+ def build
15
+
16
+ config = {}
17
+
18
+ unless options[:verbose].blank?
19
+ config[:verbose] = options[:verbose]
20
+ end
21
+
22
+ DuckMap::Sync.new.build(config)
23
+
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end