roda-route_list 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []