ably 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.travis.yml +2 -2
  2. data/Rakefile +2 -0
  3. data/SPEC.md +230 -194
  4. data/ably.gemspec +2 -0
  5. data/lib/ably/auth.rb +7 -5
  6. data/lib/ably/models/idiomatic_ruby_wrapper.rb +5 -7
  7. data/lib/ably/models/paginated_resource.rb +14 -21
  8. data/lib/ably/models/protocol_message.rb +1 -1
  9. data/lib/ably/modules/ably.rb +4 -0
  10. data/lib/ably/modules/async_wrapper.rb +2 -2
  11. data/lib/ably/modules/channels_collection.rb +31 -8
  12. data/lib/ably/modules/conversions.rb +10 -0
  13. data/lib/ably/modules/enum.rb +2 -3
  14. data/lib/ably/modules/state_emitter.rb +8 -8
  15. data/lib/ably/modules/state_machine.rb +7 -3
  16. data/lib/ably/realtime/channel.rb +6 -5
  17. data/lib/ably/realtime/channel/channel_manager.rb +11 -10
  18. data/lib/ably/realtime/channel/channel_state_machine.rb +10 -9
  19. data/lib/ably/realtime/channels.rb +3 -0
  20. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +11 -1
  21. data/lib/ably/realtime/connection.rb +55 -16
  22. data/lib/ably/realtime/connection/connection_manager.rb +25 -8
  23. data/lib/ably/realtime/connection/connection_state_machine.rb +9 -9
  24. data/lib/ably/realtime/connection/websocket_transport.rb +2 -2
  25. data/lib/ably/realtime/presence.rb +16 -17
  26. data/lib/ably/util/crypto.rb +1 -1
  27. data/lib/ably/version.rb +1 -1
  28. data/spec/acceptance/realtime/channel_history_spec.rb +6 -5
  29. data/spec/acceptance/realtime/connection_failures_spec.rb +103 -27
  30. data/spec/acceptance/realtime/connection_spec.rb +81 -17
  31. data/spec/acceptance/realtime/presence_spec.rb +82 -30
  32. data/spec/acceptance/rest/auth_spec.rb +22 -19
  33. data/spec/acceptance/rest/client_spec.rb +4 -4
  34. data/spec/acceptance/rest/presence_spec.rb +12 -6
  35. data/spec/rspec_config.rb +9 -0
  36. data/spec/shared/model_behaviour.rb +1 -1
  37. data/spec/spec_helper.rb +4 -1
  38. data/spec/support/event_machine_helper.rb +26 -37
  39. data/spec/support/markdown_spec_formatter.rb +96 -68
  40. data/spec/support/rest_testapp_before_retry.rb +15 -0
  41. data/spec/support/test_app.rb +4 -0
  42. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +20 -2
  43. data/spec/unit/models/message_spec.rb +1 -1
  44. data/spec/unit/models/paginated_resource_spec.rb +15 -1
  45. data/spec/unit/modules/enum_spec.rb +10 -0
  46. data/spec/unit/realtime/channels_spec.rb +30 -0
  47. data/spec/unit/rest/channels_spec.rb +30 -0
  48. metadata +101 -35
  49. checksums.yaml +0 -7
@@ -5,17 +5,19 @@ describe Ably::Auth do
5
5
  include Ably::Modules::Conversions
6
6
 
7
7
  def hmac_for(token_request, secret)
8
- text = token_request.values_at(
8
+ ruby_named_token_request = Ably::Models::IdiomaticRubyWrapper.new(token_request)
9
+
10
+ text = [
9
11
  :id,
10
12
  :ttl,
11
13
  :capability,
12
14
  :client_id,
13
15
  :timestamp,
14
16
  :nonce
15
- ).map { |t| "#{t}\n" }.join("")
17
+ ].map { |key| "#{ruby_named_token_request[key]}\n" }.join("")
16
18
 
17
19
  encode64(
18
- Digest::HMAC.digest(text, key_secret, Digest::SHA256)
20
+ OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, secret, text)
19
21
  )
20
22
  end
21
23
 
@@ -38,7 +40,7 @@ describe Ably::Auth do
38
40
  else
39
41
  JSON.parse(request.body)
40
42
  end
41
- body[key.to_s].to_s == val.to_s
43
+ body[convert_to_mixed_case(key)].to_s == val.to_s
42
44
  end
43
45
 
44
46
  def serialize(object, protocol)
@@ -50,7 +52,7 @@ describe Ably::Auth do
50
52
  end
51
53
 
52
54
  it 'has immutable options' do
53
- expect { auth.options['key_id'] = 'new_id' }.to raise_error RuntimeError, /can't modify frozen Hash/
55
+ expect { auth.options['key_id'] = 'new_id' }.to raise_error RuntimeError, /can't modify frozen.*Hash/
54
56
  end
55
57
 
56
58
  describe '#request_token' do
@@ -70,7 +72,7 @@ describe Ably::Auth do
70
72
  end
71
73
 
72
74
  %w(client_id capability nonce timestamp ttl).each do |option|
73
- context "option :#{option}", :webmock do
75
+ context "with option :#{option}", :webmock do
74
76
  let(:random) { random_int_str }
75
77
  let(:options) { { option.to_sym => random } }
76
78
 
@@ -88,7 +90,7 @@ describe Ably::Auth do
88
90
 
89
91
  before { auth.request_token options }
90
92
 
91
- it 'overrides default' do
93
+ it 'overrides default and uses camelCase notation for all attributes' do
92
94
  expect(request_token_stub).to have_been_requested
93
95
  end
94
96
  end
@@ -134,7 +136,7 @@ describe Ably::Auth do
134
136
  context 'without :query_time option' do
135
137
  let(:options) { { query_time: false } }
136
138
 
137
- it 'queries the server for the time' do
139
+ it 'does not query the server for the time' do
138
140
  expect(client).to_not receive(:time)
139
141
  auth.request_token(options)
140
142
  end
@@ -356,25 +358,25 @@ describe Ably::Auth do
356
358
  subject { auth.create_token_request(options) }
357
359
 
358
360
  it 'uses the key ID from the client' do
359
- expect(subject[:id]).to eql(key_id)
361
+ expect(subject['id']).to eql(key_id)
360
362
  end
361
363
 
362
364
  it 'uses the default TTL' do
363
- expect(subject[:ttl]).to eql(Ably::Models::Token::DEFAULTS[:ttl])
365
+ expect(subject['ttl']).to eql(Ably::Models::Token::DEFAULTS[:ttl])
364
366
  end
365
367
 
366
368
  it 'uses the default capability' do
367
- expect(subject[:capability]).to eql(Ably::Models::Token::DEFAULTS[:capability].to_json)
369
+ expect(subject['capability']).to eql(Ably::Models::Token::DEFAULTS[:capability].to_json)
368
370
  end
369
371
 
370
372
  context 'the nonce' do
371
373
  it 'is unique for every request' do
372
- unique_nonces = 100.times.map { auth.create_token_request[:nonce] }
374
+ unique_nonces = 100.times.map { auth.create_token_request['nonce'] }
373
375
  expect(unique_nonces.uniq.length).to eql(100)
374
376
  end
375
377
 
376
378
  it 'is at least 16 characters' do
377
- expect(subject[:nonce].length).to be >= 16
379
+ expect(subject['nonce'].length).to be >= 16
378
380
  end
379
381
  end
380
382
 
@@ -385,7 +387,7 @@ describe Ably::Auth do
385
387
  options[attribute.to_sym] = option_value
386
388
  end
387
389
  it "overrides default" do
388
- expect(subject[attribute.to_sym].to_s).to eql(option_value.to_s)
390
+ expect(subject[convert_to_mixed_case(attribute)].to_s).to eql(option_value.to_s)
389
391
  end
390
392
  end
391
393
  end
@@ -394,8 +396,9 @@ describe Ably::Auth do
394
396
  let(:options) { { nonce: 'valid', is_not_used_by_token_request: 'invalid' } }
395
397
  specify 'are ignored' do
396
398
  expect(subject.keys).to_not include(:is_not_used_by_token_request)
397
- expect(subject.keys).to include(:nonce)
398
- expect(subject[:nonce]).to eql('valid')
399
+ expect(subject.keys).to_not include(convert_to_mixed_case(:is_not_used_by_token_request))
400
+ expect(subject.keys).to include('nonce')
401
+ expect(subject['nonce']).to eql('valid')
399
402
  end
400
403
  end
401
404
 
@@ -417,7 +420,7 @@ describe Ably::Auth do
417
420
 
418
421
  it 'queries the server for the timestamp' do
419
422
  expect(client).to receive(:time).and_return(time)
420
- expect(subject[:timestamp]).to eql(time.to_i)
423
+ expect(subject['timestamp']).to eql(time.to_i)
421
424
  end
422
425
  end
423
426
 
@@ -426,7 +429,7 @@ describe Ably::Auth do
426
429
  let(:options) { { timestamp: token_request_time } }
427
430
 
428
431
  it 'uses the provided timestamp in the token request' do
429
- expect(subject[:timestamp]).to eql(token_request_time.to_i)
432
+ expect(subject['timestamp']).to eql(token_request_time.to_i)
430
433
  end
431
434
  end
432
435
 
@@ -444,7 +447,7 @@ describe Ably::Auth do
444
447
 
445
448
  it 'generates a valid HMAC' do
446
449
  hmac = hmac_for(options, key_secret)
447
- expect(subject[:mac]).to eql(hmac)
450
+ expect(subject['mac']).to eql(hmac)
448
451
  end
449
452
  end
450
453
  end
@@ -54,12 +54,12 @@ describe Ably::Rest::Client do
54
54
 
55
55
  it 'creates a new token automatically when the old token expires' do
56
56
  expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
57
- expect(client.auth.current_token.client_id).to eql(token_request_1[:client_id])
57
+ expect(client.auth.current_token.client_id).to eql(token_request_1['clientId'])
58
58
 
59
59
  sleep 1
60
60
 
61
61
  expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
62
- expect(client.auth.current_token.client_id).to eql(token_request_2[:client_id])
62
+ expect(client.auth.current_token.client_id).to eql(token_request_2['clientId'])
63
63
  end
64
64
  end
65
65
 
@@ -68,12 +68,12 @@ describe Ably::Rest::Client do
68
68
 
69
69
  it 'reuses the existing token for every request' do
70
70
  expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
71
- expect(client.auth.current_token.client_id).to eql(token_request_1[:client_id])
71
+ expect(client.auth.current_token.client_id).to eql(token_request_1['clientId'])
72
72
 
73
73
  sleep 1
74
74
 
75
75
  expect { client.channel('channel_name').publish('event', 'message') }.to_not change { client.auth.current_token }
76
- expect(client.auth.current_token.client_id).to eql(token_request_1[:client_id])
76
+ expect(client.auth.current_token.client_id).to eql(token_request_1['clientId'])
77
77
  end
78
78
  end
79
79
  end
@@ -18,13 +18,13 @@ describe Ably::Rest::Presence do
18
18
  end
19
19
 
20
20
  context 'tested against presence fixture data set up in test app' do
21
- before(:context) do
22
- # When this test is run as a part of a test suite, the presence data injected in the test app may have expired
23
- WebMock.disable!
24
- TestApp.reload
25
- end
26
-
27
21
  describe '#get' do
22
+ before(:context) do
23
+ # When this test is run as a part of a test suite, the presence data injected in the test app may have expired
24
+ WebMock.disable!
25
+ TestApp.reload
26
+ end
27
+
28
28
  let(:channel) { client.channel('persisted:presence_fixtures') }
29
29
  let(:presence) { channel.presence.get }
30
30
 
@@ -52,6 +52,12 @@ describe Ably::Rest::Presence do
52
52
  end
53
53
 
54
54
  describe '#history' do
55
+ before(:context) do
56
+ # When this test is run as a part of a test suite, the presence data injected in the test app may have expired
57
+ WebMock.disable!
58
+ TestApp.reload
59
+ end
60
+
55
61
  let(:channel) { client.channel('persisted:presence_fixtures') }
56
62
  let(:presence_history) { channel.presence.history }
57
63
 
data/spec/rspec_config.rb CHANGED
@@ -5,6 +5,8 @@
5
5
  #
6
6
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
7
 
8
+ require 'rspec/retry'
9
+
8
10
  RSpec.configure do |config|
9
11
  config.run_all_when_everything_filtered = true
10
12
  config.filter_run :focus
@@ -45,4 +47,11 @@ RSpec.configure do |config|
45
47
  end
46
48
 
47
49
  config.add_formatter Ably::RSpec::PrivateApiFormatter
50
+
51
+ if ENV['RSPEC_RETRY']
52
+ puts 'Running tests using RSpec retry'
53
+ config.verbose_retry = true # show retry status in spec process
54
+ config.default_retry_count = 3
55
+ config.default_sleep_interval = 2
56
+ end
48
57
  end
@@ -74,7 +74,7 @@ shared_examples 'a model' do |shared_options = {}|
74
74
  let(:model_options) { { channel: 'name' } }
75
75
 
76
76
  it 'prevents changes' do
77
- expect { model.hash[:channel] = 'new' }.to raise_error RuntimeError, /can't modify frozen Hash/
77
+ expect { model.hash[:channel] = 'new' }.to raise_error RuntimeError, /can't modify frozen.*Hash/
78
78
  end
79
79
 
80
80
  it 'dups options' do
data/spec/spec_helper.rb CHANGED
@@ -9,9 +9,12 @@ require 'webmock/rspec'
9
9
  require 'ably'
10
10
 
11
11
  require 'support/api_helper'
12
- require 'support/event_machine_helper'
13
12
  require 'support/private_api_formatter'
14
13
  require 'support/protocol_helper'
15
14
  require 'support/random_helper'
16
15
 
17
16
  require 'rspec_config'
17
+
18
+ # EM Helper must be loaded after rspec_config to ensure around block occurs before RSpec retry
19
+ require 'support/event_machine_helper'
20
+ require 'support/rest_testapp_before_retry'
@@ -6,7 +6,7 @@ module RSpec
6
6
  module EventMachine
7
7
  extend self
8
8
 
9
- DEFAULT_TIMEOUT = 5
9
+ DEFAULT_TIMEOUT = 10
10
10
 
11
11
  def run_reactor(timeout = DEFAULT_TIMEOUT)
12
12
  Timeout::timeout(timeout + 0.5) do
@@ -24,8 +24,8 @@ module RSpec
24
24
 
25
25
  # Allows multiple Deferrables to be passed in and calls the provided block when
26
26
  # all success callbacks have completed
27
- def when_all(*deferrables, &block)
28
- raise "Block expected" unless block_given?
27
+ def when_all(*deferrables)
28
+ raise ArgumentError, 'Block required' unless block_given?
29
29
 
30
30
  options = if deferrables.last.kind_of?(Hash)
31
31
  deferrables.pop
@@ -40,9 +40,9 @@ module RSpec
40
40
  successful_deferrables[deferrable.object_id] = true
41
41
  if successful_deferrables.keys.sort == deferrables.map(&:object_id).sort
42
42
  if options[:and_wait]
43
- ::EventMachine.add_timer(options[:and_wait]) { block.call }
43
+ ::EventMachine.add_timer(options[:and_wait]) { yield }
44
44
  else
45
- block.call
45
+ yield
46
46
  end
47
47
  end
48
48
  end
@@ -52,6 +52,15 @@ module RSpec
52
52
  end
53
53
  end
54
54
  end
55
+
56
+ def wait_until(condition_block, &block)
57
+ raise ArgumentError, 'Block required' unless block_given?
58
+
59
+ yield if condition_block.call
60
+ ::EventMachine.add_timer(0.1) do
61
+ wait_until condition_block, &block
62
+ end
63
+ end
55
64
  end
56
65
  end
57
66
 
@@ -62,32 +71,11 @@ RSpec.configure do |config|
62
71
  end
63
72
  end
64
73
 
65
- # Running a reactor block and then calling the example block with #call
66
- # does not work as expected as the example completes immediately and the block
67
- # calls after hooks before it returns the EventMachine loop.
74
+ # Run the test block wrapped in an EventMachine reactor that has a configured timeout.
75
+ # As RSpec does not provide an API to wrap blocks, accessing the instance variables is required.
76
+ # Note, if you start a reactor and simply run the example with example#run then the example
77
+ # will run and not wait for the reactor to stop thus triggering after callbacks prematurely.
68
78
  #
69
- # As there is no public API to inject around blocks correctly without calling the after blocks,
70
- # we have to monkey patch the run_after_example method at https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/example.rb#L376
71
- # so that it does not run until we explicitly call it once the EventMachine reactor loop is finished.
72
- #
73
- def patch_example_block_with_surrounding_eventmachine_reactor(example)
74
- example.example.class.class_eval do
75
- alias_method :run_after_example_original, :run_after_example
76
- public :run_after_example_original
77
-
78
- # prevent after hooks being run for example until EventMachine reactor has finished
79
- def run_after_example; end
80
- end
81
- end
82
-
83
- def remove_patch_example_block(example)
84
- example.example.class.class_eval do
85
- remove_method :run_after_example
86
- alias_method :run_after_example, :run_after_example_original
87
- remove_method :run_after_example_original
88
- end
89
- end
90
-
91
79
  config.around(:example, :event_machine) do |example|
92
80
  timeout = if example.metadata[:em_timeout].is_a?(Numeric)
93
81
  example.metadata[:em_timeout]
@@ -95,17 +83,18 @@ RSpec.configure do |config|
95
83
  RSpec::EventMachine::DEFAULT_TIMEOUT
96
84
  end
97
85
 
98
- patch_example_block_with_surrounding_eventmachine_reactor example
86
+ example_block = example.example.instance_variable_get('@example_block')
87
+ example_group_instance = example.example.instance_variable_get('@example_group_instance')
99
88
 
100
- begin
89
+ event_machine_block = Proc.new do
101
90
  RSpec::EventMachine.run_reactor(timeout) do
102
- example.call
103
- raise example.exception if example.exception
91
+ example_group_instance.instance_exec(example, &example_block)
104
92
  end
105
- ensure
106
- example.example.run_after_example_original
107
- remove_patch_example_block example
108
93
  end
94
+
95
+ example.example.instance_variable_set('@example_block', event_machine_block)
96
+
97
+ example.run
109
98
  end
110
99
 
111
100
  config.before(:example) do
@@ -1,89 +1,117 @@
1
- module Ably::RSpec
2
- # Generate Markdown Specification from the RSpec public API tests
3
- #
4
- class MarkdownSpecFormatter
5
- ::RSpec::Core::Formatters.register self, :start, :close,
6
- :example_group_started, :example_group_finished,
7
- :example_passed, :example_failed, :example_pending,
8
- :dump_summary
9
-
10
- def initialize(output)
11
- @output = File.open(File.expand_path('../../../SPEC.md', __FILE__), 'w')
12
- @indent = 0
13
- @passed = 0
14
- @pending = 0
15
- @failed = 0
16
- end
1
+ module Ably
2
+ module RSpec
3
+ # Generate Markdown Specification from the RSpec public API tests
4
+ #
5
+ class MarkdownSpecFormatter
6
+ ::RSpec::Core::Formatters.register self, :start, :close,
7
+ :example_group_started, :example_group_finished,
8
+ :example_passed, :example_failed, :example_pending,
9
+ :dump_summary
10
+
11
+ def initialize(output)
12
+ @output = if documenting_rest_only?
13
+ File.open(File.expand_path('../../../../../../SPEC.md', __FILE__), 'w')
14
+ else
15
+ File.open(File.expand_path('../../../SPEC.md', __FILE__), 'w')
16
+ end
17
+
18
+ @indent = 0
19
+ @passed = 0
20
+ @pending = 0
21
+ @failed = 0
22
+ end
17
23
 
18
- def start(notification)
19
- puts "\n\e[33m --> Creating SPEC.md <--\e[0m\n"
20
- output.write "# Ably Client Library #{Ably::VERSION} Specification\n"
21
- end
24
+ def start(notification)
25
+ puts "\n\e[33m --> Creating SPEC.md <--\e[0m\n"
26
+ scope = if defined?(Ably::Realtime)
27
+ 'Real-time & REST'
28
+ else
29
+ 'REST'
30
+ end
31
+ output.write "# Ably #{scope} Client Library #{Ably::VERSION} Specification\n"
32
+ end
22
33
 
23
- def close(notification)
24
- output.close
25
- end
34
+ def close(notification)
35
+ output.close
36
+ end
26
37
 
27
- def example_group_started(notification)
28
- output.write "#{indent_prefix}#{notification.group.description}\n"
29
- output.write "_(see #{heading_location_path(notification)})_\n" if indent == 0
30
- @indent += 1
31
- end
38
+ def example_group_started(notification)
39
+ output.write "#{indent_prefix}#{notification.group.description}\n"
40
+ output.write "_(see #{heading_location_path(notification)})_\n" if indent == 0
41
+ @indent += 1
42
+ end
32
43
 
33
- def example_group_finished(notification)
34
- @indent -= 1
35
- end
44
+ def example_group_finished(notification)
45
+ @indent -= 1
46
+ end
36
47
 
37
- def example_passed(notification)
38
- unless notification.example.metadata[:api_private]
39
- output.write "#{indent_prefix}#{example_name_and_link(notification)}\n"
40
- @passed += 1
48
+ def example_passed(notification)
49
+ unless notification.example.metadata[:api_private]
50
+ output.write "#{indent_prefix}#{example_name_and_link(notification)}\n"
51
+ @passed += 1
52
+ end
41
53
  end
42
- end
43
54
 
44
- def example_failed(notification)
45
- unless notification.example.metadata[:api_private]
46
- output.write "#{indent_prefix}FAILED: ~~#{example_name_and_link(notification)}~~\n"
47
- @failed += 1
55
+ def example_failed(notification)
56
+ unless notification.example.metadata[:api_private]
57
+ output.write "#{indent_prefix}FAILED: ~~#{example_name_and_link(notification)}~~\n"
58
+ @failed += 1
59
+ end
48
60
  end
49
- end
50
61
 
51
- def example_pending(notification)
52
- unless notification.example.metadata[:api_private]
53
- output.write "#{indent_prefix}PENDING: *#{example_name_and_link(notification)}*\n"
54
- @pending += 1
62
+ def example_pending(notification)
63
+ unless notification.example.metadata[:api_private]
64
+ output.write "#{indent_prefix}PENDING: *#{example_name_and_link(notification)}*\n"
65
+ @pending += 1
66
+ end
55
67
  end
56
- end
57
68
 
58
- def dump_summary(notification)
59
- output.write <<-EOF.gsub(' ', '')
69
+ def dump_summary(notification)
70
+ output.write <<-EOF.gsub(' ', '')
60
71
 
61
- -------
72
+ -------
62
73
 
63
- ## Test summary
74
+ ## Test summary
64
75
 
65
- * Passing tests: #{@passed}
66
- * Pending tests: #{@pending}
67
- * Failing tests: #{@failed}
68
- EOF
69
- end
76
+ * Passing tests: #{@passed}
77
+ * Pending tests: #{@pending}
78
+ * Failing tests: #{@failed}
79
+ EOF
80
+ end
70
81
 
71
- private
72
- attr_reader :output, :indent
82
+ private
83
+ attr_reader :output, :indent
73
84
 
74
- def example_name_and_link(notification)
75
- "[#{notification.example.metadata[:description]}](#{notification.example.location.gsub(/\:(\d+)/, '#L\1')})"
76
- end
85
+ def documenting_rest_only?
86
+ File.exists?(File.expand_path('../../../../../../ably-rest.gemspec', __FILE__))
87
+ end
77
88
 
78
- def heading_location_path(notification)
79
- "[#{notification.group.location.gsub(/\:(\d+)/, '').gsub(%r{^.\/}, '')}](#{notification.group.location.gsub(/\:(\d+)/, '')})"
80
- end
89
+ def example_name_and_link(notification)
90
+ "[#{notification.example.metadata[:description]}](#{path_for(notification.example.location).gsub(/\:(\d+)/, '#L\1')})"
91
+ end
92
+
93
+ def heading_location_path(notification)
94
+ "[#{notification.group.location.gsub(/\:(\d+)/, '').gsub(%r{^\.\/}, '')}](#{path_for(notification.group.location).gsub(/\:(\d+)/, '')})"
95
+ end
96
+
97
+ def path_for(location)
98
+ if documenting_rest_only?
99
+ "https://github.com/ably/ably-ruby/tree/#{submodule_sha}#{location.gsub(%r{^\./lib/submodules/ably-ruby}, '')}"
100
+ else
101
+ location
102
+ end
103
+ end
104
+
105
+ def submodule_sha
106
+ @sha ||= `git ls-tree HEAD:lib/submodules grep ably-ruby`[/^\w+\s+\w+\s+(\w+)/, 1]
107
+ end
81
108
 
82
- def indent_prefix
83
- if indent > 0
84
- "#{' ' * indent}* "
85
- else
86
- "\n### "
109
+ def indent_prefix
110
+ if indent > 0
111
+ "#{' ' * indent}* "
112
+ else
113
+ "\n### "
114
+ end
87
115
  end
88
116
  end
89
117
  end