faastruby 0.3.5 → 0.3.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/faastruby/cli/commands/function/new.rb +83 -17
- data/lib/faastruby/version.rb +1 -1
- data/templates/crystal/example-blank/spec/handler_spec.cr +8 -0
- data/templates/crystal/example-blank/spec/helpers/faastruby.cr +77 -0
- data/templates/crystal/example-blank/spec/spec_helper.cr +4 -0
- data/templates/crystal/example-blank/src/handler.cr +25 -0
- data/templates/crystal/example/spec/handler_spec.cr +27 -0
- data/templates/crystal/example/spec/helpers/faastruby.cr +77 -0
- data/templates/crystal/example/spec/spec_helper.cr +4 -0
- data/templates/crystal/example/src/handler.cr +27 -0
- data/{example-blank → templates/ruby/example-blank}/Gemfile +0 -0
- data/{example-blank → templates/ruby/example-blank}/handler.rb +2 -0
- data/{example-blank → templates/ruby/example-blank}/spec/handler_spec.rb +0 -0
- data/{example-blank → templates/ruby/example-blank}/spec/helpers/faastruby.rb +0 -0
- data/{example-blank → templates/ruby/example-blank}/spec/spec_helper.rb +0 -0
- data/{example → templates/ruby/example}/Gemfile +0 -0
- data/{example → templates/ruby/example}/handler.rb +1 -3
- data/{example → templates/ruby/example}/spec/handler_spec.rb +0 -0
- data/{example → templates/ruby/example}/spec/helpers/faastruby.rb +0 -1
- data/{example → templates/ruby/example}/spec/spec_helper.rb +0 -0
- metadata +20 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4086556ca79303b6f5648d703b26a0a7f5170baa4f9736027d9aa728afe1bf47
|
4
|
+
data.tar.gz: 5f8ed48b70913ecd61208b08315ef199492d8f1c02cf7cf7d42101a32313b0ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af818d48b8e17d6d56290f4181b6aadb026a8734bc7ee51d84ce5536dabf52ca728e9c15de7c8c8096e7ea06aff8e8bba2503cd6e44a0dd3b00386ae6bc0c473
|
7
|
+
data.tar.gz: 7f6e33b947949e7d825930e2f5a0d87daf85c0ae2cce858ed01b58da52a39d592b093e52efa9332af4341e9a04b185d4f5d7d849f4d6a09df124de4107177dcd
|
data/Gemfile.lock
CHANGED
@@ -8,29 +8,29 @@ module FaaStRuby
|
|
8
8
|
FaaStRuby::CLI.error(@missing_args, color: nil) if missing_args.any?
|
9
9
|
@function_name = @args.shift
|
10
10
|
@base_dir = "./#{@function_name}"
|
11
|
-
@yaml_content = {
|
12
|
-
'name' => @function_name,
|
13
|
-
'test_command' => 'rspec',
|
14
|
-
'abort_build_when_tests_fail' => true,
|
15
|
-
'abort_deploy_when_tests_fail' => true
|
16
|
-
}
|
17
11
|
parse_options
|
12
|
+
@options['template_path'] ||= 'templates/ruby'
|
18
13
|
@options['template'] ||= 'example'
|
14
|
+
@options['runtime_name'] ||= 'ruby'
|
15
|
+
@options['runtime_version'] ||= '2.5.3'
|
16
|
+
@yaml_content = yaml_for(@options['runtime_name'])
|
19
17
|
end
|
20
18
|
|
21
19
|
def run
|
22
20
|
dir_exists? unless @options['force']
|
23
21
|
copy_template
|
24
22
|
write_yaml
|
25
|
-
|
23
|
+
post_tasks(@options['runtime_name'])
|
26
24
|
end
|
27
25
|
|
28
26
|
def self.help
|
29
|
-
"new".blue + " FUNCTION_NAME [--blank] [--force]" +
|
27
|
+
"new".blue + " FUNCTION_NAME [--blank] [--force] [--runtime]" +
|
30
28
|
<<-EOS
|
31
29
|
|
32
30
|
--blank # Create a blank function
|
33
31
|
--force # Continue if directory already exists and overwrite files
|
32
|
+
--runtime # Choose the runtime. Options are: crystal:0.27.0,
|
33
|
+
# ruby:2.5.3 (default) and ruby:2.6.0
|
34
34
|
EOS
|
35
35
|
end
|
36
36
|
|
@@ -45,6 +45,10 @@ EOS
|
|
45
45
|
while @args.any?
|
46
46
|
option = @args.shift
|
47
47
|
case option
|
48
|
+
when '--runtime'
|
49
|
+
@options['runtime'] = @args.shift
|
50
|
+
@options['runtime_name'], @options['runtime_version'] = @options['runtime'].split(':')
|
51
|
+
@options['template_path'] = "templates/#{@options['runtime_name']}"
|
48
52
|
when '-f', '--force'
|
49
53
|
@options['force'] = true
|
50
54
|
when '--blank'
|
@@ -71,28 +75,90 @@ EOS
|
|
71
75
|
end
|
72
76
|
|
73
77
|
def copy_template
|
74
|
-
source = "#{Gem::Specification.find_by_name("faastruby").gem_dir}/#{@options['template']}"
|
78
|
+
source = "#{Gem::Specification.find_by_name("faastruby").gem_dir}/#{@options['template_path']}/#{@options['template']}"
|
75
79
|
FileUtils.mkdir_p(@base_dir)
|
76
80
|
FileUtils.cp_r("#{source}/.", "#{@base_dir}/")
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
81
|
+
case @options['runtime_name']
|
82
|
+
when 'ruby'
|
83
|
+
puts "+ d #{@base_dir}".green
|
84
|
+
puts "+ d #{@base_dir}/spec".green
|
85
|
+
puts "+ d #{@base_dir}/spec/helpers".green
|
86
|
+
puts "+ f #{@base_dir}/spec/helpers/faastruby.rb".green
|
87
|
+
puts "+ f #{@base_dir}/spec/handler_spec.rb".green
|
88
|
+
puts "+ f #{@base_dir}/spec/spec_helper.rb".green
|
89
|
+
puts "+ f #{@base_dir}/Gemfile".green
|
90
|
+
puts "+ f #{@base_dir}/handler.rb".green
|
91
|
+
when 'crystal'
|
92
|
+
puts "+ d #{@base_dir}".green
|
93
|
+
puts "+ d #{@base_dir}/spec".green
|
94
|
+
puts "+ d #{@base_dir}/spec/helpers".green
|
95
|
+
puts "+ f #{@base_dir}/spec/helpers/faastruby.cr".green
|
96
|
+
puts "+ f #{@base_dir}/spec/handler_spec.cr".green
|
97
|
+
puts "+ f #{@base_dir}/spec/spec_helper.cr".green
|
98
|
+
puts "+ d #{@base_dir}/src".green
|
99
|
+
puts "+ f #{@base_dir}/src/handler.cr".green
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def yaml_for(runtime_name)
|
104
|
+
case runtime_name
|
105
|
+
when 'crystal'
|
106
|
+
test_command = 'crystal spec --no-color'
|
107
|
+
when 'ruby'
|
108
|
+
test_command = 'rspec'
|
109
|
+
else
|
110
|
+
test_command = 'rspec'
|
111
|
+
end
|
112
|
+
{
|
113
|
+
'name' => @function_name,
|
114
|
+
'runtime' => @options['runtime'],
|
115
|
+
'test_command' => test_command,
|
116
|
+
'abort_build_when_tests_fail' => true,
|
117
|
+
'abort_deploy_when_tests_fail' => true
|
118
|
+
}
|
85
119
|
end
|
86
120
|
|
87
121
|
def write_yaml
|
88
122
|
write_file("#{@base_dir}/faastruby.yml", @yaml_content.to_yaml)
|
89
123
|
end
|
90
124
|
|
125
|
+
def post_tasks(runtime_name)
|
126
|
+
case runtime_name
|
127
|
+
when 'ruby'
|
128
|
+
bundle_install
|
129
|
+
when 'crystal'
|
130
|
+
write_shards_file
|
131
|
+
shards_install
|
132
|
+
else
|
133
|
+
bundle_install
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
91
137
|
def bundle_install
|
92
138
|
spinner = spin("Installing gems...")
|
93
139
|
system("bundle install --gemfile=#{@base_dir}/Gemfile > /dev/null")
|
94
140
|
spinner.stop('Done!')
|
95
141
|
end
|
142
|
+
|
143
|
+
def write_shards_file
|
144
|
+
shards = {
|
145
|
+
'name' => @function_name,
|
146
|
+
'version' => '0.1.0',
|
147
|
+
'crystal' => @options['runtime_version'],
|
148
|
+
'targets' => {
|
149
|
+
@function_name => {
|
150
|
+
'main' => 'src/handler.cr'
|
151
|
+
}
|
152
|
+
}
|
153
|
+
}.to_yaml
|
154
|
+
write_file("#{@base_dir}/shard.yml", shards)
|
155
|
+
end
|
156
|
+
|
157
|
+
def shards_install
|
158
|
+
spinner = spin("Installing shards...")
|
159
|
+
system("cd #{@base_dir} && shards install > /dev/null")
|
160
|
+
spinner.stop('Done!')
|
161
|
+
end
|
96
162
|
end
|
97
163
|
end
|
98
164
|
end
|
data/lib/faastruby/version.rb
CHANGED
@@ -0,0 +1,77 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "json"
|
3
|
+
require "base64"
|
4
|
+
|
5
|
+
module FaaStRuby
|
6
|
+
class Event
|
7
|
+
JSON.mapping(
|
8
|
+
body: String?,
|
9
|
+
headers: Hash(String, String),
|
10
|
+
context: String?,
|
11
|
+
query_params: Hash(String, String)
|
12
|
+
)
|
13
|
+
end
|
14
|
+
class Response
|
15
|
+
@@rendered = false
|
16
|
+
property "body"
|
17
|
+
property "status"
|
18
|
+
property "headers"
|
19
|
+
property "io"
|
20
|
+
def initialize(@body : String?, @status : Int32, @headers : Hash(String, String))
|
21
|
+
@io = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(@io : Bytes, @status : Int32, @headers : Hash(String, String))
|
25
|
+
end
|
26
|
+
|
27
|
+
def payload
|
28
|
+
if io
|
29
|
+
hash = {
|
30
|
+
"response" => Base64.encode(io.not_nil!),
|
31
|
+
"status" => status,
|
32
|
+
"headers" => headers
|
33
|
+
}
|
34
|
+
else
|
35
|
+
hash = {
|
36
|
+
"response" => body,
|
37
|
+
"status" => status,
|
38
|
+
"headers" => headers
|
39
|
+
}
|
40
|
+
end
|
41
|
+
hash
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def respond_with(body : String? = nil, status : Int32 = 200, headers : Hash(String, String) = {} of String => String)
|
46
|
+
Response.new(body: body, status: status, headers: headers)
|
47
|
+
end
|
48
|
+
|
49
|
+
def respond_with(io : Bytes, status : Int32 = 200, headers : Hash(String, String) = {} of String => String)
|
50
|
+
Response.new(io: io, status: status, headers: headers)
|
51
|
+
end
|
52
|
+
|
53
|
+
def render(io : Bytes? = nil, js : String? = nil, inline : String? = nil, html : String? = nil, json : String? = nil, yaml : String? = nil, text : String? = nil, status : Int32 = 200, headers : Hash(String, String) = {} of String => String, content_type : String? = nil)
|
54
|
+
headers["Content-Type"] = content_type if content_type
|
55
|
+
case
|
56
|
+
when json
|
57
|
+
headers["Content-Type"] ||= "application/json"
|
58
|
+
resp_body = json
|
59
|
+
when html, inline
|
60
|
+
headers["Content-Type"] ||= "text/html"
|
61
|
+
resp_body = html
|
62
|
+
when text
|
63
|
+
headers["Content-Type"] ||= "text/plain"
|
64
|
+
resp_body = text
|
65
|
+
when yaml
|
66
|
+
headers["Content-Type"] ||= "text/yaml"
|
67
|
+
resp_body = yaml
|
68
|
+
when js
|
69
|
+
headers["Content-Type"] ||= "text/javascript"
|
70
|
+
resp_body = js
|
71
|
+
when io
|
72
|
+
headers["Content-Type"] ||= "application/octet-stream"
|
73
|
+
return respond_with(io: io, status: status, headers: headers)
|
74
|
+
end
|
75
|
+
respond_with(body: resp_body, status: status, headers: headers)
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# To deploy this function, cd into its folder and run:
|
2
|
+
# faastruby deploy-to WORKSPACE_NAME
|
3
|
+
def handler(event : FaaStRuby::Event) : FaaStRuby::Response
|
4
|
+
# event.body : String | Nil
|
5
|
+
# event.headers : Hash(String, String)
|
6
|
+
# event.context : String | Nil
|
7
|
+
# query_params : Hash(String, String)
|
8
|
+
|
9
|
+
# FUNCTION RESPONSE
|
10
|
+
#
|
11
|
+
# You can render text, json, yaml, html or js. Example:
|
12
|
+
# render html: "<p>Hello World!</p>"
|
13
|
+
# render yaml: {"hello" => "world!"}
|
14
|
+
#
|
15
|
+
# Status:
|
16
|
+
# The default status is 200. You can set a custom status like this:
|
17
|
+
# render json: {"error" => "Could not perform the action"}, status: 422
|
18
|
+
#
|
19
|
+
# Headers:
|
20
|
+
# The 'Content-Type' header is automatically set when you use 'render'.
|
21
|
+
# You can set custom headers using a Hash(String, String). Example:
|
22
|
+
# render text: "It Works!", headers: {"TransactionId" => 23928}
|
23
|
+
|
24
|
+
# TODO: Write code here!
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "./spec_helper"
|
2
|
+
|
3
|
+
describe "handler(event)" do
|
4
|
+
body = {"name" => "Ruby"}.to_json
|
5
|
+
event_hash = {
|
6
|
+
"body" => body,
|
7
|
+
"context" => nil,
|
8
|
+
"headers" => {"Content-Type" => "application/json"},
|
9
|
+
"query_params" => {} of String => String
|
10
|
+
}
|
11
|
+
event = Event.from_json(event_hash.to_json)
|
12
|
+
|
13
|
+
it "should return Hash, String or Array" do
|
14
|
+
body = handler(event).body
|
15
|
+
body.class.should eq(String)
|
16
|
+
end
|
17
|
+
it "should add the name to the response string" do
|
18
|
+
body = handler(event).body
|
19
|
+
body.should eq("Hello, Ruby!\n")
|
20
|
+
end
|
21
|
+
it "should say Hello, World! when name is not present" do
|
22
|
+
event_hash["body"] = nil
|
23
|
+
event = Event.from_json(event_hash.to_json)
|
24
|
+
body = handler(event).body
|
25
|
+
body.should eq("Hello, World!\n")
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "json"
|
3
|
+
require "base64"
|
4
|
+
|
5
|
+
module FaaStRuby
|
6
|
+
class Event
|
7
|
+
JSON.mapping(
|
8
|
+
body: String?,
|
9
|
+
headers: Hash(String, String),
|
10
|
+
context: String?,
|
11
|
+
query_params: Hash(String, String)
|
12
|
+
)
|
13
|
+
end
|
14
|
+
class Response
|
15
|
+
@@rendered = false
|
16
|
+
property "body"
|
17
|
+
property "status"
|
18
|
+
property "headers"
|
19
|
+
property "io"
|
20
|
+
def initialize(@body : String?, @status : Int32, @headers : Hash(String, String))
|
21
|
+
@io = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(@io : Bytes, @status : Int32, @headers : Hash(String, String))
|
25
|
+
end
|
26
|
+
|
27
|
+
def payload
|
28
|
+
if io
|
29
|
+
hash = {
|
30
|
+
"response" => Base64.encode(io.not_nil!),
|
31
|
+
"status" => status,
|
32
|
+
"headers" => headers
|
33
|
+
}
|
34
|
+
else
|
35
|
+
hash = {
|
36
|
+
"response" => body,
|
37
|
+
"status" => status,
|
38
|
+
"headers" => headers
|
39
|
+
}
|
40
|
+
end
|
41
|
+
hash
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def respond_with(body : String? = nil, status : Int32 = 200, headers : Hash(String, String) = {} of String => String)
|
46
|
+
Response.new(body: body, status: status, headers: headers)
|
47
|
+
end
|
48
|
+
|
49
|
+
def respond_with(io : Bytes, status : Int32 = 200, headers : Hash(String, String) = {} of String => String)
|
50
|
+
Response.new(io: io, status: status, headers: headers)
|
51
|
+
end
|
52
|
+
|
53
|
+
def render(io : Bytes? = nil, js : String? = nil, inline : String? = nil, html : String? = nil, json : String? = nil, yaml : String? = nil, text : String? = nil, status : Int32 = 200, headers : Hash(String, String) = {} of String => String, content_type : String? = nil)
|
54
|
+
headers["Content-Type"] = content_type if content_type
|
55
|
+
case
|
56
|
+
when json
|
57
|
+
headers["Content-Type"] ||= "application/json"
|
58
|
+
resp_body = json
|
59
|
+
when html, inline
|
60
|
+
headers["Content-Type"] ||= "text/html"
|
61
|
+
resp_body = html
|
62
|
+
when text
|
63
|
+
headers["Content-Type"] ||= "text/plain"
|
64
|
+
resp_body = text
|
65
|
+
when yaml
|
66
|
+
headers["Content-Type"] ||= "text/yaml"
|
67
|
+
resp_body = yaml
|
68
|
+
when js
|
69
|
+
headers["Content-Type"] ||= "text/javascript"
|
70
|
+
resp_body = js
|
71
|
+
when io
|
72
|
+
headers["Content-Type"] ||= "application/octet-stream"
|
73
|
+
return respond_with(io: io, status: status, headers: headers)
|
74
|
+
end
|
75
|
+
respond_with(body: resp_body, status: status, headers: headers)
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# require "cool-shard"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
# To deploy this function, cd into its folder and run:
|
5
|
+
# faastruby deploy-to WORKSPACE_NAME
|
6
|
+
def handler(event : FaaStRuby::Event) : FaaStRuby::Response
|
7
|
+
# event.body : String | Nil
|
8
|
+
# event.headers : Hash(String, String)
|
9
|
+
# event.context : String | Nil
|
10
|
+
# query_params : Hash(String, String)
|
11
|
+
data = JSON.parse(event.body.not_nil!) rescue {} of String => String
|
12
|
+
# FUNCTION RESPONSE
|
13
|
+
#
|
14
|
+
# You can render text, json, yaml, html or js. Example:
|
15
|
+
# render html: "<p>Hello World!</p>"
|
16
|
+
# render yaml: {"hello" => "world!"}
|
17
|
+
#
|
18
|
+
# Status:
|
19
|
+
# The default status is 200. You can set a custom status like this:
|
20
|
+
# render json: {"error" => "Could not perform the action"}, status: 422
|
21
|
+
#
|
22
|
+
# Headers:
|
23
|
+
# The 'Content-Type' header is automatically set when you use 'render'.
|
24
|
+
# You can set custom headers using a Hash(String, String). Example:
|
25
|
+
# render text: "It Works!", headers: {"TransactionId" => 23928}
|
26
|
+
render text: "Hello, #{data["name"]? || "World"}!\n"
|
27
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -1,9 +1,7 @@
|
|
1
1
|
# require 'cool-gem'
|
2
2
|
require 'json'
|
3
3
|
|
4
|
-
#
|
5
|
-
# faastruby create-workspace WORKSPACE_NAME
|
6
|
-
# 2) To deploy this function, cd into its folder and run:
|
4
|
+
# To deploy this function, cd into its folder and run:
|
7
5
|
# faastruby deploy-to WORKSPACE_NAME
|
8
6
|
def handler event
|
9
7
|
data = event.body ? JSON.parse(event.body) : {}
|
File without changes
|
File without changes
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: faastruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paulo Arruda
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-12-
|
11
|
+
date: 2018-12-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rest-client
|
@@ -226,16 +226,6 @@ files:
|
|
226
226
|
- Rakefile
|
227
227
|
- bin/console
|
228
228
|
- bin/setup
|
229
|
-
- example-blank/Gemfile
|
230
|
-
- example-blank/handler.rb
|
231
|
-
- example-blank/spec/handler_spec.rb
|
232
|
-
- example-blank/spec/helpers/faastruby.rb
|
233
|
-
- example-blank/spec/spec_helper.rb
|
234
|
-
- example/Gemfile
|
235
|
-
- example/handler.rb
|
236
|
-
- example/spec/handler_spec.rb
|
237
|
-
- example/spec/helpers/faastruby.rb
|
238
|
-
- example/spec/spec_helper.rb
|
239
229
|
- exe/faastruby
|
240
230
|
- exe/faastruby-server
|
241
231
|
- faastruby.gemspec
|
@@ -268,6 +258,24 @@ files:
|
|
268
258
|
- lib/faastruby/function.rb
|
269
259
|
- lib/faastruby/version.rb
|
270
260
|
- lib/faastruby/workspace.rb
|
261
|
+
- templates/crystal/example-blank/spec/handler_spec.cr
|
262
|
+
- templates/crystal/example-blank/spec/helpers/faastruby.cr
|
263
|
+
- templates/crystal/example-blank/spec/spec_helper.cr
|
264
|
+
- templates/crystal/example-blank/src/handler.cr
|
265
|
+
- templates/crystal/example/spec/handler_spec.cr
|
266
|
+
- templates/crystal/example/spec/helpers/faastruby.cr
|
267
|
+
- templates/crystal/example/spec/spec_helper.cr
|
268
|
+
- templates/crystal/example/src/handler.cr
|
269
|
+
- templates/ruby/example-blank/Gemfile
|
270
|
+
- templates/ruby/example-blank/handler.rb
|
271
|
+
- templates/ruby/example-blank/spec/handler_spec.rb
|
272
|
+
- templates/ruby/example-blank/spec/helpers/faastruby.rb
|
273
|
+
- templates/ruby/example-blank/spec/spec_helper.rb
|
274
|
+
- templates/ruby/example/Gemfile
|
275
|
+
- templates/ruby/example/handler.rb
|
276
|
+
- templates/ruby/example/spec/handler_spec.rb
|
277
|
+
- templates/ruby/example/spec/helpers/faastruby.rb
|
278
|
+
- templates/ruby/example/spec/spec_helper.rb
|
271
279
|
homepage: https://faastruby.io
|
272
280
|
licenses:
|
273
281
|
- MIT
|