proc 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9b1cac83c7cff9f09dc94086b6b58e43cf686c4faa28e95aecda09c73e9eebbc
4
+ data.tar.gz: e349377c73af33cdff5dfb488dbfd199ec37b9c1db84132e82dde7d65889b71b
5
+ SHA512:
6
+ metadata.gz: e9b7322deb5945ddac4cff0c12c444d8c90d88b917619176bc2983370e215f54ebafdf83e82a4778424fc94a9ed67149596ebb7fd7378d0bea8d0fcdac611a67
7
+ data.tar.gz: 91286717d2ee7d67a8021f3aedf2debec4c2da88e2f9abbe34dadd751f02c64f227a40dbdb8e3f6b95ed0ca5ead33557b096683594b3b5e32f10aab5eaf23367
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ This software is licensed under the MIT License.
2
+
3
+ Copyright 2020 Metabahn.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a
6
+ copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to permit
10
+ persons to whom the Software is furnished to do so, subject to the
11
+ following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included
14
+ in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
19
+ NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
20
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
21
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
22
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,61 @@
1
+ # Pass `token: "..."` or default to `PROC_ACCESS_TOKEN`:
2
+ #
3
+ client = Proc.connect
4
+
5
+ # Not chainable, returns the value:
6
+ #
7
+ client.stdlib.echo("bar")
8
+ # => bar
9
+
10
+ # Create chains using `chain` (perhaps alias as `with`):
11
+ #
12
+ client.chain("foo") { |chain| chain.stdlib.reverse.capitalize.truncate(2) }
13
+ # => Oo
14
+
15
+ # Or return a chain to pass around:
16
+ #
17
+ chain = client.chain("foo")
18
+ # => <Proc::Chain ...>
19
+ chain.stdlib.echo("bar")
20
+
21
+ # Call `value` or `perform` to evaluate the chain:
22
+ #
23
+ chain.value
24
+
25
+ # Calling a non-existent proc raises a Proc::NameError,
26
+ #
27
+ chain.nonexistent
28
+
29
+ # Pass values through different contexts:
30
+ #
31
+ client.chain("foo") { |chain| chain.stdlib.reverse >> chain.whatever.other }
32
+
33
+ # Use from the cli (included with the ruby gem OR as a go library):
34
+ #
35
+ # proc echo "bar" -t access-token
36
+ # proc chain "bar" "stdlib.reverse >> whatever.other" -t access-token
37
+
38
+ #########################################################
39
+ # alternatives so that we don't have to fetch metadata: #
40
+ #########################################################
41
+
42
+ proc = Proc.connect
43
+
44
+ # I like this because it's obvious how to get a reference as well as just make the call.
45
+ #
46
+ proc["stdlib.echo"].call("foo")
47
+
48
+ # This works for simple composition, where the value is simply passed through.
49
+ #
50
+ my_proc = proc["stdlib.reverse"] >> proc["stdlib.capitalize"]
51
+
52
+ my_proc.call("foo")
53
+
54
+ # Perhaps something like this, where we compose a proc with arguments. Each is implicitly called, or
55
+ # call explicitly to pass an argument to the proc. I sort of like the simplicity of this.
56
+ #
57
+ my_proc = proc.compose { |context, length:|
58
+ context["stdlib.reverse"] >> context["stdlib.capitalize"] >> context["stdlib.truncate"].call(length)
59
+ }
60
+
61
+ my_proc.call("foo", length: 2)
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO: Introduce inspectable everywhere (just bundle from pakyow).
4
+
5
+ require_relative "proc/client"
6
+
7
+ class Proc
8
+ def self.connect(authorization, **options)
9
+ Client.new(authorization, **options)
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Proc
4
+ class Callable
5
+ attr_reader :proc
6
+
7
+ def initialize(proc, client:)
8
+ @proc = proc
9
+ @client = client
10
+ end
11
+
12
+ def call(input = nil, **arguments)
13
+ @client.perform(@proc, input, **arguments)
14
+ end
15
+
16
+ def >>(callable)
17
+ composed = Composition.new(client: @client)
18
+ composed << self
19
+ composed << callable
20
+ composed
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "oj"
4
+
5
+ require_relative "callable"
6
+ require_relative "composition"
7
+
8
+ require_relative "http/client"
9
+
10
+ class Proc
11
+ class Error < StandardError
12
+ end
13
+
14
+ class ArgumentError < ::ArgumentError
15
+ end
16
+
17
+ class Undefined < ::NameError
18
+ end
19
+
20
+ class Unauthorized < Error
21
+ end
22
+
23
+ class Unavailable < Error
24
+ end
25
+
26
+ class Client < Http::Client
27
+ def initialize(authorization, scheme: "https", host: "proc.dev")
28
+ @authorization = authorization
29
+ @scheme = scheme
30
+ @host = host
31
+
32
+ super()
33
+ end
34
+
35
+ def [](proc)
36
+ Callable.new(proc, client: self)
37
+ end
38
+
39
+ def compose(&block)
40
+ Composition.new(client: self, &block)
41
+ end
42
+
43
+ DEFAULT_HEADERS = {
44
+ "content-type" => "application/json"
45
+ }.freeze
46
+
47
+ def perform(proc, input, **arguments)
48
+ body = {
49
+ input: input, arguments: arguments
50
+ }
51
+
52
+ headers = {
53
+ "authorization" => "Bearer #{@authorization}"
54
+ }.merge(DEFAULT_HEADERS)
55
+
56
+ begin
57
+ response = call(:post, build_uri(proc), headers: headers, body: Oj.dump(body, mode: :json))
58
+ rescue => error
59
+ raise Proc::Unavailable, error.message
60
+ end
61
+
62
+ payload = if response.body
63
+ Oj.load(response.body.read, mode: :strict)
64
+ end
65
+
66
+ case response.status
67
+ when 200
68
+ payload["value"]
69
+ when 400
70
+ raise Proc::ArgumentError, payload.dig("error", "message")
71
+ when 403
72
+ raise Proc::Unauthorized, payload.dig("error", "message")
73
+ when 404
74
+ raise Proc::Undefined, payload.dig("error", "message")
75
+ when 500
76
+ raise Proc::Error, payload.dig("error", "message")
77
+ end
78
+ end
79
+
80
+ private def build_uri(proc)
81
+ host_and_path = File.join(@host, proc.split(".").join("/"))
82
+
83
+ "#{@scheme}://#{host_and_path}"
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Proc
4
+ class Composition
5
+ require_relative "composition/deferable"
6
+ require_relative "composition/evaluator"
7
+
8
+ def initialize(client:, &block)
9
+ @client = client
10
+ @block = block
11
+ @callables = []
12
+ end
13
+
14
+ def initialize_copy(_)
15
+ @callables = @callables.dup
16
+ end
17
+
18
+ def call(input, **arguments)
19
+ if @block
20
+ evaluator = @block.call(Evaluator.new, **arguments)
21
+ @client.perform("compose", input, procs: evaluator.serialize.concat(serialize))
22
+ else
23
+ @client.perform("compose", input, procs: serialize)
24
+ end
25
+ end
26
+
27
+ def >>(callable)
28
+ composed = dup
29
+ composed << callable
30
+ composed
31
+ end
32
+
33
+ def <<(callable)
34
+ @callables << callable
35
+ end
36
+
37
+ protected def serialize
38
+ @callables.map(&:proc)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,19 @@
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, @arguments]
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,52 @@
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 >>(callable)
27
+ evaluator = dup
28
+
29
+ case callable
30
+ when Evaluator
31
+ callable.callables.each do |each_callable|
32
+ evaluator << each_callable
33
+ end
34
+ when Callable
35
+ evaluator << Deferable.new(callable.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
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "async"
4
+ require "async/http/internet"
5
+
6
+ require "protocol/http/body/streamable"
7
+
8
+ require_relative "request"
9
+ require_relative "response"
10
+
11
+ class Proc
12
+ module Http
13
+ class Client
14
+ class NullLogger
15
+ class << self
16
+ def method_missing(*, **)
17
+ end
18
+
19
+ def respond_to_missing?(*)
20
+ true
21
+ end
22
+ end
23
+ end
24
+
25
+ def initialize
26
+ @internet = Async::HTTP::Internet.new
27
+ @responses = {}
28
+ end
29
+
30
+ def call(method, uri, params: {}, headers: {}, body: nil)
31
+ request = Request.new(method: method, uri: uri, params: params, headers: headers, body: body)
32
+
33
+ Async(logger: NullLogger) {
34
+ async_request = @internet.call(*request.callable)
35
+ wrap_async_response_for_request(async_request, request)
36
+ }.wait
37
+ end
38
+
39
+ def close
40
+ @responses.each_value(&:close)
41
+ @responses.clear
42
+ @internet.close
43
+ end
44
+
45
+ def count
46
+ @responses.count
47
+ end
48
+
49
+ private def wrap_async_response_for_request(async_response, request)
50
+ Protocol::HTTP::Body::Streamable.wrap(async_response) do
51
+ @responses.delete(async_response)
52
+ end
53
+
54
+ response = Response.new(
55
+ request: request,
56
+ status: async_response.status,
57
+ version: async_response.version,
58
+ headers: async_response.headers,
59
+ body: async_response.body
60
+ )
61
+
62
+ @responses[async_response] = response
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Proc
4
+ module Http
5
+ class Request
6
+ attr_reader :method, :uri, :params, :headers, :body
7
+
8
+ def initialize(method:, uri:, params:, headers:, body:)
9
+ @method = method
10
+ @uri = uri
11
+ @params = params
12
+ @headers = headers
13
+ @body = body
14
+ end
15
+
16
+ def callable
17
+ return @method.to_s.upcase, finalize_uri(@uri, @params), @headers, @body
18
+ end
19
+
20
+ private def finalize_uri(uri, params)
21
+ "#{uri}#{query_string(params)}"
22
+ end
23
+
24
+ private def query_string(params)
25
+ params = params.compact
26
+
27
+ if params.any?
28
+ "?" + params.compact.map { |key, value|
29
+ "#{key}=#{value}"
30
+ }.join("&")
31
+ else
32
+ ""
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Proc
4
+ module Http
5
+ class Response
6
+ attr_reader :request, :status, :version, :headers, :body
7
+
8
+ def initialize(request:, status:, version:, headers:, body:)
9
+ @request = request
10
+ @status = status
11
+ @version = version
12
+ @headers = headers
13
+ @body = body
14
+
15
+ @stream = nil
16
+ @stream_blocks = []
17
+ end
18
+
19
+ def stream(&block)
20
+ @stream_blocks << block
21
+
22
+ @stream ||= Async {
23
+ @body.each do |chunk|
24
+ @stream_blocks.each do |stream_callback|
25
+ stream_callback.call(chunk)
26
+ end
27
+ end
28
+ }
29
+ end
30
+
31
+ def close
32
+ @stream&.stop
33
+ @stream_blocks.clear
34
+ @body.close
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Proc
4
+ VERSION = "0.0.1"
5
+
6
+ def self.version
7
+ VERSION
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: proc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Bryan Powell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-10-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: async-http
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.52.5
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.52.5
27
+ - !ruby/object:Gem::Dependency
28
+ name: oj
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.10'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.10'
41
+ description: Proc client library.
42
+ email: bryan@metabahn.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - LICENSE
48
+ - lib/examples/scratch.rb
49
+ - lib/proc.rb
50
+ - lib/proc/callable.rb
51
+ - lib/proc/client.rb
52
+ - lib/proc/composition.rb
53
+ - lib/proc/composition/deferable.rb
54
+ - lib/proc/composition/evaluator.rb
55
+ - lib/proc/http/client.rb
56
+ - lib/proc/http/request.rb
57
+ - lib/proc/http/response.rb
58
+ - lib/proc/version.rb
59
+ homepage: https://proc.dev/
60
+ licenses:
61
+ - MIT
62
+ metadata: {}
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 2.5.0
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.1.2
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: Proc client library.
82
+ test_files: []