proc 0.0.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a33f3939a8156059653e7edde57a9ac0b0ee46ab04bcb1f08ce098073e884358
4
- data.tar.gz: df8c021333f0f03b6acdaa195b2c39035a4054b026fd1f6c09d08df674aef7f9
3
+ metadata.gz: c3a73eafa61306bca405c0eaec0f8df0d145b22d27a98ee8dcf28db94cdadfa3
4
+ data.tar.gz: b1e95096528fe529ec664a370095269ae26672994178ed62e3c78159c46ed173
5
5
  SHA512:
6
- metadata.gz: 52107c5f8c68d623aead4dab7131fe7ea42c5abc29bf748cb81f2aea80bc0ae2b68447764c02a1d40aca4a21e24521f2bc291e9ae7794bd69d98cb867c7f0959
7
- data.tar.gz: 8bcab50d65564adb5ce355b75dd407f82ccf93dee798c1219def178293978251b6bfff6c588a823eecd52732a6784a971226026f796601941f1866d3d0c00035
6
+ metadata.gz: 9708b1585d8c95deb6016cc5dbed10d5c7bffdfa55421d0392d3c51ec2af5ce334eb5b41283c585795874c7cdf5b035e6eb41ca145ee56d7409147546b61a6bf
7
+ data.tar.gz: ec138355afed48dde883579c73d304196b3cb2b6b8ac7d5a0986b43c5bffb6591fd33c019b516807c46b2c1ef3c6d75df7fc10d5bae10ecfbc38428ea89977c9
data/README.md CHANGED
@@ -0,0 +1,80 @@
1
+ The `proc` gem lets you call codeless web functions through the proc.dev service.
2
+
3
+ ## Getting Started
4
+
5
+ Install `proc` using the `gem` command from the command line:
6
+
7
+ ```
8
+ gem install proc
9
+ ```
10
+
11
+ You will also need a proc.dev account. Create a free account in seconds at [proc.dev](https://proc.dev/).
12
+
13
+ ## Connecting
14
+
15
+ Sign in to your proc.dev account and locate your secret key. Use the secret to create a client connection:
16
+
17
+ ```ruby
18
+ client = Proc.connect("secret-key")
19
+ ```
20
+
21
+ ## Calling Procs
22
+
23
+ You can call available procs through a connected client:
24
+
25
+ ```ruby
26
+ client.core.string.reverse.call("hello")
27
+ => "olleh"
28
+ ```
29
+
30
+ Requests are sent to proc.dev anytime the `call` method is invoked.
31
+
32
+ ## Callable Contexts
33
+
34
+ Proc lookups create contexts that can be called later:
35
+
36
+ ```ruby
37
+ string = client.core.string
38
+
39
+ string.reverse.call("hello")
40
+ => "olleh"
41
+
42
+ string.truncate.call("hello", length: 3)
43
+ => "hel"
44
+ ```
45
+
46
+ Contexts can be configured with default input and arguments using the `with` method:
47
+
48
+ ```ruby
49
+ truncate = client.core.string.truncate.with("default", length: 1)
50
+
51
+ truncate.call
52
+ => "d"
53
+ ```
54
+
55
+ Default input and/or arguments can be overidden for a specific call:
56
+
57
+ ```ruby
58
+ truncate.call(length: 3)
59
+ => "def"
60
+ ```
61
+
62
+ Procs can also be looked up using the hash key syntax:
63
+
64
+ ```ruby
65
+ client["core.string.truncate"].call("hello", length: 3)
66
+ => "hel"
67
+ ```
68
+
69
+ ## Compositions
70
+
71
+ Procs can be composed together to build more complex behavior:
72
+
73
+ ```ruby
74
+ composition = client.core.string.reverse >> client.core.string.truncate(length: 3) >> client.core.string.capitalize
75
+
76
+ composition.call("hello")
77
+ => "Oll"
78
+ ```
79
+
80
+ Compositions are sent to proc.dev in a single request.
@@ -6,4 +6,12 @@ class Proc
6
6
  def self.connect(authorization, **options)
7
7
  Client.new(authorization, **options)
8
8
  end
9
+
10
+ def self.undefined
11
+ @_undefined ||= Object.new
12
+ end
13
+
14
+ def self.undefined?(value)
15
+ value == undefined
16
+ end
9
17
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Proc
4
+ class Argument
5
+ def initialize(name, **options)
6
+ @name = name
7
+ @options = options
8
+ end
9
+
10
+ def serialize
11
+ {"::" => {"name" => @name.to_s}.merge(serialized_options)}
12
+ end
13
+
14
+ def serialized_options
15
+ @options.each_pair.each_with_object({}) { |(key, value), hash|
16
+ hash[key.to_s] = serialize_value(value)
17
+ }
18
+ end
19
+
20
+ private def serialize_value(value)
21
+ if value.respond_to?(:serialize)
22
+ value.serialize
23
+ else
24
+ value
25
+ end
26
+ end
27
+ end
28
+ end
@@ -2,22 +2,105 @@
2
2
 
3
3
  class Proc
4
4
  class Callable
5
- attr_reader :proc
5
+ attr_reader :proc, :input, :arguments
6
6
 
7
- def initialize(proc, client:)
8
- @proc = proc
7
+ def initialize(proc, client:, input: Proc.undefined, arguments: {})
8
+ @proc = proc.to_s
9
9
  @client = client
10
+ @input = input
11
+ @arguments = arguments
10
12
  end
11
13
 
12
- def call(input = nil, **arguments)
13
- @client.call(@proc, input, **arguments)
14
+ def initialize_copy(_)
15
+ @input = input.dup
16
+ @arguments = arguments.dup
17
+ end
18
+
19
+ def call(input = input_omitted = true, **arguments)
20
+ callable = self.class.new(
21
+ @proc,
22
+ client: @client,
23
+ input: input_omitted ? @input : input,
24
+ arguments: @arguments.merge(arguments)
25
+ )
26
+
27
+ @client.call(@proc, callable.serialized_input, **callable.serialized_arguments)
28
+ end
29
+
30
+ def with(input = input_omitted = true, **arguments)
31
+ self.class.new(
32
+ @proc,
33
+ client: @client,
34
+ input: input_omitted ? @input : input,
35
+ arguments: @arguments.merge(arguments)
36
+ )
14
37
  end
15
38
 
16
39
  def >>(other)
17
- composed = Composition.new(client: @client)
40
+ composed = Composition.new(client: @client, input: @input)
18
41
  composed << self
19
42
  composed << other
20
43
  composed
21
44
  end
45
+
46
+ def serialize
47
+ wrapped = {"[]" => [[@proc, serialized_arguments]]}
48
+
49
+ unless Proc.undefined?(@input)
50
+ wrapped["<<"] = serialized_input
51
+ end
52
+
53
+ {"{}" => wrapped}
54
+ end
55
+
56
+ def serialized_input
57
+ serialize_value(@input)
58
+ end
59
+
60
+ def serialized_arguments
61
+ @arguments.each_pair.each_with_object({}) { |(key, value), hash|
62
+ hash[key.to_s] = serialize_value(value)
63
+ }
64
+ end
65
+
66
+ def [](proc)
67
+ Callable.new(
68
+ [@proc, proc].join("."),
69
+ client: @client,
70
+ input: @input,
71
+ arguments: @arguments
72
+ )
73
+ end
74
+
75
+ IGNORE_MISSING = %i[to_hash].freeze
76
+
77
+ def method_missing(name, input = input_omitted = true, **arguments)
78
+ if IGNORE_MISSING.include?(name)
79
+ super
80
+ else
81
+ Callable.new(
82
+ [@proc, name].join("."),
83
+ client: @client,
84
+ input: input_omitted ? @input : input,
85
+ arguments: @arguments.merge(arguments)
86
+ )
87
+ end
88
+ end
89
+
90
+ def respond_to_missing?(name, *)
91
+ if IGNORE_MISSING.include?(name)
92
+ super
93
+ else
94
+ true
95
+ end
96
+ end
97
+
98
+ private def serialize_value(value)
99
+ if value.respond_to?(:serialize)
100
+ value.serialize
101
+ else
102
+ value
103
+ end
104
+ end
22
105
  end
23
106
  end
@@ -3,6 +3,7 @@
3
3
  require "async"
4
4
  require "oj"
5
5
 
6
+ require_relative "argument"
6
7
  require_relative "callable"
7
8
  require_relative "composition"
8
9
  require_relative "null_logger"
@@ -13,7 +14,7 @@ class Proc
13
14
  class Error < StandardError
14
15
  end
15
16
 
16
- class ArgumentError < ::ArgumentError
17
+ class Invalid < ::ArgumentError
17
18
  end
18
19
 
19
20
  class Undefined < ::NameError
@@ -22,10 +23,13 @@ class Proc
22
23
  class Unauthorized < Error
23
24
  end
24
25
 
26
+ class Forbidden < Error
27
+ end
28
+
25
29
  class Unavailable < Error
26
30
  end
27
31
 
28
- class RateLimited < Error
32
+ class Limited < Error
29
33
  end
30
34
 
31
35
  class Timeout < Error
@@ -60,10 +64,6 @@ class Proc
60
64
  @resets_at
61
65
  end
62
66
 
63
- def compose(&block)
64
- Composition.new(client: self, &block)
65
- end
66
-
67
67
  DEFAULT_HEADERS = {
68
68
  "accept" => "application/json",
69
69
  "content-type" => "application/json"
@@ -71,14 +71,14 @@ class Proc
71
71
 
72
72
  def call(proc = nil, input = nil, **arguments)
73
73
  Async(logger: NullLogger) { |task|
74
- body = { "<<" => input }
74
+ body = {"<>" => true}
75
+
76
+ unless Proc.undefined?(input)
77
+ body["<<"] = serialize_value(input)
78
+ end
75
79
 
76
80
  arguments.each_pair do |key, value|
77
- body[key.to_s] = if value.respond_to?(:serialize)
78
- value.serialize
79
- else
80
- value
81
- end
81
+ body[key.to_s] = serialize_value(value)
82
82
  end
83
83
 
84
84
  headers = {
@@ -86,14 +86,14 @@ class Proc
86
86
  }.merge(DEFAULT_HEADERS)
87
87
 
88
88
  begin
89
- response = super(:post, build_uri(proc), headers: headers, body: Oj.dump(body, mode: :json), task: task)
89
+ response = super(:post, build_uri(proc), headers: headers, body: Oj.dump(body, mode: :custom), task: task)
90
90
 
91
91
  @remaining = response.headers["x-rate-limit-remaining"].to_s.to_i
92
92
  @resets_at = Time.at(response.headers["x-rate-limit-reset"].to_s.to_i)
93
93
 
94
- payload = Oj.load(response.read, mode: :strict)
95
- rescue => error
96
- raise Proc::Unavailable, error.message
94
+ payload = Oj.load(response.read, mode: :compat)
95
+ rescue
96
+ raise Proc::Unavailable
97
97
  ensure
98
98
  response&.close
99
99
  end
@@ -102,27 +102,56 @@ class Proc
102
102
  when 200
103
103
  payload[">>"]
104
104
  when 400
105
- raise Proc::ArgumentError, payload.dig("error", "message")
106
- when 403
105
+ raise Proc::Invalid, payload.dig("error", "message")
106
+ when 401
107
107
  raise Proc::Unauthorized, payload.dig("error", "message")
108
+ when 403
109
+ raise Proc::Forbidden, payload.dig("error", "message")
108
110
  when 404
109
111
  raise Proc::Undefined, payload.dig("error", "message")
110
112
  when 408
111
113
  raise Proc::Timeout, payload.dig("error", "message")
112
114
  when 429
113
- raise Proc::RateLimited, payload.dig("error", "message")
115
+ raise Proc::Limited, payload.dig("error", "message")
114
116
  when 500
115
117
  raise Proc::Error, payload.dig("error", "message")
118
+ when 508
119
+ raise Proc::Error, payload.dig("error", "message")
116
120
  else
117
121
  raise Proc::Error, "unhandled"
118
122
  end
119
123
  }.wait
120
124
  end
121
125
 
126
+ def method_missing(name, input = input_omitted = true, **arguments)
127
+ if input_omitted
128
+ Callable.new(name, client: self, arguments: arguments)
129
+ else
130
+ Callable.new(name, client: self, input: input, arguments: arguments)
131
+ end
132
+ end
133
+
134
+ def respond_to_missing?(name, *)
135
+ true
136
+ end
137
+
138
+ def argument(name, **options)
139
+ Argument.new(name, **options)
140
+ end
141
+ alias_method :arg, :argument
142
+
122
143
  private def build_uri(proc)
123
144
  host_and_path = File.join(@host, proc.to_s.split(".").join("/"))
124
145
 
125
146
  "#{@scheme}://#{host_and_path}"
126
147
  end
148
+
149
+ private def serialize_value(value)
150
+ if value.respond_to?(:serialize)
151
+ value.serialize
152
+ else
153
+ value
154
+ end
155
+ end
127
156
  end
128
157
  end
@@ -2,23 +2,37 @@
2
2
 
3
3
  class Proc
4
4
  class Composition
5
- require_relative "composition/deferable"
6
- require_relative "composition/evaluator"
5
+ attr_reader :input, :callables, :arguments
7
6
 
8
- def initialize(client:, &block)
7
+ def initialize(client:, input:, callables: [], arguments: {})
9
8
  @client = client
10
- @block = block
11
- @callables = []
9
+ @input = input
10
+ @callables = callables
11
+ @arguments = arguments
12
12
  end
13
13
 
14
14
  def initialize_copy(_)
15
15
  @callables = @callables.dup
16
16
  end
17
17
 
18
- def call(input = nil, **arguments)
19
- # TODO: This will probably call `proc.run`.
20
- #
21
- @client.call("compose", input, proc: build_evaluator(**arguments))
18
+ def call(input = input_omitted = true, **arguments)
19
+ callable = self.class.new(
20
+ client: @client,
21
+ input: input_omitted ? @input : input,
22
+ callables: @callables.dup,
23
+ arguments: @arguments.merge(arguments)
24
+ )
25
+
26
+ @client.call("proc.exec", nil, proc: callable.serialize)
27
+ end
28
+
29
+ def with(input = input_omitted = true, **arguments)
30
+ self.class.new(
31
+ client: @client,
32
+ input: input_omitted ? @input : input,
33
+ callables: @callables.dup,
34
+ arguments: @arguments.merge(arguments)
35
+ )
22
36
  end
23
37
 
24
38
  def >>(other)
@@ -28,31 +42,59 @@ class Proc
28
42
  end
29
43
 
30
44
  def <<(callable)
31
- @callables << callable
45
+ case callable
46
+ when Composition
47
+ merge(callable)
48
+ when Callable
49
+ @callables << callable
50
+ end
32
51
  end
33
52
 
34
- def with(**arguments)
35
- build_evaluator(**arguments)
53
+ def serialize
54
+ wrapped = {"[]" => serialized_calls}
55
+
56
+ unless Proc.undefined?(@input)
57
+ wrapped["<<"] = serialized_input
58
+ end
59
+
60
+ {"{}" => wrapped.merge(serialized_arguments)}
36
61
  end
37
62
 
38
- def serialize
39
- build_evaluator.serialize
40
- rescue ::ArgumentError => error
41
- raise ::ArgumentError, error.message + " (try using `with' to build a composition with arguments)"
63
+ def serialized_calls
64
+ @callables.map { |callable|
65
+ arguments = callable.serialized_arguments
66
+
67
+ unless Proc.undefined?(callable.input)
68
+ arguments["<<"] = callable.serialized_input
69
+ end
70
+
71
+ [callable.proc, arguments]
72
+ }
42
73
  end
43
74
 
44
- private def build_evaluator(**arguments)
45
- evaluator = if @block
46
- @block.call(Evaluator.new, **arguments)
47
- else
48
- Evaluator.new
49
- end
75
+ def serialized_input
76
+ serialize_value(@input)
77
+ end
50
78
 
51
- @callables.each do |callable|
52
- evaluator << Deferable.new(callable.proc)
53
- end
79
+ def serialized_arguments
80
+ @arguments.each_pair.each_with_object({}) { |(key, value), hash|
81
+ hash[key.to_s] = serialize_value(value)
82
+ }
83
+ end
54
84
 
55
- evaluator
85
+ def merge(composition)
86
+ raise ArgumentError, "expected a composition" unless composition.is_a?(self.class)
87
+
88
+ @callables.concat(composition.callables)
89
+ @arguments.merge!(composition.arguments)
90
+ end
91
+
92
+ private def serialize_value(value)
93
+ if value.respond_to?(:serialize)
94
+ value.serialize
95
+ else
96
+ value
97
+ end
56
98
  end
57
99
  end
58
100
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Proc
4
- VERSION = "0.0.4"
4
+ VERSION = "0.3.0"
5
5
 
6
6
  def self.version
7
7
  VERSION
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: proc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Powell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-24 00:00:00.000000000 Z
11
+ date: 2020-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-http
@@ -47,11 +47,10 @@ files:
47
47
  - LICENSE
48
48
  - README.md
49
49
  - lib/proc.rb
50
+ - lib/proc/argument.rb
50
51
  - lib/proc/callable.rb
51
52
  - lib/proc/client.rb
52
53
  - lib/proc/composition.rb
53
- - lib/proc/composition/deferable.rb
54
- - lib/proc/composition/evaluator.rb
55
54
  - lib/proc/http/client.rb
56
55
  - lib/proc/http/request.rb
57
56
  - lib/proc/http/response.rb
@@ -76,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
75
  - !ruby/object:Gem::Version
77
76
  version: '0'
78
77
  requirements: []
79
- rubygems_version: 3.1.2
78
+ rubygems_version: 3.1.4
80
79
  signing_key:
81
80
  specification_version: 4
82
81
  summary: Proc client library.
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Proc
4
- class Composition
5
- class Deferable
6
- attr_reader :proc
7
- attr_writer :arguments
8
-
9
- def initialize(proc)
10
- @proc = proc
11
- @arguments = {}
12
- end
13
-
14
- def serialize
15
- [@proc, serialized_arguments]
16
- end
17
-
18
- private def serialized_arguments
19
- @arguments.each.each_with_object({}) do |(key, value), hash|
20
- hash[key] = if value.respond_to?(:serialize)
21
- value.serialize
22
- else
23
- value
24
- end
25
- end
26
- end
27
- end
28
- end
29
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Proc
4
- class Composition
5
- class Evaluator
6
- attr_reader :callables
7
-
8
- def initialize
9
- @callables = []
10
- end
11
-
12
- def initialize_copy(_)
13
- @callables = @callables.map(&:dup)
14
- end
15
-
16
- def [](proc)
17
- evaluator = dup
18
- evaluator << Deferable.new(proc)
19
- evaluator
20
- end
21
-
22
- def <<(proc)
23
- @callables << proc
24
- end
25
-
26
- def >>(other)
27
- evaluator = dup
28
-
29
- case other
30
- when Evaluator
31
- other.callables.each do |each_callable|
32
- evaluator << each_callable
33
- end
34
- when Callable
35
- evaluator << Deferable.new(other.proc)
36
- end
37
-
38
- evaluator
39
- end
40
-
41
- def call(**arguments)
42
- evaluator = dup
43
- evaluator.callables.last.arguments = arguments
44
- evaluator
45
- end
46
-
47
- def serialize
48
- { "[]" => @callables.map(&:serialize) }
49
- end
50
- end
51
- end
52
- end