hubert 0.0.1

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 82674970a036232aa45dbf9a07b2ef0f0650628c
4
+ data.tar.gz: a944796e70aa1b5079927533137683daa14c41c8
5
+ SHA512:
6
+ metadata.gz: 46524cde0077bf047a9c0fe4defa29ea67b0b1e652c4da5d3dc375ed662aead1bbcf0952e33c2de7c05cc514efee4923b5d6ba4c262e5fce86396ddf2628f2d6
7
+ data.tar.gz: 5e0c5069c05665ed04dd637c2fb40f1c0f3a268eccd0a0f46644bd1e03bbda4c7dfea79ba23d8d636fcae83ebea03fb72b6a49dbbaef5505333e872d7b531ae4
@@ -0,0 +1 @@
1
+ TODO.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,31 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ hubert (0.0.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.2.5)
10
+ rake (10.3.2)
11
+ rspec (3.1.0)
12
+ rspec-core (~> 3.1.0)
13
+ rspec-expectations (~> 3.1.0)
14
+ rspec-mocks (~> 3.1.0)
15
+ rspec-core (3.1.7)
16
+ rspec-support (~> 3.1.0)
17
+ rspec-expectations (3.1.2)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.1.0)
20
+ rspec-mocks (3.1.3)
21
+ rspec-support (~> 3.1.0)
22
+ rspec-support (3.1.2)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ bundler (~> 1.7)
29
+ hubert!
30
+ rake (~> 10.0)
31
+ rspec (~> 3.0)
data/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Andrzej Kajetanowicz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,65 @@
1
+ #Hubert: simple tool for building HTTP URIs
2
+ Hubert makes it easy to generate URLs using simple template strings. Building
3
+ http API clients usually requires some way composing different URLs. This task
4
+ is easy if the API provides only a few endpoints but it becomes more cumbersome
5
+ as the number of available endpoints increases. Clients need to specify correct
6
+ protocols, ports, escape any dynamic values inserted into URI segments and query
7
+ strings. This gem is attempting to address this problem by providing a simple DSL
8
+ for defining methods that will return desired URLs or paths.
9
+
10
+ ## Example usage:
11
+ ```ruby
12
+ require 'hubert'
13
+
14
+ class MyAwesomeApiClient
15
+
16
+ # to make this work you need to extend your class with DSL module
17
+ extend Hubert::DSL
18
+
19
+ # all URLs will use https protocol (http is the default)
20
+ https!
21
+
22
+ # specifying hostname is required (unless you only want to generate paths)
23
+ host 'api.example.com'
24
+
25
+ # any path can be optionally prefixed - this can be usefull
26
+ # if for example your code uses specific API version or
27
+ # hits some specific group of endpoints (/users/... etc.)
28
+ path_prefix '/v1'
29
+
30
+ # this will allow to call user_url(id: 123) in your class
31
+ url '/users/:id', as: user
32
+
33
+ # this is a simplified version of the one above
34
+ # you can use it by calling user_path(id: 123)
35
+ path '/users/:id', as: user
36
+
37
+ def print_user_url
38
+ puts user_url(id: 123)
39
+ end
40
+ # => 'https://api.example.com/v1/users/123'
41
+
42
+ def print_user_path
43
+ puts user_path(id: 123)
44
+ end
45
+ # => '/v1/users/123'
46
+
47
+ # any additional key - value pairs will become a part of the query string
48
+ # all values will be escaped using CGI.escape
49
+ def print_user_url_with_additional_ke_value_pairs
50
+ puts user_url(id: 123, format: 'very pretty', include_description: true)
51
+ end
52
+ # => 'https://api.example.com/v1/users/123?format=very+pretty&include_description=true'
53
+
54
+ end
55
+
56
+ ```
57
+
58
+ More examples can be found in spec files.
59
+
60
+ ## Contributing
61
+ 1. Fork it
62
+ 2. Create your feature branch (git checkout -b feature/my-new-feature)
63
+ 3. Commit your changes (git commit -am 'Add some feature')
64
+ 4. Push to the branch (git push origin feature/my-new-feature)
65
+ 5. Create new Pull Request`
@@ -0,0 +1,15 @@
1
+ begin
2
+ require 'rspec/core/rake_task'
3
+ RSpec::Core::RakeTask.new(:spec) do |t|
4
+ t.verbose = false
5
+ end
6
+
7
+ task default: :spec
8
+ rescue LoadError
9
+ end
10
+
11
+ task :benchmark do
12
+ Dir['benchmark/**/*.rb'].each do |benchmark|
13
+ sh 'ruby', benchmark
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ require 'benchmark'
2
+ require 'hubert'
3
+
4
+ probes = 100000
5
+ Benchmark.bm(25) do |b|
6
+ b.report('Hubert::Template#render') do
7
+
8
+ template = Hubert::Template.new('/simple/path/:id/some/:name')
9
+ context = { id: 123, name: 'hello world', sort: 'name', order: 'asc' }
10
+
11
+ probes.times { template.render(context) }
12
+ end
13
+ end
@@ -0,0 +1,46 @@
1
+ # Simple github client
2
+
3
+ require 'net/http'
4
+ require 'hubert'
5
+
6
+ module Github
7
+ class User
8
+ extend Hubert::DSL
9
+
10
+ https!
11
+ host 'api.github.com'
12
+ path_prefix '/users'
13
+
14
+ url '/:username', as: 'summary'
15
+ url '/:username/followers', as: 'followers'
16
+ url '/:username/gists', as: 'gists'
17
+ url '/:username/gists/:gist_id', as: 'gist'
18
+
19
+
20
+ def initialize(username)
21
+ @username = username
22
+ end
23
+
24
+ def summary
25
+ get summary_url(username: @username)
26
+ end
27
+
28
+ def followers
29
+ get followers_url(username: @username)
30
+ end
31
+
32
+ def gists
33
+ get gists_url(username: @username)
34
+ end
35
+
36
+ def gist(id)
37
+ get gist_url(username: @username, gist_id: id)
38
+ end
39
+
40
+ private
41
+
42
+ def get(uri)
43
+ Net::HTTP.get URI(uri)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,22 @@
1
+ require File.expand_path('../lib/hubert/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'hubert'
5
+ gem.version = Hubert::VERSION
6
+ gem.summary = 'Hubert: simple http URL builder tool'
7
+ gem.description = 'Hubert makes it easy to generate URLs using template strings known from Ruby On Rails framework.'
8
+
9
+ gem.authors = ['Andrzej Kajetanowicz']
10
+ gem.email = ['andrzej.kajetanowicz@gmail.com']
11
+ gem.homepage = 'https://github.com/kajetanowicz/hubert'
12
+ gem.license = 'MIT'
13
+
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.executables = []
16
+ gem.test_files = gem.files.grep(%r{^spec/})
17
+ gem.require_paths = ['lib']
18
+
19
+ gem.add_development_dependency 'bundler', '~> 1.7'
20
+ gem.add_development_dependency 'rake', '~> 10.0'
21
+ gem.add_development_dependency 'rspec', '~> 3.0'
22
+ end
@@ -0,0 +1,11 @@
1
+ module Hubert
2
+
3
+ end
4
+
5
+ require 'hubert/template'
6
+ require 'hubert/template/context'
7
+ require 'hubert/template/renderer'
8
+ require 'hubert/components'
9
+ require 'hubert/builder'
10
+ require 'hubert/errors'
11
+ require 'hubert/dsl'
@@ -0,0 +1,46 @@
1
+ require 'forwardable'
2
+
3
+ module Hubert
4
+ class Builder
5
+ extend Forwardable
6
+
7
+ def_delegators :components, :protocol=, :protocol
8
+ def_delegators :components, :host=, :host
9
+ def_delegators :components, :path_prefix=, :path_prefix
10
+ def_delegators :components, :port=, :port
11
+ def_delegators :components, :http!, :https!
12
+
13
+ def initialize
14
+ @templates = {}
15
+ yield(self) if block_given?
16
+ end
17
+
18
+ def path(template, context = {})
19
+ templates(template).render(context)
20
+ end
21
+
22
+ def url(template, context = {})
23
+ fail HostNotSet, 'Unable to generate URL without host' if host.nil?
24
+
25
+ String.new.tap do|url|
26
+ url << protocol + '://'
27
+ url << host
28
+ url << ':' + port unless components.default_port?
29
+ url << path_prefix
30
+ url << path(template, context)
31
+ end
32
+ end
33
+
34
+ def templates(name)
35
+ @templates.fetch(name) do
36
+ @templates[name] = Template.new(name)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def components
43
+ @components ||= Components.new
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,64 @@
1
+ require 'uri'
2
+ module Hubert
3
+ class Components
4
+
5
+ DEFAULT_PORTS = { 'http' => '80', 'https' => '443' }
6
+
7
+ def protocol=(protocol)
8
+ protocol = protocol.to_s unless protocol.is_a?(String)
9
+ protocol.strip!
10
+ if protocol =~ /(http)|(https)/i
11
+ @protocol = protocol.downcase
12
+ else
13
+ fail InvalidProtocol, "Provided protocol: [#{protocol}] is invalid"
14
+ end
15
+ end
16
+
17
+ def protocol
18
+ @protocol ||= 'http'
19
+ end
20
+
21
+ def http!
22
+ @protocol = 'http'
23
+ end
24
+
25
+ def https!
26
+ @protocol = 'https'
27
+ end
28
+
29
+ def host=(host)
30
+ host.strip!
31
+ host = 'http://' + host if URI.parse(host).scheme.nil?
32
+ @host = URI.parse(host).host
33
+ end
34
+
35
+ def host
36
+ @host
37
+ end
38
+
39
+ def path_prefix=(path)
40
+ path.strip!
41
+ path = URI.parse(path).path
42
+ path = path[0..-2] if path.end_with?('/')
43
+ path = path[1..-1] if path.start_with?('/')
44
+
45
+ @path_prefix = '/' + path
46
+ end
47
+
48
+ def path_prefix
49
+ @path_prefix ||= ''
50
+ end
51
+
52
+ def port
53
+ @port ||= DEFAULT_PORTS.fetch(protocol, '80')
54
+ end
55
+
56
+ def port=(port)
57
+ @port = port.to_s
58
+ end
59
+
60
+ def default_port?
61
+ DEFAULT_PORTS.fetch(protocol) == port
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,60 @@
1
+ module Hubert
2
+ module DSL
3
+
4
+ class MethodDefiner
5
+ def initialize(klass)
6
+ @klass = klass
7
+ end
8
+
9
+ def define(method, path, options)
10
+ ensure_alias!(options, method)
11
+
12
+ @klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
13
+ def #{options[:as]}_#{method}(ctx = {}) # def get_items_path(ctx = {})
14
+ self.class._hubert_builder.#{method}('#{path}', ctx) # self.class._hubert_builder.url('some/:interesting/path:id', ctx)
15
+ end # end
16
+ RUBY
17
+ end
18
+
19
+ private
20
+
21
+ def ensure_alias!(options, method)
22
+ if options.fetch(:as, '') =~ /^\s*$/
23
+ fail AliasNotSet, "Please specify ':as' option when defining new #{method}"
24
+ end
25
+ end
26
+ end
27
+
28
+ def path(path, options = {})
29
+ MethodDefiner.new(self).define(:path, path, options)
30
+ end
31
+
32
+ def url(path, options = {})
33
+ MethodDefiner.new(self).define(:url, path, options)
34
+ end
35
+
36
+ def http!
37
+ _hubert_builder.http!
38
+ end
39
+
40
+ def https!
41
+ _hubert_builder.https!
42
+ end
43
+
44
+ def host(host)
45
+ _hubert_builder.host = host
46
+ end
47
+
48
+ def port(port)
49
+ _hubert_builder.port = port
50
+ end
51
+
52
+ def path_prefix(path_prefix)
53
+ _hubert_builder.path_prefix = path_prefix
54
+ end
55
+
56
+ def _hubert_builder
57
+ @_hubert_builder ||= Builder.new
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,11 @@
1
+ module Hubert
2
+ HubertError = Class.new(StandardError)
3
+
4
+ KeyNotFound = Class.new(HubertError)
5
+
6
+ InvalidProtocol = Class.new(HubertError)
7
+
8
+ HostNotSet = Class.new(HubertError)
9
+
10
+ AliasNotSet = Class.new(HubertError)
11
+ end
@@ -0,0 +1,52 @@
1
+ require 'forwardable'
2
+
3
+ module Hubert
4
+ class Template
5
+ PH = /:[a-zA-Z_]+/
6
+
7
+ extend Forwardable
8
+ def_delegator :@compiled, :map
9
+
10
+ def initialize(template)
11
+ @template = template
12
+ @compiled = []
13
+
14
+ compile!
15
+ end
16
+
17
+ def render(ctx = {})
18
+ Renderer.new(self, ctx).render
19
+ end
20
+
21
+ def placeholders
22
+ @compiled.select { |segment| segment.is_a?(Symbol) }
23
+ end
24
+
25
+ private
26
+
27
+ def compile!
28
+ normalize_template!
29
+ extract_placeholders!
30
+ end
31
+
32
+ def normalize_template!
33
+ path = @template
34
+
35
+ path = path[0..-2] if path.end_with?('/')
36
+ path = path[1..-1] if path.start_with?('/')
37
+
38
+ @template = '/' + path
39
+ end
40
+
41
+ def extract_placeholders!
42
+ tail = @template
43
+
44
+ loop do
45
+ head, placeholder, tail = tail.partition(PH)
46
+ @compiled << head
47
+ break if placeholder.empty?
48
+ @compiled << placeholder[1..-1].to_sym
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,28 @@
1
+ module Hubert
2
+ class Template
3
+ class Context
4
+ def initialize(ctx = {})
5
+ @context = ctx
6
+ @lookups = []
7
+ end
8
+
9
+ def lookup(key)
10
+ @lookups << key
11
+
12
+ @context.fetch(key) do
13
+ fail KeyNotFound, "Unable to find a value for a given key [#{key}]"
14
+ end
15
+ end
16
+
17
+ def unused
18
+ lookups.reduce(@context.dup) do |ctx, key|
19
+ ctx.delete(key); ctx
20
+ end
21
+ end
22
+
23
+ def lookups
24
+ @lookups.uniq
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,47 @@
1
+ require 'cgi'
2
+
3
+ module Hubert
4
+ class Template
5
+ class Renderer
6
+
7
+ def initialize(template, ctx)
8
+ @template = template
9
+ @context = Context.new(ctx)
10
+ end
11
+
12
+ def render
13
+ result = path
14
+ result << '?' + query if query?
15
+ result
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :template, :context
21
+
22
+ def path
23
+ template.map do |segment|
24
+ segment.kind_of?(Symbol) ? lookup(segment) : segment
25
+ end * ''
26
+ end
27
+
28
+ def query
29
+ context.unused.map do |key, value|
30
+ "#{escape(key)}=#{escape(value)}"
31
+ end * '&'
32
+ end
33
+
34
+ def query?
35
+ context.unused.any?
36
+ end
37
+
38
+ def lookup(segment)
39
+ escape(context.lookup(segment))
40
+ end
41
+
42
+ def escape(stringish)
43
+ CGI.escape(stringish.to_s)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,3 @@
1
+ module Hubert
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ module Hubert
4
+ describe Builder do
5
+ let(:builder) do
6
+ Builder.new
7
+ end
8
+
9
+ describe '#path' do
10
+ it 'renders a path' do
11
+ expect(builder.path('/foo/bar')).to eq('/foo/bar')
12
+ end
13
+
14
+ it 'replaces placeholders with values' do
15
+ expect(
16
+ builder.path('create/:id/some/:name', id: 11, name: 'example')
17
+ ).to eq('/create/11/some/example')
18
+ end
19
+
20
+ it 'caches templates' do
21
+ expect(Template).to receive(:new).with('/path/to/resource/:id').once.and_call_original
22
+
23
+ builder.path('/path/to/resource/:id', id: 11)
24
+ builder.path('/path/to/resource/:id', id: 12)
25
+ end
26
+ end
27
+
28
+ describe '#url' do
29
+ let(:builder) do
30
+ Builder.new do |b|
31
+ b.http!
32
+ b.host = 'example.com'
33
+ b.port = port
34
+ b.path_prefix = 'api'
35
+ end
36
+ end
37
+
38
+ let(:port) { 80 }
39
+
40
+ it 'returns full URL' do
41
+ expect(builder.url('/path/to/resource/:id', id: 11, sort: 'name'))
42
+ .to eq('http://example.com/api/path/to/resource/11?sort=name')
43
+ end
44
+
45
+ context 'when using non-default port number' do
46
+ let(:port) { 8080 }
47
+
48
+ it 'creates an url with port number' do
49
+ expect(builder.url('/path/to/resource/:id', id: 11, sort: 'name'))
50
+ .to eq('http://example.com:8080/api/path/to/resource/11?sort=name')
51
+ end
52
+ end
53
+
54
+ context 'when protocol is not set' do
55
+ let(:builder) do
56
+ Builder.new do |b|
57
+ b.host = 'example.com'
58
+ end
59
+ end
60
+
61
+ it 'uses default http protocol' do
62
+ expect(builder.url('/path/to/resource'))
63
+ .to eq('http://example.com/path/to/resource')
64
+ end
65
+ end
66
+
67
+ context 'when domain is not set' do
68
+ let(:builder) { Builder.new }
69
+
70
+ it 'raises an exception' do
71
+ expect { builder.url('/path') }.to raise_error(HostNotSet)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,108 @@
1
+ require 'spec_helper'
2
+
3
+ module Hubert
4
+ describe Components do
5
+ let(:components) { Components.new }
6
+
7
+ describe '#protocol=' do
8
+ it 'assigns the protocol' do
9
+ components.protocol = 'HTTP'
10
+
11
+ expect(components.protocol).to eq('http')
12
+ end
13
+
14
+ it 'raises an exception if the protocol is invalid' do
15
+ expect { components.protocol = 'ftp' }.
16
+ to raise_error(InvalidProtocol, /\[ftp\]/)
17
+ end
18
+
19
+ context 'when passed as a symbol' do
20
+ it 'converts protocol to a String' do
21
+ components.protocol = :http
22
+
23
+ expect(components.protocol).to eq('http')
24
+ end
25
+ end
26
+ end
27
+
28
+ describe '#http!' do
29
+ it 'assigns the HTTP protocol' do
30
+ components.http!
31
+
32
+ expect(components.protocol).to eq('http')
33
+ end
34
+ end
35
+
36
+ describe '#https!' do
37
+ it 'assigns the HTTPS protocol' do
38
+ components.https!
39
+
40
+ expect(components.protocol).to eq('https')
41
+ end
42
+ end
43
+
44
+ describe '#host=' do
45
+ it 'assigns the host' do
46
+ components.host = 'example.com'
47
+
48
+ expect(components.host).to eq('example.com')
49
+ end
50
+
51
+ it 'removes the path component from the host' do
52
+ components.host = 'example.com/path/to/resource'
53
+
54
+ expect(components.host).to eq('example.com')
55
+ end
56
+
57
+ it 'removes the protocol component from the host' do
58
+ components.host = 'http://example.com/path/to/resource'
59
+
60
+ expect(components.host).to eq('example.com')
61
+ end
62
+ end
63
+
64
+ describe '#path_prefix' do
65
+ it 'assigns the path prefix' do
66
+ components.path_prefix = 'foo/bar/baz'
67
+
68
+ expect(components.path_prefix).to eq('/foo/bar/baz')
69
+ end
70
+
71
+ it 'removes slashes from the end of the path' do
72
+ components.path_prefix = '/foo/bar/baz/'
73
+
74
+ expect(components.path_prefix).to eq('/foo/bar/baz')
75
+ end
76
+ end
77
+
78
+ describe '#port' do
79
+ it 'returns default HTTP port (80)' do
80
+ expect(components.port).to eq('80')
81
+ end
82
+
83
+ context 'when using HTTPS' do
84
+ it 'returns the default HTTPS port (443)' do
85
+ components.https!
86
+
87
+ expect(components.port).to eq('443')
88
+ end
89
+ end
90
+
91
+ context 'when using HTTP' do
92
+ it 'returns the default HTTP port (80)' do
93
+ components.http!
94
+
95
+ expect(components.port).to eq('80')
96
+ end
97
+ end
98
+ end
99
+
100
+ describe '#port=' do
101
+ it 'assings port' do
102
+ components.port = 123
103
+
104
+ expect(components.port).to eq('123')
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ module Hubert
4
+ describe DSL do
5
+ let(:example_class) do
6
+ Class.new { extend DSL }
7
+ end
8
+
9
+ %w( path url http! https! port host path_prefix ).each do |method|
10
+ it "adds '#{method}' method to the host class" do
11
+ expect(example_class).to respond_to(method)
12
+ end
13
+ end
14
+
15
+ describe '.path' do
16
+ before do
17
+ example_class.class_eval do
18
+ path 'some/:simple/path/:id', as: :get_items
19
+ end
20
+ end
21
+
22
+ it 'defines *_path method on any instance of the class' do
23
+ expect(example_class.new).to respond_to(:get_items_path)
24
+ end
25
+
26
+ context 'when calling new method' do
27
+ subject do
28
+ instance.get_items_path(simple: 'foo', id: 1234, sort: 'name', order: 'desc')
29
+ end
30
+
31
+ let(:instance) do
32
+ example_class.new
33
+ end
34
+
35
+ it 'builds path using passed Hash' do
36
+ expect(subject).to eq('/some/foo/path/1234?sort=name&order=desc')
37
+ end
38
+ end
39
+
40
+ context 'when :as key is not present' do
41
+ it 'raises an exception' do
42
+ expect {
43
+ example_class.class_eval do
44
+ path 'some/:simple/path/:id'
45
+ end
46
+ }.to raise_error(AliasNotSet, /:as/)
47
+ end
48
+ end
49
+ end
50
+
51
+ describe '.url' do
52
+ before do
53
+ example_class.class_eval do
54
+ url 'some/:simple/path/:id', as: :get_items
55
+ end
56
+ end
57
+
58
+ it 'defines *_url method on any instance of the class' do
59
+ expect(example_class.new).to respond_to(:get_items_url)
60
+ end
61
+
62
+ describe 'defined method' do
63
+ context 'when host is not specified' do
64
+ it 'raises an exception' do
65
+ expect { example_class.new.get_items_url }
66
+ .to raise_error(HostNotSet)
67
+ end
68
+ end
69
+
70
+ context 'when all path segments are specified' do
71
+ before do
72
+ example_class.class_eval do
73
+ https!; host 'example.com'; port 8080; path_prefix 'api'
74
+
75
+ url 'some/:simple/path/:id', as: :get_items
76
+ end
77
+ end
78
+
79
+ it 'returns a url' do
80
+ expect(example_class.new.get_items_url(simple: 'foo', id: 'bar'))
81
+ .to eq('https://example.com:8080/api/some/foo/path/bar')
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ module Hubert
4
+ describe Template do
5
+ let(:template) do
6
+ Template.new(path)
7
+ end
8
+
9
+ let(:path) do
10
+ '/first/second/third/'
11
+ end
12
+
13
+ describe '#placeholders' do
14
+ subject { template.placeholders }
15
+
16
+ let(:path) do
17
+ 'simple/:path/:with/some/:placeholders'
18
+ end
19
+
20
+ it' returns a list of placeholders' do
21
+ expect(subject).to eq([:path, :with, :placeholders])
22
+ end
23
+ end
24
+
25
+ describe '#render' do
26
+ subject { template.render({}) }
27
+
28
+ it 'returns a string' do
29
+ expect(subject).to be_kind_of(String)
30
+ end
31
+
32
+ it 'renders a path' do
33
+ expect(subject).to include('first/second/third')
34
+ end
35
+
36
+ it 'returns a path prefixed with "/"' do
37
+ expect(subject).to match(/^\/[^\/]/)
38
+ end
39
+
40
+ it 'removes following slash from the path' do
41
+ expect(subject[-1]).not_to eq('/')
42
+ end
43
+
44
+ context 'when path contains placeholders' do
45
+ subject do
46
+ template.render(name: 'test', id: 1234)
47
+ end
48
+
49
+ let(:path) do
50
+ 'a/path/to/:name/followed/by/:id/and/:name/again'
51
+ end
52
+
53
+ it 'substitues all placeholders with provided values' do
54
+ expect(subject).to eq(
55
+ '/a/path/to/test/followed/by/1234/and/test/again'
56
+ )
57
+ end
58
+ end
59
+
60
+ context 'when hash contains additional keys' do
61
+ subject do
62
+ template.render(parent_id: 10, id: 512, sort: 'name', order: 'asc')
63
+ end
64
+
65
+ let(:path) do
66
+ 'a/path/:parent_id/:id/'
67
+ end
68
+
69
+ it 'builds a query string' do
70
+ expect(subject).to eq('/a/path/10/512?sort=name&order=asc')
71
+ end
72
+ end
73
+
74
+ context 'when unable to find a placeholder substitution' do
75
+ subject do
76
+ template.render(id: 123)
77
+ end
78
+
79
+ let(:path) do
80
+ 'a/path/:id/:name'
81
+ end
82
+
83
+ it 'raises an exception' do
84
+ expect { subject }.to raise_error(KeyNotFound, /\[name\]/)
85
+ end
86
+ end
87
+
88
+ context 'when query string contains characters that are not allowed' do
89
+ subject do
90
+ template.render(escape: 'all the things', like: 'a boss!!')
91
+ end
92
+
93
+ let(:path) do
94
+ 'a/path/without/placeholders'
95
+ end
96
+
97
+ it 'escapes all illegal characters' do
98
+ expect(subject).to eq(
99
+ '/a/path/without/placeholders?escape=all+the+things&like=a+boss%21%21'
100
+ )
101
+ end
102
+ end
103
+
104
+ context 'when segments contain illegal characters' do
105
+ subject do
106
+ template.render(what: 'all the things', who: 'a boss')
107
+ end
108
+
109
+ let(:path) do
110
+ 'escape/:what/like/:who'
111
+ end
112
+
113
+ it 'escapes all illegal characters' do
114
+ expect(subject).to eq('/escape/all+the+things/like/a+boss')
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1 @@
1
+ require 'spec_helper'
@@ -0,0 +1,7 @@
1
+ require 'hubert'
2
+
3
+ RSpec.configure do |config|
4
+ config.raise_errors_for_deprecations!
5
+ config.fail_fast = false
6
+ config.order = 'random'
7
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hubert
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrzej Kajetanowicz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: Hubert makes it easy to generate URLs using template strings known from
56
+ Ruby On Rails framework.
57
+ email:
58
+ - andrzej.kajetanowicz@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - Gemfile
66
+ - Gemfile.lock
67
+ - LICENCE
68
+ - README.md
69
+ - Rakefile
70
+ - benchmark/template.rb
71
+ - examples/github.rb
72
+ - hubert.gemspec
73
+ - lib/hubert.rb
74
+ - lib/hubert/builder.rb
75
+ - lib/hubert/components.rb
76
+ - lib/hubert/dsl.rb
77
+ - lib/hubert/errors.rb
78
+ - lib/hubert/template.rb
79
+ - lib/hubert/template/context.rb
80
+ - lib/hubert/template/renderer.rb
81
+ - lib/hubert/version.rb
82
+ - spec/lib/hubert/builder_spec.rb
83
+ - spec/lib/hubert/components_spec.rb
84
+ - spec/lib/hubert/dsl_spec.rb
85
+ - spec/lib/hubert/template_spec.rb
86
+ - spec/lib/hubert_spec.rb
87
+ - spec/spec_helper.rb
88
+ homepage: https://github.com/kajetanowicz/hubert
89
+ licenses:
90
+ - MIT
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.2.2
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: 'Hubert: simple http URL builder tool'
112
+ test_files:
113
+ - spec/lib/hubert/builder_spec.rb
114
+ - spec/lib/hubert/components_spec.rb
115
+ - spec/lib/hubert/dsl_spec.rb
116
+ - spec/lib/hubert/template_spec.rb
117
+ - spec/lib/hubert_spec.rb
118
+ - spec/spec_helper.rb
119
+ has_rdoc: