concurrent-ruby 0.1.1.pre.3 → 0.1.1.pre.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -21
  3. data/README.md +275 -279
  4. data/lib/concurrent.rb +27 -28
  5. data/lib/concurrent/agent.rb +114 -108
  6. data/lib/concurrent/cached_thread_pool.rb +129 -130
  7. data/lib/concurrent/defer.rb +65 -67
  8. data/lib/concurrent/event.rb +60 -60
  9. data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
  10. data/lib/concurrent/executor.rb +93 -95
  11. data/lib/concurrent/fixed_thread_pool.rb +95 -89
  12. data/lib/concurrent/functions.rb +120 -120
  13. data/lib/concurrent/future.rb +42 -47
  14. data/lib/concurrent/global_thread_pool.rb +16 -16
  15. data/lib/concurrent/goroutine.rb +29 -29
  16. data/lib/concurrent/null_thread_pool.rb +22 -22
  17. data/lib/concurrent/obligation.rb +67 -67
  18. data/lib/concurrent/promise.rb +174 -166
  19. data/lib/concurrent/reactor.rb +161 -162
  20. data/lib/concurrent/reactor/drb_async_demux.rb +74 -74
  21. data/lib/concurrent/reactor/tcp_sync_demux.rb +98 -98
  22. data/lib/concurrent/thread_pool.rb +76 -69
  23. data/lib/concurrent/utilities.rb +32 -34
  24. data/lib/concurrent/version.rb +3 -3
  25. data/lib/concurrent_ruby.rb +1 -1
  26. data/md/agent.md +123 -123
  27. data/md/defer.md +174 -174
  28. data/md/event.md +32 -32
  29. data/md/executor.md +176 -176
  30. data/md/future.md +83 -83
  31. data/md/goroutine.md +52 -52
  32. data/md/obligation.md +32 -32
  33. data/md/promise.md +227 -227
  34. data/md/thread_pool.md +224 -224
  35. data/spec/concurrent/agent_spec.rb +386 -380
  36. data/spec/concurrent/cached_thread_pool_spec.rb +125 -125
  37. data/spec/concurrent/defer_spec.rb +195 -195
  38. data/spec/concurrent/event_machine_defer_proxy_spec.rb +256 -253
  39. data/spec/concurrent/event_spec.rb +134 -134
  40. data/spec/concurrent/executor_spec.rb +184 -184
  41. data/spec/concurrent/fixed_thread_pool_spec.rb +83 -84
  42. data/spec/concurrent/functions_spec.rb +217 -217
  43. data/spec/concurrent/future_spec.rb +108 -108
  44. data/spec/concurrent/global_thread_pool_spec.rb +38 -38
  45. data/spec/concurrent/goroutine_spec.rb +67 -67
  46. data/spec/concurrent/null_thread_pool_spec.rb +54 -54
  47. data/spec/concurrent/obligation_shared.rb +135 -121
  48. data/spec/concurrent/promise_spec.rb +312 -305
  49. data/spec/concurrent/reactor/drb_async_demux_spec.rb +12 -12
  50. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +12 -12
  51. data/spec/concurrent/reactor_spec.rb +351 -10
  52. data/spec/concurrent/thread_pool_shared.rb +209 -210
  53. data/spec/concurrent/utilities_spec.rb +74 -74
  54. data/spec/spec_helper.rb +44 -30
  55. metadata +11 -22
  56. data/lib/concurrent/smart_mutex.rb +0 -66
  57. data/spec/concurrent/smart_mutex_spec.rb +0 -234
@@ -1,98 +1,98 @@
1
- require 'socket'
2
- require 'drb/acl'
3
- require 'functional'
4
- require 'concurrent/reactor'
5
-
6
- module Concurrent
7
- class Reactor
8
-
9
- class TcpSyncDemux
10
-
11
- behavior(:sync_event_demux)
12
-
13
- DEFAULT_HOST = '127.0.0.1'
14
- DEFAULT_PORT = 12345
15
- DEFAULT_ACL = %[allow all]
16
-
17
- def initialize(opts = {})
18
- @host = opts[:host] || DEFAULT_HOST
19
- @port = opts[:port] || DEFAULT_PORT
20
- @acl = ACL.new(opts[:acl] || DEFAULT_ACL)
21
- end
22
-
23
- def start
24
- @server = TCPServer.new(@host, @port)
25
- end
26
-
27
- def stop
28
- atomic {
29
- @socket.close unless @socket.nil?
30
- @server.close unless @server.nil?
31
- @server = @socket = nil
32
- }
33
- end
34
-
35
- def stopped?
36
- return @server.nil?
37
- end
38
-
39
- def accept
40
- @socket = @server.accept if @socket.nil?
41
- return nil unless @acl.allow_socket?(@socket)
42
- event, args = get_message(@socket)
43
- return nil if event.nil?
44
- return Reactor::EventContext.new(event, args)
45
- end
46
-
47
- def respond(result, message)
48
- return nil if @socket.nil?
49
- @socket.puts(format_message(result, message))
50
- end
51
-
52
- def close
53
- @socket.close
54
- @socket = nil
55
- end
56
-
57
- def self.format_message(event, *args)
58
- args = args.reduce('') do |memo, arg|
59
- memo << "#{arg}\r\n"
60
- end
61
- return ":#{event}\r\n#{args}\r\n"
62
- end
63
- def format_message(*args) self.class.format_message(*args); end
64
-
65
- def self.parse_message(message)
66
- return atomic {
67
- event = message.first.match /^:?(\w+)/
68
- event = event[1].to_s.downcase.to_sym unless event.nil?
69
-
70
- args = message.slice(1, message.length) || []
71
-
72
- [event, args]
73
- }
74
- end
75
- def parse_message(*args) self.class.parse_message(*args); end
76
-
77
- def self.get_message(socket)
78
- message = []
79
- while line = socket.gets
80
- if line.nil? || (line = line.strip).empty?
81
- break
82
- else
83
- message << line
84
- end
85
- end
86
-
87
- if message.empty?
88
- return nil
89
- else
90
- return parse_message(message)
91
- end
92
- end
93
- def get_message(*args) self.class.get_message(*args); end
94
- end
95
-
96
- TcpSyncDemultiplexer = TcpSyncDemux
97
- end
98
- end
1
+ require 'socket'
2
+ require 'drb/acl'
3
+ require 'functional'
4
+ require 'concurrent/reactor'
5
+
6
+ module Concurrent
7
+ class Reactor
8
+
9
+ class TcpSyncDemux
10
+
11
+ behavior(:sync_event_demux)
12
+
13
+ DEFAULT_HOST = '127.0.0.1'
14
+ DEFAULT_PORT = 12345
15
+ DEFAULT_ACL = %[allow all]
16
+
17
+ def initialize(opts = {})
18
+ @host = opts[:host] || DEFAULT_HOST
19
+ @port = opts[:port] || DEFAULT_PORT
20
+ @acl = ACL.new(opts[:acl] || DEFAULT_ACL)
21
+ end
22
+
23
+ def start
24
+ @server = TCPServer.new(@host, @port)
25
+ end
26
+
27
+ def stop
28
+ atomic {
29
+ @socket.close unless @socket.nil?
30
+ @server.close unless @server.nil?
31
+ @server = @socket = nil
32
+ }
33
+ end
34
+
35
+ def stopped?
36
+ return @server.nil?
37
+ end
38
+
39
+ def accept
40
+ @socket = @server.accept if @socket.nil?
41
+ return nil unless @acl.allow_socket?(@socket)
42
+ event, args = get_message(@socket)
43
+ return nil if event.nil?
44
+ return Reactor::EventContext.new(event, args)
45
+ end
46
+
47
+ def respond(result, message)
48
+ return nil if @socket.nil?
49
+ @socket.puts(format_message(result, message))
50
+ end
51
+
52
+ def close
53
+ @socket.close
54
+ @socket = nil
55
+ end
56
+
57
+ def self.format_message(event, *args)
58
+ args = args.reduce('') do |memo, arg|
59
+ memo << "#{arg}\r\n"
60
+ end
61
+ return ":#{event}\r\n#{args}\r\n"
62
+ end
63
+ def format_message(*args) self.class.format_message(*args); end
64
+
65
+ def self.parse_message(message)
66
+ return atomic {
67
+ event = message.first.match /^:?(\w+)/
68
+ event = event[1].to_s.downcase.to_sym unless event.nil?
69
+
70
+ args = message.slice(1, message.length) || []
71
+
72
+ [event, args]
73
+ }
74
+ end
75
+ def parse_message(*args) self.class.parse_message(*args); end
76
+
77
+ def self.get_message(socket)
78
+ message = []
79
+ while line = socket.gets
80
+ if line.nil? || (line = line.strip).empty?
81
+ break
82
+ else
83
+ message << line
84
+ end
85
+ end
86
+
87
+ if message.empty?
88
+ return nil
89
+ else
90
+ return parse_message(message)
91
+ end
92
+ end
93
+ def get_message(*args) self.class.get_message(*args); end
94
+ end
95
+
96
+ TcpSyncDemultiplexer = TcpSyncDemux
97
+ end
98
+ end
@@ -1,69 +1,76 @@
1
- require 'functional/behavior'
2
-
3
- require 'concurrent/event'
4
- require 'concurrent/utilities'
5
-
6
- behavior_info(:thread_pool,
7
- running?: 0,
8
- shutdown?: 0,
9
- killed?: 0,
10
- shutdown: 0,
11
- kill: 0,
12
- size: 0,
13
- wait_for_termination: -1,
14
- post: -1,
15
- :<< => 1,
16
- status: 0)
17
-
18
- behavior_info(:global_thread_pool,
19
- post: -1,
20
- :<< => 1)
21
-
22
- module Concurrent
23
-
24
- class ThreadPool
25
-
26
- def initialize
27
- @status = :running
28
- @queue = Queue.new
29
- @termination = Event.new
30
- @pool = []
31
- end
32
-
33
- def running?
34
- return @status == :running
35
- end
36
-
37
- def shutdown?
38
- return @status == :shutdown
39
- end
40
-
41
- def killed?
42
- return @status == :killed
43
- end
44
-
45
- def shutdown
46
- atomic {
47
- if @pool.empty?
48
- @status = :shutdown
49
- else
50
- @status = :shuttingdown
51
- @pool.size.times{ @queue << :stop }
52
- end
53
- }
54
- end
55
-
56
- def wait_for_termination(timeout = nil)
57
- if shutdown? || killed?
58
- return true
59
- else
60
- return @termination.wait(timeout)
61
- end
62
- end
63
-
64
- def <<(block)
65
- self.post(&block)
66
- return self
67
- end
68
- end
69
- end
1
+ require 'functional/behavior'
2
+
3
+ require 'concurrent/event'
4
+ require 'concurrent/utilities'
5
+
6
+ behavior_info(:thread_pool,
7
+ running?: 0,
8
+ shutdown?: 0,
9
+ killed?: 0,
10
+ shutdown: 0,
11
+ kill: 0,
12
+ size: 0,
13
+ wait_for_termination: -1,
14
+ post: -1,
15
+ :<< => 1,
16
+ status: 0)
17
+
18
+ behavior_info(:global_thread_pool,
19
+ post: -1,
20
+ :<< => 1)
21
+
22
+ module Concurrent
23
+
24
+ class ThreadPool
25
+
26
+ def initialize
27
+ @status = :running
28
+ @queue = Queue.new
29
+ @termination = Event.new
30
+ @pool = []
31
+ end
32
+
33
+ def running?
34
+ return @status == :running
35
+ end
36
+
37
+ def shutdown?
38
+ return @status == :shutdown
39
+ end
40
+
41
+ def killed?
42
+ return @status == :killed
43
+ end
44
+
45
+ def shutdown
46
+ mutex.synchronize do
47
+ if @pool.empty?
48
+ @status = :shutdown
49
+ else
50
+ @status = :shuttingdown
51
+ @pool.size.times{ @queue << :stop }
52
+ end
53
+ end
54
+ end
55
+
56
+ def wait_for_termination(timeout = nil)
57
+ if shutdown? || killed?
58
+ return true
59
+ else
60
+ return @termination.wait(timeout)
61
+ end
62
+ end
63
+
64
+ def <<(block)
65
+ self.post(&block)
66
+ return self
67
+ end
68
+
69
+ protected
70
+
71
+ # @private
72
+ def mutex # :nodoc:
73
+ @mutex || Mutex.new
74
+ end
75
+ end
76
+ end
@@ -1,34 +1,32 @@
1
- module Kernel
2
-
3
- # Perform the given block as though it were an atomic operation. This means
4
- # that the Ruby scheduler cannot premept the block and context switch to
5
- # another thread. Basically a light wrapper around Ruby's Fiber class.
6
- #
7
- # @note Be very careful about what operations you perform within an atomic
8
- # block. Blocking operations such as I/O should *never* occur within an
9
- # atomic block. In those cases the entire Ruby VM will lock until the
10
- # blocking operation is complete. This would be bad.
11
- #
12
- # @yield calls the block
13
- # @yieldparam args an arbitrary set of block arguments
14
- #
15
- # @param [Array] zero more more optional arguments to pass to the block
16
- def atomic(*args)
17
- raise ArgumentError.new('no block given') unless block_given?
18
- return Fiber.new {
19
- yield(*args)
20
- }.resume
21
- end
22
- module_function :atomic
23
- end
24
-
25
- class Mutex
26
-
27
- def sync_with_timeout(timeout)
28
- Timeout::timeout(timeout) {
29
- self.synchronize {
30
- yield
31
- }
32
- }
33
- end
34
- end
1
+ module Kernel
2
+
3
+ # Perform the given block as though it were an atomic operation. This means
4
+ # that the Ruby scheduler cannot premept the block and context switch to
5
+ # another thread. Basically a light wrapper around Ruby's Fiber class.
6
+ #
7
+ # @note Be very careful about what operations you perform within an atomic
8
+ # block. Blocking operations such as I/O should *never* occur within an
9
+ # atomic block. In those cases the entire Ruby VM will lock until the
10
+ # blocking operation is complete. This would be bad.
11
+ #
12
+ # @yield calls the block
13
+ # @yieldparam args an arbitrary set of block arguments
14
+ #
15
+ # @param [Array] zero more more optional arguments to pass to the block
16
+ def atomic(*args)
17
+ raise ArgumentError.new('no block given') unless block_given?
18
+ return Fiber.new {
19
+ yield(*args)
20
+ }.resume
21
+ end
22
+ module_function :atomic
23
+ end
24
+
25
+ class Mutex
26
+
27
+ def sync_with_timeout(timeout, &block)
28
+ Timeout::timeout(timeout) {
29
+ synchronize(&block)
30
+ }
31
+ end
32
+ end
@@ -1,3 +1,3 @@
1
- module Concurrent
2
- VERSION = '0.1.1.pre.3'
3
- end
1
+ module Concurrent
2
+ VERSION = '0.1.1.pre.4'
3
+ end
@@ -1 +1 @@
1
- require 'concurrent'
1
+ require 'concurrent'
data/md/agent.md CHANGED
@@ -1,123 +1,123 @@
1
- # Secret Agent Man
2
-
3
- Agents are inspired by [Clojure's](http://clojure.org/) [agent](http://clojure.org/agents) keyword.
4
- An agent is a single atomic value that represents an identity. The current value
5
- of the agent can be requested at any time (`deref`). Each agent has a work queue and operates on
6
- the global thread pool (see below). Consumers can `post` code blocks to the
7
- agent. The code block (function) will receive the current value of the agent as its sole
8
- parameter. The return value of the block will become the new value of the agent. Agents support
9
- two error handling modes: fail and continue. A good example of an agent is a shared incrementing
10
- counter, such as the score in a video game.
11
-
12
- An agent must be initialize with an initial value. This value is always accessible via the `value`
13
- (or `deref`) methods. Code blocks sent to the agent will be processed in the order received. As
14
- each block is processed the current value is updated with the result from the block. This update
15
- is an atomic operation so a `deref` will never block and will always return the current value.
16
-
17
- When an agent is created it may be given an optional `validate` block and zero or more `rescue`
18
- blocks. When a new value is calculated the value will be checked against the validator, if present.
19
- If the validator returns `true` the new value will be accepted. If it returns `false` it will be
20
- rejected. If a block raises an exception during execution the list of `rescue` blocks will be
21
- seacrhed in order until one matching the current exception is found. That `rescue` block will
22
- then be called an passed the exception object. If no matching `rescue` block is found, or none
23
- were configured, then the exception will be suppressed.
24
-
25
- Agents also implement Ruby's [Observable](http://ruby-doc.org/stdlib-1.9.3/libdoc/observer/rdoc/Observable.html).
26
- Code that observes an agent will receive a callback with the new value any time the value
27
- is changed.
28
-
29
- ## Examples
30
-
31
- A simple example:
32
-
33
- ```ruby
34
- require 'concurrent'
35
-
36
- score = Concurrent::Agent.new(10)
37
- score.value #=> 10
38
-
39
- score << proc{|current| current + 100 }
40
- sleep(0.1)
41
- score.value #=> 110
42
-
43
- score << proc{|current| current * 2 }
44
- sleep(0.1)
45
- deref score #=> 220
46
-
47
- score << proc{|current| current - 50 }
48
- sleep(0.1)
49
- score.value #=> 170
50
- ```
51
-
52
- With validation and error handling:
53
-
54
- ```ruby
55
- score = agent(0).validate{|value| value <= 1024 }.
56
- rescue(NoMethodError){|ex| puts "Bam!" }.
57
- rescue(ArgumentError){|ex| puts "Pow!" }.
58
- rescue{|ex| puts "Boom!" }
59
- score.value #=> 0
60
-
61
- score << proc{|current| current + 2048 }
62
- sleep(0.1)
63
- score.value #=> 0
64
-
65
- score << proc{|current| raise ArgumentError }
66
- sleep(0.1)
67
- #=> puts "Pow!"
68
- score.value #=> 0
69
-
70
- score << proc{|current| current + 100 }
71
- sleep(0.1)
72
- score.value #=> 100
73
- ```
74
-
75
- With observation:
76
-
77
- ```ruby
78
- bingo = Class.new{
79
- def update(time, score)
80
- puts "Bingo! [score: #{score}, time: #{time}]" if score >= 100
81
- end
82
- }.new
83
-
84
- score = agent(0)
85
- score.add_observer(bingo)
86
-
87
- score << proc{|current| sleep(0.1); current += 30 }
88
- score << proc{|current| sleep(0.1); current += 30 }
89
- score << proc{|current| sleep(0.1); current += 30 }
90
- score << proc{|current| sleep(0.1); current += 30 }
91
-
92
- sleep(1)
93
- #=> Bingo! [score: 120, time: 2013-07-22 21:26:08 -0400]
94
- ```
95
-
96
- ## Copyright
97
-
98
- *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
99
- It is free software and may be redistributed under the terms specified in the LICENSE file.
100
-
101
- ## License
102
-
103
- Released under the MIT license.
104
-
105
- http://www.opensource.org/licenses/mit-license.php
106
-
107
- > Permission is hereby granted, free of charge, to any person obtaining a copy
108
- > of this software and associated documentation files (the "Software"), to deal
109
- > in the Software without restriction, including without limitation the rights
110
- > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
111
- > copies of the Software, and to permit persons to whom the Software is
112
- > furnished to do so, subject to the following conditions:
113
- >
114
- > The above copyright notice and this permission notice shall be included in
115
- > all copies or substantial portions of the Software.
116
- >
117
- > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
118
- > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
119
- > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
120
- > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
121
- > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
122
- > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
123
- > THE SOFTWARE.
1
+ # Secret Agent Man
2
+
3
+ Agents are inspired by [Clojure's](http://clojure.org/) [agent](http://clojure.org/agents) keyword.
4
+ An agent is a single atomic value that represents an identity. The current value
5
+ of the agent can be requested at any time (`deref`). Each agent has a work queue and operates on
6
+ the global thread pool (see below). Consumers can `post` code blocks to the
7
+ agent. The code block (function) will receive the current value of the agent as its sole
8
+ parameter. The return value of the block will become the new value of the agent. Agents support
9
+ two error handling modes: fail and continue. A good example of an agent is a shared incrementing
10
+ counter, such as the score in a video game.
11
+
12
+ An agent must be initialize with an initial value. This value is always accessible via the `value`
13
+ (or `deref`) methods. Code blocks sent to the agent will be processed in the order received. As
14
+ each block is processed the current value is updated with the result from the block. This update
15
+ is an atomic operation so a `deref` will never block and will always return the current value.
16
+
17
+ When an agent is created it may be given an optional `validate` block and zero or more `rescue`
18
+ blocks. When a new value is calculated the value will be checked against the validator, if present.
19
+ If the validator returns `true` the new value will be accepted. If it returns `false` it will be
20
+ rejected. If a block raises an exception during execution the list of `rescue` blocks will be
21
+ seacrhed in order until one matching the current exception is found. That `rescue` block will
22
+ then be called an passed the exception object. If no matching `rescue` block is found, or none
23
+ were configured, then the exception will be suppressed.
24
+
25
+ Agents also implement Ruby's [Observable](http://ruby-doc.org/stdlib-1.9.3/libdoc/observer/rdoc/Observable.html).
26
+ Code that observes an agent will receive a callback with the new value any time the value
27
+ is changed.
28
+
29
+ ## Examples
30
+
31
+ A simple example:
32
+
33
+ ```ruby
34
+ require 'concurrent'
35
+
36
+ score = Concurrent::Agent.new(10)
37
+ score.value #=> 10
38
+
39
+ score << proc{|current| current + 100 }
40
+ sleep(0.1)
41
+ score.value #=> 110
42
+
43
+ score << proc{|current| current * 2 }
44
+ sleep(0.1)
45
+ deref score #=> 220
46
+
47
+ score << proc{|current| current - 50 }
48
+ sleep(0.1)
49
+ score.value #=> 170
50
+ ```
51
+
52
+ With validation and error handling:
53
+
54
+ ```ruby
55
+ score = agent(0).validate{|value| value <= 1024 }.
56
+ rescue(NoMethodError){|ex| puts "Bam!" }.
57
+ rescue(ArgumentError){|ex| puts "Pow!" }.
58
+ rescue{|ex| puts "Boom!" }
59
+ score.value #=> 0
60
+
61
+ score << proc{|current| current + 2048 }
62
+ sleep(0.1)
63
+ score.value #=> 0
64
+
65
+ score << proc{|current| raise ArgumentError }
66
+ sleep(0.1)
67
+ #=> puts "Pow!"
68
+ score.value #=> 0
69
+
70
+ score << proc{|current| current + 100 }
71
+ sleep(0.1)
72
+ score.value #=> 100
73
+ ```
74
+
75
+ With observation:
76
+
77
+ ```ruby
78
+ bingo = Class.new{
79
+ def update(time, score)
80
+ puts "Bingo! [score: #{score}, time: #{time}]" if score >= 100
81
+ end
82
+ }.new
83
+
84
+ score = agent(0)
85
+ score.add_observer(bingo)
86
+
87
+ score << proc{|current| sleep(0.1); current += 30 }
88
+ score << proc{|current| sleep(0.1); current += 30 }
89
+ score << proc{|current| sleep(0.1); current += 30 }
90
+ score << proc{|current| sleep(0.1); current += 30 }
91
+
92
+ sleep(1)
93
+ #=> Bingo! [score: 120, time: 2013-07-22 21:26:08 -0400]
94
+ ```
95
+
96
+ ## Copyright
97
+
98
+ *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
99
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
100
+
101
+ ## License
102
+
103
+ Released under the MIT license.
104
+
105
+ http://www.opensource.org/licenses/mit-license.php
106
+
107
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
108
+ > of this software and associated documentation files (the "Software"), to deal
109
+ > in the Software without restriction, including without limitation the rights
110
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
111
+ > copies of the Software, and to permit persons to whom the Software is
112
+ > furnished to do so, subject to the following conditions:
113
+ >
114
+ > The above copyright notice and this permission notice shall be included in
115
+ > all copies or substantial portions of the Software.
116
+ >
117
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
118
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
119
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
120
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
121
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
122
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
123
+ > THE SOFTWARE.