promise.rb 0.3.0 → 0.4.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.
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # promise.rb changelog
2
+
3
+ ## 0.4.0 (December 13, 2013)
4
+
5
+ * Disclaiming my copyright, promise.rb is now in the Public Domain
6
+ * on_fulfill argument to #then now takes precedence over block
7
+ * The rejection reason now defaults to RuntimeError, so that it can be re-raised by #sync
8
+ * `progress` and `on_progress` are now first class citizens
data/Gemfile CHANGED
@@ -13,7 +13,6 @@ platform :rbx do
13
13
  gem 'rubysl', '~> 2.0'
14
14
  gem 'rubysl-json', '~> 2.0'
15
15
  gem 'rubinius', '~> 2.0'
16
- gem 'racc'
17
16
  end
18
17
 
19
18
  # Added by devtools
data/Gemfile.devtools CHANGED
@@ -4,6 +4,10 @@ group :development do
4
4
  gem 'rake', '~> 10.1.0'
5
5
  gem 'rspec', '~> 2.14.1'
6
6
  gem 'yard', '~> 0.8.7'
7
+
8
+ platform :rbx do
9
+ gem 'rubysl-singleton', '~> 2.0.0'
10
+ end
7
11
  end
8
12
 
9
13
  group :yard do
@@ -11,14 +15,13 @@ group :yard do
11
15
  end
12
16
 
13
17
  group :guard do
14
- gem 'guard', '~> 1.8.1'
15
- gem 'guard-bundler', '~> 1.0.0'
16
- gem 'guard-rspec', '~> 3.0.2'
17
- gem 'guard-rubocop', '~> 0.2.0'
18
- gem 'guard-mutant', '~> 0.0.1'
18
+ gem 'guard', '~> 2.2.4'
19
+ gem 'guard-bundler', '~> 2.0.0'
20
+ gem 'guard-rspec', '~> 4.0.4'
21
+ gem 'guard-rubocop', '~> 1.0.0'
19
22
 
20
23
  # file system change event handling
21
- gem 'listen', '~> 1.3.0'
24
+ gem 'listen', '~> 2.2.0'
22
25
  gem 'rb-fchange', '~> 0.0.6', require: false
23
26
  gem 'rb-fsevent', '~> 0.9.3', require: false
24
27
  gem 'rb-inotify', '~> 0.9.0', require: false
@@ -30,18 +33,27 @@ group :guard do
30
33
  end
31
34
 
32
35
  group :metrics do
33
- gem 'coveralls', '~> 0.6.7'
36
+ gem 'coveralls', '~> 0.7.0'
34
37
  gem 'flay', '~> 2.4.0'
35
- gem 'flog', '~> 4.1.1'
38
+ gem 'flog', '~> 4.2.0'
36
39
  gem 'reek', '~> 1.3.2'
37
- gem 'rubocop', '~> 0.12.0'
38
- gem 'simplecov', '~> 0.7.1'
40
+ gem 'rubocop', '~> 0.15.0'
41
+ gem 'simplecov', '~> 0.8.2'
39
42
  gem 'yardstick', '~> 0.9.7', git: 'https://github.com/dkubb/yardstick.git'
40
43
 
41
44
  platforms :ruby_19, :ruby_20 do
42
- gem 'mutant', git: 'https://github.com/mbj/mutant.git'
45
+ gem 'mutant', '~> 0.3.0.rc3', git: 'https://github.com/mbj/mutant.git'
46
+ gem 'unparser', '~> 0.1.5', git: 'https://github.com/mbj/unparser.git'
43
47
  gem 'yard-spellcheck', '~> 0.1.5'
44
48
  end
49
+
50
+ platform :rbx do
51
+ gem 'json', '~> 1.8.1'
52
+ gem 'racc', '~> 1.4.10'
53
+ gem 'rubysl-logger', '~> 2.0.0'
54
+ gem 'rubysl-open-uri', '~> 2.0.0'
55
+ gem 'rubysl-prettyprint', '~> 2.0.2'
56
+ end
45
57
  end
46
58
 
47
59
  group :benchmarks do
data/README.md CHANGED
@@ -1,7 +1,20 @@
1
1
  # promise.rb [![Build Status](https://travis-ci.org/lgierth/promise.rb.png?branch=master)](https://travis-ci.org/lgierth/promise.rb) [![Code Climate](https://codeclimate.com/github/lgierth/promise.rb.png)](https://codeclimate.com/github/lgierth/promise.rb) [![Coverage Status](https://coveralls.io/repos/lgierth/promise.rb/badge.png?branch=master)](https://coveralls.io/r/lgierth/promise.rb?branch=master)
2
2
 
3
3
  Ruby implementation of the [Promises/A+ spec](http://promisesaplus.com/).
4
- 100% mutation coverage, tested on 1.9, 2.0, Rubinius, and JRuby.
4
+ 100% [mutation coverage](https://github.com/mbj/mutant), tested on 1.9, 2.0, Rubinius, and JRuby.
5
+
6
+ Similar projects:
7
+
8
+ - [concurrent-ruby](https://github.com/jdantonio/concurrent-ruby/blob/master/md/promise.md), Promises/A(+) implementation, thread based
9
+ - [promise](https://github.com/bhuga/promising-future), a.k.a. promising-future, classic promises and futures, thread based
10
+ - [celluloid-promise](https://github.com/cotag/celluloid-promise), inspired by Q, backed by a Celluloid actor
11
+ - [em-promise](https://github.com/cotag/em-promise), inspired by Q, backed by an EventMachine reactor
12
+ - [futuristic](https://github.com/seanlilmateus/futuristic), MacRuby bindings for Grand Central Dispatch
13
+ - [methodmissing/promise](https://github.com/methodmissing/promise), thread based, abandoned
14
+
15
+ ## Todo
16
+
17
+ - test with https://gist.github.com/joeljackson/5722487
5
18
 
6
19
  ## Installation
7
20
 
@@ -19,19 +32,11 @@ Or install it yourself as:
19
32
 
20
33
  ## Usage
21
34
 
22
- promise.rb doesn't come with a way of scheduling callback dispatch.
35
+ This guide assumes that you are familiar with the [Promises/A+ spec](http://promisesaplus.com/). It's a quick read, though.
23
36
 
24
- ```ruby
25
- require 'promise'
26
-
27
- class MyPromise < Promise
28
- def defer(callback, arg)
29
- callback.dispatch(arg)
30
- end
31
- end
32
- ```
33
-
34
- The above scheduling mechanism violates the following section of the spec:
37
+ promise.rb comes with a very primitive way of scheduling callback dispatch. It
38
+ immediately executes the callback, instead of scheduling it for execution
39
+ *after* `Promise#fulfill` or `Promise#reject`, as demanded by the spec:
35
40
 
36
41
  > onFulfilled or onRejected must not be called until the execution context
37
42
  > stack contains only platform code.
@@ -44,8 +49,8 @@ require 'promise'
44
49
  require 'eventmachine'
45
50
 
46
51
  class MyPromise < Promise
47
- def defer(callback, arg)
48
- EM.next_tick { callback.dispatch(arg) }
52
+ def defer
53
+ EM.next_tick { yield }
49
54
  end
50
55
  end
51
56
  ```
@@ -76,10 +81,65 @@ end
76
81
  failing_stuff.then(proc { |value| }, proc { |reason| p reason })
77
82
  ```
78
83
 
79
- ## License
84
+ promise.rb also comes with the utility method `Promise#sync`, which waits for
85
+ the promise to be fulfilled and returns the value, or for it to be rejected and
86
+ re-raises the reason. Using `#sync` requires you to implement `#wait`. You could
87
+ for example cooperatively schedule fibers waiting for different promises:
88
+
89
+ ```ruby
90
+ require 'promise'
91
+ require 'eventmachine'
92
+
93
+ class MyPromise < Promise
94
+ def defer
95
+ EM.next_tick { yield }
96
+ end
97
+
98
+ def wait
99
+ fiber = Fiber.current
100
+ resume = proc do |arg|
101
+ defer { fiber.resume(arg) }
102
+ end
103
+
104
+ self.then(resume, resume)
105
+ Fiber.yield
106
+ end
107
+ end
108
+
109
+ promise = MyPromise.new
110
+ Fiber.new { p promise.sync }.resume
111
+ promise.fulfill
112
+ ```
113
+
114
+ Or have the rejection reason re-raised from `#sync`:
115
+
116
+ ```ruby
117
+ promise = MyPromise.new
118
+
119
+ Fiber.new do
120
+ begin
121
+ promise.sync
122
+ rescue MyError
123
+ p $!
124
+ end
125
+ end.resume
126
+
127
+ promise.reject(MyError.new)
128
+ ```
129
+
130
+ Very simple progress callbacks, as per Promises/A, are supported as well. They have been dropped in A+, but I found them to be a useful mechanism - if kept simple. Callback dispatch happens immediately in the call to `#progress`, in the order of definition via `#on_progress`. Also note that `#on_progress` does not return a new promise for chaining - the progress mechanism is meant to be very lightweight, and ignores many of the constraints and guarantees of `then`.
131
+
132
+ ```ruby
133
+ promise = MyPromise.new
134
+ promise.on_progress { |status| p status }
135
+ promise.progress(:anything)
136
+ ```
137
+
138
+ ## Unlicense
80
139
 
81
- Hatetepe is licensed under the [MIT License](http://opensource.org/licenses/MIT).
82
- See LICENSE.txt for details.
140
+ promise.rb is free and unencumbered public domain software. For more
141
+ information, see [unlicense.org](http://unlicense.org/) or the accompanying
142
+ UNLICENSE file.
83
143
 
84
144
  ## Contributing
85
145
 
File without changes
data/config/flay.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  ---
2
- threshold: 14
2
+ threshold: 10
3
3
  total_score: 49
data/config/rubocop.yml CHANGED
@@ -24,15 +24,6 @@ CollectionMethods:
24
24
  find: 'detect'
25
25
  find_all: 'select'
26
26
 
27
- # Do not force public/protected/private keyword to be indented at the same
28
- # level as the def keyword. My personal preference is to outdent these keywords
29
- # because I think when scanning code it makes it easier to identify the
30
- # sections of code and visually separate them. When the keyword is at the same
31
- # level I think it sort of blends in with the def keywords and makes it harder
32
- # to scan the code and see where the sections are.
33
- AccessControl:
34
- Enabled: false
35
-
36
27
  # Limit line length
37
28
  LineLength:
38
29
  Max: 79
data/lib/promise.rb CHANGED
@@ -6,6 +6,8 @@ require 'promise/callback'
6
6
  require 'promise/progress'
7
7
 
8
8
  class Promise
9
+ include Promise::Progress
10
+
9
11
  attr_reader :value, :reason
10
12
 
11
13
  def initialize
@@ -27,7 +29,7 @@ class Promise
27
29
  end
28
30
 
29
31
  def then(on_fulfill = nil, on_reject = nil, &block)
30
- on_fulfill = block if block
32
+ on_fulfill ||= block
31
33
  next_promise = add_callbacks(on_fulfill, on_reject)
32
34
 
33
35
  maybe_dispatch(@on_fulfill.last, @on_reject.last)
@@ -47,7 +49,7 @@ class Promise
47
49
  end
48
50
  end
49
51
 
50
- def reject(reason = nil)
52
+ def reject(reason = RuntimeError)
51
53
  dispatch(@on_reject, reason) do
52
54
  @state = :rejected
53
55
  @reason = reason
@@ -2,18 +2,15 @@
2
2
 
3
3
  class Promise
4
4
  module Progress
5
- def initialize
6
- super
7
- @on_progress = []
8
- end
9
-
10
5
  def on_progress(&block)
11
- @on_progress << block
6
+ @on_progress ||= []
7
+ @on_progress << block if block_given?
8
+ @on_progress
12
9
  end
13
10
 
14
11
  def progress(status)
15
12
  if pending?
16
- @on_progress.each { |block| block.call(status) }
13
+ on_progress.each { |block| block.call(status) }
17
14
  end
18
15
  end
19
16
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  class Promise
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/promise.rb.gemspec CHANGED
@@ -10,9 +10,9 @@ Gem::Specification.new do |spec|
10
10
  spec.authors = ['Lars Gierth']
11
11
  spec.email = ['lars.gierth@gmail.com']
12
12
  spec.description = %q{Promises/A+ for Ruby}
13
- spec.summary = %q{Promises/A+ for Ruby}
13
+ spec.summary = %q{Ruby implementation of the Promises/A+ spec}
14
14
  spec.homepage = 'https://github.com/lgierth/promise'
15
- spec.license = 'MIT'
15
+ spec.license = 'Public Domain'
16
16
 
17
17
  spec.files = `git ls-files`.split($/)
18
18
  spec.test_files = spec.files.grep(%r{^spec/})
data/spec/promise_spec.rb CHANGED
@@ -117,6 +117,14 @@ describe Promise do
117
117
  subject.fulfill(value)
118
118
  expect(result).to eq(value)
119
119
  end
120
+
121
+ it 'takes precedence over block' do
122
+ result = nil
123
+ subject.then(proc { |_| result = :arg }) { |_| result = :block }
124
+
125
+ subject.fulfill(value)
126
+ expect(result).to be(:arg)
127
+ end
120
128
  end
121
129
 
122
130
  describe '3.2.3 on_reject' do
@@ -220,7 +228,7 @@ describe Promise do
220
228
  end
221
229
 
222
230
  it 'rejects promise2 with error raised by on_fulfill' do
223
- promise2 = subject.then(proc { |_| raise error })
231
+ promise2 = subject.then(proc { |_| fail(error) })
224
232
  expect { subject.fulfill(value) }.to raise_error(error)
225
233
 
226
234
  expect(promise2).to be_rejected
@@ -228,7 +236,7 @@ describe Promise do
228
236
  end
229
237
 
230
238
  it 'rejects promise2 with error raised by on_reject' do
231
- promise2 = subject.then(nil, proc { |_| raise error })
239
+ promise2 = subject.then(nil, proc { |_| fail(error) })
232
240
  expect { subject.reject(reason) }.to raise_error(error)
233
241
 
234
242
  expect(promise2).to be_rejected
@@ -305,11 +313,6 @@ describe Promise do
305
313
  end
306
314
 
307
315
  describe '#progress' do
308
- let(:klass) do
309
- Class.new(Promise) { include Promise::Progress }
310
- end
311
- let(:subject) { klass.new }
312
-
313
316
  let(:status) { double('status') }
314
317
 
315
318
  it 'calls the callbacks in the order of calls to #on_progress' do
@@ -355,7 +358,7 @@ describe Promise do
355
358
 
356
359
  it 'does not require a reason' do
357
360
  subject.reject
358
- expect(subject.reason).to be(nil)
361
+ expect(subject.reason).to be(RuntimeError)
359
362
  end
360
363
  end
361
364
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: promise.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-12-07 00:00:00.000000000 Z
12
+ date: 2013-12-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -37,11 +37,12 @@ files:
37
37
  - .gitignore
38
38
  - .rspec
39
39
  - .travis.yml
40
+ - CHANGELOG.md
40
41
  - Gemfile
41
42
  - Gemfile.devtools
42
- - LICENSE
43
43
  - README.md
44
44
  - Rakefile
45
+ - UNLICENSE
45
46
  - config/devtools.yml
46
47
  - config/flay.yml
47
48
  - config/flog.yml
@@ -58,7 +59,7 @@ files:
58
59
  - spec/spec_helper.rb
59
60
  homepage: https://github.com/lgierth/promise
60
61
  licenses:
61
- - MIT
62
+ - Public Domain
62
63
  post_install_message:
63
64
  rdoc_options: []
64
65
  require_paths:
@@ -80,7 +81,7 @@ rubyforge_project:
80
81
  rubygems_version: 1.8.23
81
82
  signing_key:
82
83
  specification_version: 3
83
- summary: Promises/A+ for Ruby
84
+ summary: Ruby implementation of the Promises/A+ spec
84
85
  test_files:
85
86
  - spec/promise_spec.rb
86
87
  - spec/spec_helper.rb