roda-route_list 1.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 22860ddac7c2f5805445a274c59217bfd1b829f1
4
+ data.tar.gz: 75976d49b266e7f6a4783ba4a4e8376ce7807873
5
+ SHA512:
6
+ metadata.gz: 676bb12a6726a91ea0c782e18bcb7f0d4c925748129e706abec2ae0e3d3a4b19c14c1aa52125b11c10fbd01a3240dbc3899a93fb29caec8de6370f2f15f46cdf
7
+ data.tar.gz: f026084da9cd254125f26abd4e3efe7b45703cdbd1ed0436669c9ef10aac413a0c8f57a727630fab3b4f2ba0561d45d53cbd6a2869fdfe228ceab45406e68c1e
data/CHANGELOG ADDED
@@ -0,0 +1,3 @@
1
+ = 1.0.0 (2015-03-08)
2
+
3
+ * Initial public release
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2015 Jeremy Evans
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,98 @@
1
+ = roda-route_list
2
+
3
+ The Roda route_list plugin reads route information from a json
4
+ file, and then makes the route metadata available for
5
+ introspection. This provides a workaround to the general
6
+ issue of routing trees being unable to introspect the routes.
7
+
8
+ == Installation
9
+
10
+ gem install roda-route_list
11
+
12
+ == Source Code
13
+
14
+ Source code is available on GitHub at https://github.com/jeremyevans/roda-route_list
15
+
16
+ == Basic Usage
17
+
18
+ This plugin assumes that a json file containing the routes
19
+ metadata has already been created. The recommended way to
20
+ create one is to add comments above each route in the Roda
21
+ app, in one of the following formats:
22
+
23
+ # route: /path/to/foo
24
+ # route: GET /path/to/foo
25
+ # route: GET|POST /path/to/foo/:foo_id
26
+ # route[route_name]: /path/to/foo
27
+ # route[route_name]: GET /path/to/foo
28
+ # route[foo]: GET|POST /path/to/foo/:foo_id
29
+
30
+ As you can see, the general style is a comment followed by
31
+ the word route. If you want to name the route, you can
32
+ put the name in brackets. Then you have a colon. Optionally
33
+ after that you can have the method for the route, or multiple
34
+ methods separated by pipes if the path works with multiple
35
+ methods. The end is the path for the route.
36
+
37
+ Assuming you have added the appropriate comments as explained
38
+ above, you can create the json file using the roda-parse_routes
39
+ executable that came with the roda-route_list gem:
40
+
41
+ roda-parse_routes -f routes.json app.rb
42
+
43
+ Assuming you have the necessary json file created, you can then
44
+ get route information:
45
+
46
+ plugin :route_list
47
+
48
+ # Array of route metadata hashes
49
+ route_list # => [{:path=>'/path/to/foo', :methods=>['GET', 'POST']}]
50
+
51
+ # path for the route with the given name
52
+ named_route(:route_name) # => '/path/to/foo'
53
+
54
+ # path for the route with the given name, supplying hash for placeholders
55
+ named_route(:foo, :foo_id=>3) # => '/path/to/foo/3'
56
+
57
+ # path for the route with the given name, supplying array for placeholders
58
+ named_route(:foo, [3]) # => '/path/to/foo/3'
59
+
60
+ The +named_route+ method is also available at the instance level to make it
61
+ easier to use inside the route block.
62
+
63
+ === Automatically Updating the Routes Metadata
64
+
65
+ ==== On Heroku
66
+
67
+ You can get this to work on Heroku by hooking into the facility for
68
+ precompiling assets. If this consider your route list an asset, this
69
+ makes sense. You just need to add an assets:precompile task, similar to
70
+ this (or add the code an existing assets:precompile task):
71
+
72
+ namespace :assets do
73
+ desc "Update the routes metadata"
74
+ task :precompile do
75
+ sh 'roda-parse_routes -f routes.json app.rb'
76
+ end
77
+ end
78
+
79
+ ==== Otherwise
80
+
81
+ At the top of your Roda app file, or at least before you create the Roda app
82
+ subclass, just call the <tt>roda-parse_routes</tt> program:
83
+
84
+ # app.rb
85
+ system 'roda-parse_routes', '-f', 'routes.json', __FILE__
86
+ require 'roda'
87
+
88
+ class App < Roda
89
+ # ...
90
+ end
91
+
92
+ == License
93
+
94
+ MIT
95
+
96
+ == Maintainer
97
+
98
+ Jeremy Evans <code@jeremyevans.net>
data/Rakefile ADDED
@@ -0,0 +1,69 @@
1
+ require "rake"
2
+ require "rake/clean"
3
+
4
+ CLEAN.include ["rdoc", "roda-route_list-*.gem"]
5
+
6
+ desc "Build roda-route_list gem"
7
+ task :package=>[:clean] do |p|
8
+ sh %{#{FileUtils::RUBY} -S gem build roda-route_list.gemspec}
9
+ end
10
+
11
+ ### Specs
12
+
13
+ begin
14
+ begin
15
+ # RSpec 1
16
+ require "spec/rake/spectask"
17
+ spec_class = Spec::Rake::SpecTask
18
+ spec_files_meth = :spec_files=
19
+ rescue LoadError
20
+ # RSpec 2
21
+ require "rspec/core/rake_task"
22
+ spec_class = RSpec::Core::RakeTask
23
+ spec_files_meth = :pattern=
24
+ end
25
+
26
+ spec = lambda do |name, files, d|
27
+ lib_dir = File.join(File.dirname(File.expand_path(__FILE__)), 'lib')
28
+ ENV['RUBYLIB'] ? (ENV['RUBYLIB'] += ":#{lib_dir}") : (ENV['RUBYLIB'] = lib_dir)
29
+ desc d
30
+ spec_class.new(name) do |t|
31
+ ENV['RUBY'] = FileUtils::RUBY
32
+ t.send(spec_files_meth, files)
33
+ end
34
+ end
35
+
36
+ task :default => [:spec]
37
+ spec.call("spec", Dir["spec/*_spec.rb"], "Run specs")
38
+ rescue LoadError
39
+ task :default do
40
+ puts "Must install rspec to run the default task (which runs specs)"
41
+ end
42
+ end
43
+
44
+ ### RDoc
45
+
46
+ RDOC_DEFAULT_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', 'roda-route_list: List routes when using Roda']
47
+
48
+ begin
49
+ gem 'hanna-nouveau'
50
+ RDOC_DEFAULT_OPTS.concat(['-f', 'hanna'])
51
+ rescue Gem::LoadError
52
+ end
53
+
54
+ rdoc_task_class = begin
55
+ require "rdoc/task"
56
+ RDoc::Task
57
+ rescue LoadError
58
+ require "rake/rdoctask"
59
+ Rake::RDocTask
60
+ end
61
+
62
+ RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
63
+
64
+ rdoc_task_class.new do |rdoc|
65
+ rdoc.rdoc_dir = "rdoc"
66
+ rdoc.options += RDOC_OPTS
67
+ rdoc.rdoc_files.add %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb"
68
+ end
69
+
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'json'
5
+ require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'lib', 'roda-route_parser')
6
+
7
+ file = $stdout
8
+ options = OptionParser.new do |opts|
9
+ opts.banner = "roda-parse_routes: Parse route comments from roda app files"
10
+ opts.define_head "Usage: roda-parse_routes [options] [file] ..."
11
+ opts.separator "Options:"
12
+
13
+ opts.on_tail("-h", "-?", "--help", "Show this message") do
14
+ puts opts
15
+ exit
16
+ end
17
+
18
+ opts.on("-f", "--file ", "output to given file instead of stdout") do |v|
19
+ file = File.open(v, 'wb')
20
+ end
21
+ end
22
+ opts = options
23
+ opts.parse!
24
+
25
+ file.puts(RodaRouteParser.parse(ARGF).to_json)
@@ -0,0 +1,33 @@
1
+ class RodaRouteParser
2
+ def self.parse(input)
3
+ new.parse(input)
4
+ end
5
+
6
+ def parse(input)
7
+ if input.is_a?(String)
8
+ require 'stringio'
9
+ return parse(StringIO.new(input))
10
+ end
11
+
12
+ routes = []
13
+ regexp = /\A\s*#\s*route(?:\[(\w+)\])?:\s+(?:([A-Z|]+)?\s+)?(\S+)\s*\z/
14
+ input.each_line do |line|
15
+ if md = regexp.match(line)
16
+ name, methods, route = md.captures
17
+ route = {'path'=>route}
18
+
19
+ if methods
20
+ route['methods'] = methods.split('|').compact
21
+ end
22
+
23
+ if name
24
+ route['name'] = name
25
+ end
26
+
27
+ routes << route
28
+ end
29
+ end
30
+
31
+ routes
32
+ end
33
+ end
@@ -0,0 +1,147 @@
1
+ require 'json'
2
+
3
+ class Roda
4
+ module RodaPlugins
5
+ # The route_list plugin reads route information from a json
6
+ # file, and then makes the route metadata available for
7
+ # introspection. This provides a workaround to the general
8
+ # issue of routing trees being unable to introspect the routes.
9
+ #
10
+ # This plugin assumes that a json file containing the routes
11
+ # metadata has already been created. The recommended way to
12
+ # create one is to add comments above each route in the Roda
13
+ # app, in one of the following formats:
14
+ #
15
+ # # route: /path/to/foo
16
+ # # route: GET /path/to/foo
17
+ # # route: GET|POST /path/to/foo/:foo_id
18
+ # # route[route_name]: /path/to/foo
19
+ # # route[route_name]: GET /path/to/foo
20
+ # # route[foo]: GET|POST /path/to/foo/:foo_id
21
+ #
22
+ # As you can see, the general style is a comment followed by
23
+ # the word route. If you want to name the route, you can
24
+ # put the name in brackets. Then you have a colon. Optionally
25
+ # after that you can have the method for the route, or multiple
26
+ # methods separated by pipes if the path works with multiple
27
+ # methods. The end is the path for the route.
28
+ #
29
+ # Assuming you have added the appropriate comments as explained
30
+ # above, you can create the json file using the roda-route_parser
31
+ # executable that came with the roda-route_list gem:
32
+ #
33
+ # roda-route_parser -f routes.json app.rb
34
+ #
35
+ # Assuming you have the necessary json file created, you can then
36
+ # get route information:
37
+ #
38
+ # plugin :route_list
39
+ #
40
+ # # Array of route metadata hashes
41
+ # route_list # => [{:path=>'/path/to/foo', :methods=>['GET', 'POST']}]
42
+ #
43
+ # # path for the route with the given name
44
+ # named_route(:route_name) # => '/path/to/foo'
45
+ #
46
+ # # path for the route with the given name, supplying hash for placeholders
47
+ # named_route(:foo, :foo_id=>3) # => '/path/to/foo/3'
48
+ #
49
+ # # path for the route with the given name, supplying array for placeholders
50
+ # named_route(:foo, [3]) # => '/path/to/foo/3'
51
+ #
52
+ # The +named_route+ method is also available at the instance level to make it
53
+ # easier to use inside the route block.
54
+ module RouteList
55
+ # Set the file to load the routes metadata from. Options:
56
+ # :file :: The JSON file containing the routes metadata (default: 'routes.json')
57
+ def self.configure(app, opts={})
58
+ file = File.expand_path(opts.fetch(:file, 'routes.json'), app.opts[:root])
59
+ app.send(:load_routes, file)
60
+ end
61
+
62
+ module ClassMethods
63
+ # Array of route metadata hashes.
64
+ attr_reader :route_list
65
+
66
+ # Return the path for the given named route. If args is not given,
67
+ # this returns the path directly. If args is a hash, any placeholder
68
+ # values in the path are replaced with the matching values in args.
69
+ # If args is an array, placeholder values are taken from the array
70
+ # in order.
71
+ def named_route(name, args=nil)
72
+ unless path = @route_list_names[name]
73
+ raise RodaError, "no route exists with the name: #{name.inspect}"
74
+ end
75
+
76
+ if args
77
+ if args.is_a?(Hash)
78
+ range = 1..-1
79
+ path = path.gsub(/:[^\/]+/) do |match|
80
+ unless value = args[match[range].to_sym]
81
+ raise RodaError, "no matching value exists in the hash for named route #{name}: #{match}"
82
+ end
83
+ value
84
+ end
85
+ else
86
+ values = args.dup
87
+ path = path.gsub(/:[^\/]+/) do |match|
88
+ if values.empty?
89
+ raise RodaError, "not enough placeholder values provided for named route #{name}: #{match}"
90
+ end
91
+ values.shift
92
+ end
93
+
94
+ unless values.empty?
95
+ raise RodaError, "too many placeholder values provided for named route #{name}"
96
+ end
97
+ end
98
+ end
99
+
100
+ path
101
+ end
102
+
103
+ private
104
+
105
+ # Load the route metadata from the given json file.
106
+ def load_routes(file)
107
+ @route_list_names = {}
108
+
109
+ routes = JSON.parse(File.read(file))
110
+ @route_list = routes.map do |r|
111
+ path = r['path'].freeze
112
+ route = {:path=>path}
113
+
114
+ if methods = r['methods']
115
+ route[:methods] = methods.map{|x| x.to_sym}
116
+ end
117
+
118
+ if name = r['name']
119
+ name = name.to_sym
120
+ route[:name] = name.to_sym
121
+ @route_list_names[name] = path
122
+ end
123
+
124
+ route.freeze
125
+ end.freeze
126
+
127
+ @route_list_names.freeze
128
+
129
+ nil
130
+ end
131
+ end
132
+
133
+ module InstanceMethods
134
+ # Calls the app's named_route method. If the app's :add_script_name option
135
+ # has been setting, prefixes the resulting path with the script name.
136
+ def named_route(name, args=nil)
137
+ app = self.class
138
+ path = app.named_route(name, args)
139
+ path = request.script_name.to_s + path if app.opts[:add_script_name]
140
+ path
141
+ end
142
+ end
143
+ end
144
+
145
+ register_plugin(:route_list, RouteList)
146
+ end
147
+ end
@@ -0,0 +1,119 @@
1
+ require 'roda'
2
+ require 'json'
3
+
4
+ if defined?(RSpec)
5
+ require 'rspec/version'
6
+ if RSpec::Version::STRING >= '2.11.0'
7
+ RSpec.configure do |config|
8
+ config.expect_with :rspec do |c|
9
+ c.syntax = :should
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ describe 'roda-route_list plugin' do
16
+ def req(path='/', env={})
17
+ if path.is_a?(Hash)
18
+ env = path
19
+ else
20
+ env['PATH_INFO'] = path
21
+ end
22
+
23
+ env = {"REQUEST_METHOD" => "GET", "PATH_INFO" => "/", "SCRIPT_NAME" => ""}.merge(env)
24
+ @app.call(env)
25
+ end
26
+
27
+ def body(path='/', env={})
28
+ s = ''
29
+ b = req(path, env)[2]
30
+ b.each{|x| s << x}
31
+ b.close if b.respond_to?(:close)
32
+ s
33
+ end
34
+
35
+ before do
36
+ @app = Class.new(Roda)
37
+ @app.plugin :route_list, :file=>'spec/routes.json'
38
+ @app.route do |r|
39
+ named_route(env['PATH_INFO'].to_sym)
40
+ end
41
+ @app
42
+ end
43
+
44
+ after do
45
+ File.delete('routes.json') if File.exist?('routes.json')
46
+ end
47
+
48
+ it "should correctly parse the routes from the json file" do
49
+ @app.route_list.should == [
50
+ {:path=>'/foo'},
51
+ {:path=>'/foo/bar', :name=>:bar},
52
+ {:path=>'/foo/baz', :methods=>[:GET]},
53
+ {:path=>'/foo/baz/quux/:quux_id', :name=>:quux, :methods=>[:GET, :POST]},
54
+ ]
55
+ end
56
+
57
+ it "should respect :root option when parsing json file" do
58
+ @app = Class.new(Roda)
59
+ @app.opts[:root] = 'spec'
60
+ @app.plugin :route_list, :file=>'routes2.json'
61
+ @app.route_list.should == [{:path=>'/foo'}]
62
+ end
63
+
64
+ it ".named_route should return path for route" do
65
+ @app.named_route(:bar).should == '/foo/bar'
66
+ @app.named_route(:quux).should == '/foo/baz/quux/:quux_id'
67
+ end
68
+
69
+ it ".named_route should return path for route when given a values hash" do
70
+ @app.named_route(:quux, :quux_id=>3).should == '/foo/baz/quux/3'
71
+ end
72
+
73
+ it ".named_route should return path for route when given a values array" do
74
+ @app.named_route(:quux, [3]).should == '/foo/baz/quux/3'
75
+ end
76
+
77
+ it ".named_route should raise RodaError if there is no matching route" do
78
+ proc{@app.named_route(:foo)}.should raise_error(Roda::RodaError)
79
+ end
80
+
81
+ it ".named_route should raise RodaError if there is no matching value when using a values hash" do
82
+ proc{@app.named_route(:quux, {})}.should raise_error(Roda::RodaError)
83
+ end
84
+
85
+ it ".named_route should raise RodaError if there is no matching value when using a values array" do
86
+ proc{@app.named_route(:quux, [])}.should raise_error(Roda::RodaError)
87
+ end
88
+
89
+ it ".named_route should raise RodaError if there are too many values when using a values array" do
90
+ proc{@app.named_route(:quux, [3, 1])}.should raise_error(Roda::RodaError)
91
+ end
92
+
93
+ it "should allow parsing routes from a separate file" do
94
+ @app.plugin :route_list, :file=>'spec/routes2.json'
95
+ @app.route_list.should == [{:path=>'/foo'}]
96
+ end
97
+
98
+ it "#named_route should work" do
99
+ body('bar').should == '/foo/bar'
100
+ end
101
+
102
+ it "#named_route should respect :add_script_name option" do
103
+ @app.opts[:add_script_name] = true
104
+ body('bar').should == '/foo/bar'
105
+ body('bar', 'SCRIPT_NAME'=>'/a').should == '/a/foo/bar'
106
+ end
107
+ end
108
+
109
+ describe 'roda-route_parser executable' do
110
+ after do
111
+ File.delete "spec/routes-example.json"
112
+ end
113
+
114
+ it "should correctly parse the routes" do
115
+ system(ENV['RUBY'], "bin/roda-parse_routes", "-f", "spec/routes-example.json", "spec/routes.example")
116
+ File.file?("spec/routes-example.json").should == true
117
+ JSON.parse(File.read('spec/routes-example.json')).should == JSON.parse(File.read('spec/routes.json'))
118
+ end
119
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: roda-route_list
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jeremy Evans
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: roda
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ description: |
42
+ Roda, like other routing tree web frameworks, doesn't have the ability
43
+ to introspect routes. roda-route_list offers a way to specify a json
44
+ file containing the route metadata, which the route_list plugin will
45
+ read. It also offers a roda-parse_routes binary that can parse routes
46
+ out of roda app files, if those app files contain comments specifying
47
+ the routes.
48
+ email: code@jeremyevans.net
49
+ executables:
50
+ - roda-parse_routes
51
+ extensions: []
52
+ extra_rdoc_files:
53
+ - README.rdoc
54
+ - CHANGELOG
55
+ - MIT-LICENSE
56
+ files:
57
+ - CHANGELOG
58
+ - MIT-LICENSE
59
+ - README.rdoc
60
+ - Rakefile
61
+ - bin/roda-parse_routes
62
+ - lib/roda-route_parser.rb
63
+ - lib/roda/plugins/route_list.rb
64
+ - spec/roda-route_list_spec.rb
65
+ homepage: http://github.com/jeremyevans/roda-route_list
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options:
71
+ - "--quiet"
72
+ - "--line-numbers"
73
+ - "--inline-source"
74
+ - "--title"
75
+ - 'roda-route_list: List routes when using Roda'
76
+ - "--main"
77
+ - README.rdoc
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 1.8.7
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.4.5
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: List routes when using Roda
96
+ test_files: []