proc 0.0.4 → 0.3.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.
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