toycol 0.3.0 → 0.3.1

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: a9cd0de8dd5ec5a48e22a49c8198492a96162c9ff922a07c897e3af7cdfe779a
4
- data.tar.gz: 938b7f68fef82731d61c1937c2ed53753c1bbb301c49434cc6d538edb6f862f9
3
+ metadata.gz: 94c69c1ed2726e69431304ff3c4fb727581b7893160d5dbe085731f4d85dfce2
4
+ data.tar.gz: 94fc2c13fbd5149a47669a521dfa40377e1edf890a92b33468bf534c6d34ed45
5
5
  SHA512:
6
- metadata.gz: 53e62c64e3f418da9e0f7068482531bab41466e6d73d560e8bf74ae900cf2587a61d810365eea15f01f194592c70b563320ffe93f170810e4fa6f443f3a73837
7
- data.tar.gz: 112910c06cdb28e5f8690fd18373a2d418a466b075cc0ff793816aed2475acb1ccf5657ae8cfb7b3c321cefc1bde44e5a34fd9b9024f3c20d14047938adf6121
6
+ metadata.gz: c5c3d01ea2c3b41491e3d365b781d92677ef027ce9fefa1d45b3ab360e947416035c39d5c9d32dd29ae01c309dfa27338097e5a63dfcd248aa9647b45784bb28
7
+ data.tar.gz: d09afa0497d2b40fcc0b404a19ceee343b9f132a434cbf80feef1cac3bbef060397bccc15503beb507be6e0b03d33b6d5f5cdbe96c5499a093b3a8a27dbd8f24
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rack"
4
3
  require "rack/handler"
5
4
 
6
5
  module Rack
7
6
  module Handler
8
7
  class Toycol
8
+ extend ::Toycol::Helper
9
+
9
10
  class << self
10
11
  attr_writer :preferred_background_server, :host, :port
11
12
 
@@ -27,20 +28,20 @@ module Rack
27
28
  def select_background_server
28
29
  case @preferred_background_server
29
30
  when "puma"
30
- return "puma" if puma_requireable?
31
+ return "puma" if try_require_puma_handler
31
32
 
32
33
  raise LoadError, "Puma is not installed in your environment."
33
34
  when nil
34
- puma_requireable? ? "puma" : "build_in"
35
+ try_require_puma_handler ? "puma" : "builtin"
35
36
  else
36
- "build_in"
37
+ "builtin"
37
38
  end
38
39
  rescue LoadError
39
40
  Process.kill(:INT, Process.ppid)
40
41
  abort
41
42
  end
42
43
 
43
- def puma_requireable?
44
+ def try_require_puma_handler
44
45
  require "rack/handler/puma"
45
46
  true
46
47
  rescue LoadError
@@ -50,10 +51,10 @@ module Rack
50
51
  def run_background_server
51
52
  case select_background_server
52
53
  when "puma"
53
- puts "Toycol starts Puma in single mode, listening on unix://#{::Toycol::UNIX_SOCKET_PATH}"
54
+ logger "Start Puma in single mode, listening on unix://#{::Toycol::UNIX_SOCKET_PATH}"
54
55
  Rack::Handler::Puma.run(@app, **{ Host: ::Toycol::UNIX_SOCKET_PATH, Silent: true })
55
56
  else
56
- puts "Toycol starts build-in server, listening on unix://#{::Toycol::UNIX_SOCKET_PATH}"
57
+ logger "Start built-in server, listening on unix://#{::Toycol::UNIX_SOCKET_PATH}"
57
58
  ::Toycol::Server.run(@app, **{ Path: ::Toycol::UNIX_SOCKET_PATH, Port: @port })
58
59
  end
59
60
  end
data/lib/toycol.rb CHANGED
@@ -1,27 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
+ require "optparse"
5
+ require "rack"
6
+ require "socket"
7
+ require "stringio"
4
8
 
5
9
  require_relative "toycol/const"
6
10
  require_relative "toycol/helper"
7
11
  require_relative "toycol/protocol"
8
12
  require_relative "toycol/proxy"
9
13
  require_relative "toycol/server"
10
- require_relative "rack/handler/toycol"
11
-
12
- Dir["#{FileUtils.pwd}/Protocolfile*"].sort.each { |f| load f }
13
-
14
+ require_relative "toycol/client"
15
+ require_relative "toycol/template_generator"
14
16
  require_relative "toycol/command"
15
17
  require_relative "toycol/version"
16
18
 
19
+ require_relative "rack/handler/toycol"
20
+
17
21
  module Toycol
18
22
  class Error < StandardError; end
19
23
 
20
- class UnauthorizedMethodError < Error; end
24
+ class UnauthorizeError < Error; end
21
25
 
22
- class UnauthorizedRequestError < Error; end
26
+ class UndefinementError < Error; end
23
27
 
24
- class UndefinedRequestMethodError < Error; end
28
+ class DuplicateProtocolError < Error; end
25
29
 
26
- class UnknownStatusCodeError < Error; end
30
+ class HTTPError < Error; end
27
31
  end
32
+
33
+ Dir["#{FileUtils.pwd}/Protocolfile*"].sort.each { |f| load f }
data/lib/toycol/client.rb CHANGED
@@ -1,19 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "socket"
4
-
5
3
  module Toycol
6
4
  class Client
5
+ extend Helper
6
+
7
7
  @port = 9292
8
+ @host = "localhost"
8
9
  CHUNK_SIZE = 1024 * 16
9
10
 
10
11
  class << self
11
- attr_writer :port
12
+ attr_writer :port, :host
12
13
 
13
14
  def execute!(request_message, &block)
14
- socket = TCPSocket.new("localhost", @port)
15
+ socket = TCPSocket.new(@host, @port)
15
16
  socket.write(request_message)
16
- puts "[Toycol] Sent request message: #{request_message}\n---"
17
+ logger "Sent request message: #{request_message}\n---"
17
18
 
18
19
  response_message = []
19
20
  response_message << socket.readpartial(CHUNK_SIZE) until socket.eof?
@@ -29,7 +30,7 @@ module Toycol
29
30
 
30
31
  def default_proc
31
32
  proc do |message|
32
- puts "[Toycol] Received response message:\n\n"
33
+ logger "Received response message:\n\n"
33
34
  puts message
34
35
  end
35
36
  end
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "optparse"
4
- require_relative "./client"
5
- require_relative "./template_generator"
6
-
7
3
  module Toycol
8
4
  class Command
9
5
  class Options
@@ -81,8 +77,13 @@ module Toycol
81
77
 
82
78
  def client_option_parser
83
79
  OptionParser.new do |opt|
84
- opt.on("-p PORT_NUMBER", "--port PORT_NUMBER", "listen on PORT (default: 9292)") do |port|
85
- ::Toycol::Client.port = port
80
+ opt.banner = "Usage: #{opt.program_name} client [-h|--help] REQUEST_MESSAGE [arg...]"
81
+ opt.on("-o HOST", "--host HOST", "connect to HOST (default: localhost)") do |host|
82
+ Client.host = host
83
+ end
84
+
85
+ opt.on("-p PORT_NUMBER", "--port PORT_NUMBER", "connect to PORT (default: 9292)") do |port|
86
+ Client.port = port
86
87
  end
87
88
 
88
89
  opt.on_head("-h", "--help", "Show this message") { help_command(opt) }
@@ -91,6 +92,7 @@ module Toycol
91
92
 
92
93
  def server_option_parser
93
94
  OptionParser.new do |opt|
95
+ opt.banner = "Usage: #{opt.program_name} server [-h|--help] APPLICATION_PATH [arg...]"
94
96
  opt.on("-o HOST", "--host HOST", "bind to HOST (default: localhost)") do |host|
95
97
  ::Rack::Handler::Toycol.host = host
96
98
  end
@@ -99,7 +101,7 @@ module Toycol
99
101
  ::Rack::Handler::Toycol.port = port
100
102
  end
101
103
 
102
- opt.on("-u SERVER_NAME", "--use SERVER_NAME", "switch using SERVER(puma/build_in)") do |server_name|
104
+ opt.on("-u SERVER_NAME", "--use SERVER_NAME", "switch using SERVER(puma/builtin)") do |server_name|
103
105
  ::Rack::Handler::Toycol.preferred_background_server = server_name
104
106
  end
105
107
 
@@ -138,13 +140,13 @@ module Toycol
138
140
 
139
141
  case command
140
142
  when "client", "c"
141
- ::Toycol::Client.execute!(options[:request_message])
143
+ Client.execute!(options[:request_message])
142
144
  when "server", "s"
143
145
  ARGV.push("-q", "-s", "toycol")
144
146
  Rack::Server.start
145
147
  when "generate", "g"
146
148
  type = options[:template_type] || "all"
147
- ::Toycol::TemplateGenerator.generate!(type: type, name: options[:protocol_name])
149
+ TemplateGenerator.generate!(type: type, name: options[:protocol_name])
148
150
  end
149
151
  end
150
152
  end
data/lib/toycol/helper.rb CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  module Toycol
4
4
  module Helper
5
+ def logger(message)
6
+ puts "[Toycol] #{message}"
7
+ end
8
+
5
9
  private
6
10
 
7
11
  def safe_execution!(&block)
@@ -10,8 +14,8 @@ module Toycol
10
14
 
11
15
  def safe_executionable_tp
12
16
  @safe_executionable_tp ||= TracePoint.new(:script_compiled) do |tp|
13
- if tp.binding.receiver == Toycol::Protocol && tp.method_id.to_s.match?(unauthorized_methods_regex)
14
- raise Toycol::UnauthorizedMethodError, <<~ERROR
17
+ if tp.binding.receiver == Protocol && tp.method_id.to_s.match?(unauthorized_methods_regex)
18
+ raise UnauthorizeError, <<~ERROR
15
19
  - Unauthorized method was called!
16
20
  You can't use methods that may cause injections in your protocol.
17
21
  Ex. Kernel.#eval, Kernel.#exec, Kernel.#require and so on.
@@ -5,23 +5,30 @@ module Toycol
5
5
  class Protocol
6
6
  @definements = {}
7
7
  @protocol_name = nil
8
- @http_status_codes = Toycol::DEFAULT_HTTP_STATUS_CODES.dup
9
- @http_request_methods = Toycol::DEFAULT_HTTP_REQUEST_METHODS.dup
8
+ @http_status_codes = DEFAULT_HTTP_STATUS_CODES.dup
9
+ @http_request_methods = DEFAULT_HTTP_REQUEST_METHODS.dup
10
10
  @custom_status_codes = nil
11
11
  @additional_request_methods = nil
12
12
 
13
13
  class << self
14
- # For protocol definition
15
- def define(protocol_name = nil, &block)
14
+ attr_reader :protocol_name
15
+
16
+ # For Protocolfile to define new protocol
17
+ def define(protocol_name = :default, &block)
18
+ if @definements[protocol_name]
19
+ raise DuplicateProtocolError,
20
+ "#{protocol_name || "Anonymous"} protocol has already been defined"
21
+ end
22
+
16
23
  @definements[protocol_name] = block
17
24
  end
18
25
 
19
- # For application which use the protocol
20
- def use(protocol_name = nil)
26
+ # For application to select which protocol to use
27
+ def use(protocol_name = :default)
21
28
  @protocol_name = protocol_name
22
29
  end
23
30
 
24
- # For server which use the protocol
31
+ # For proxy server to interpret protocol definitions and parse messages
25
32
  def run!(message)
26
33
  @request_message = message.chomp
27
34
 
@@ -67,52 +74,54 @@ module Toycol
67
74
  end
68
75
  end
69
76
 
70
- # For server: Get the request path
77
+ # For proxy server: Fetch the request path
71
78
  def request_path
72
79
  request_path = request.instance_variable_get("@path").call(request_message)
73
80
 
74
- raise UnauthorizedRequestError, "This request path is too long" if request_path.size >= 2048
75
-
76
- if request_path.scan(%r{[/\w\d\-_]}).size < request_path.size
77
- raise UnauthorizedRequestError,
81
+ if request_path.size >= 2048
82
+ raise UnauthorizeError,
83
+ "This request path is too long"
84
+ elsif request_path.scan(%r{[/\w\d\-_]}).size < request_path.size
85
+ raise UnauthorizeError,
78
86
  "This request path contains unauthorized character"
79
87
  end
80
88
 
81
89
  request_path
82
90
  end
83
91
 
84
- # For server: Get the request method
92
+ # For proxy server: Fetch the request method
85
93
  def request_method
86
94
  @http_request_methods.concat @additional_request_methods if @additional_request_methods
87
95
  request_method = request.instance_variable_get("@http_method").call(request_message)
88
96
 
89
97
  unless @http_request_methods.include? request_method
90
- raise UndefinedRequestMethodError, "This request method is undefined"
98
+ raise UndefinementError,
99
+ "This request method is undefined"
91
100
  end
92
101
 
93
102
  request_method
94
103
  end
95
104
 
96
- # For server: Get the query string
105
+ # For proxy server: Fetch the query string
97
106
  def query
98
107
  return unless (parse_query_block = request.instance_variable_get("@query"))
99
108
 
100
109
  parse_query_block.call(request_message)
101
110
  end
102
111
 
103
- # For server: Get the input body
112
+ # For proxy server: Fetch the input body
104
113
  def input
105
114
  return unless (parsed_input_block = request.instance_variable_get("@input"))
106
115
 
107
116
  parsed_input_block.call(request_message)
108
117
  end
109
118
 
110
- # For server: Get the message of status code
119
+ # For proxy server: fetch the message of status code
111
120
  def status_message(status)
112
121
  @http_status_codes.merge!(@custom_status_codes) if @custom_status_codes
113
122
 
114
123
  unless (message = @http_status_codes[status])
115
- raise UnknownStatusCodeError, "Application returns unknown status code"
124
+ raise HTTPError, "Application returns unknown status code"
116
125
  end
117
126
 
118
127
  message
data/lib/toycol/proxy.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "socket"
4
-
5
3
  module Toycol
6
4
  class Proxy
7
5
  include Helper
@@ -13,15 +11,15 @@ module Toycol
13
11
  @path = nil
14
12
  @query = nil
15
13
  @input = nil
16
- @protocol = ::Toycol::Protocol
14
+ @protocol = Protocol
17
15
  @proxy = TCPServer.new(@host, @port)
18
16
  end
19
17
 
20
18
  CHUNK_SIZE = 1024 * 16
21
19
 
22
20
  def start
23
- puts <<~MESSAGE
24
- Toycol is running on #{@host}:#{@port}
21
+ logger <<~MESSAGE
22
+ Start proxy server on #{@protocol.protocol_name} protocol, listening on #{@host}:#{@port}
25
23
  => Use Ctrl-C to stop
26
24
  MESSAGE
27
25
 
@@ -33,13 +31,13 @@ module Toycol
33
31
  while !@client.closed? && !@client.eof?
34
32
  begin
35
33
  request = @client.readpartial(CHUNK_SIZE)
36
- puts "[Toycol] Received message: #{request.inspect.chomp}"
34
+ logger "Received message: #{request.inspect.chomp}"
37
35
 
38
36
  safe_execution! { @protocol.run!(request) }
39
37
  assign_parsed_attributes!
40
38
 
41
39
  http_request_message = build_http_request_message
42
- puts "[Toycol] Message has been translated to HTTP request message: #{http_request_message.inspect}"
40
+ logger "Message has been translated to HTTP request message: #{http_request_message.inspect}"
43
41
  transfer_to_server(http_request_message)
44
42
  rescue StandardError => e
45
43
  puts "#{e.class} #{e.message} - closing socket."
@@ -82,15 +80,15 @@ module Toycol
82
80
  end
83
81
 
84
82
  def transfer_to_server(request_message)
85
- UNIXSocket.open(Toycol::UNIX_SOCKET_PATH) do |server|
83
+ UNIXSocket.open(UNIX_SOCKET_PATH) do |server|
86
84
  server.write request_message
87
85
  server.close_write
88
- puts "[Toycol] Successed to Send HTTP request message to server"
86
+ logger "Successed to Send HTTP request message to server"
89
87
 
90
88
  response_message = []
91
89
  response_message << server.readpartial(CHUNK_SIZE) until server.eof?
92
90
  response_message = response_message.join
93
- puts "[Toycol] Received response message from server: #{response_message.lines.first}"
91
+ logger "Received response message from server: #{response_message.lines.first}"
94
92
 
95
93
  response_line = response_message.lines.first
96
94
  status_number = response_line[9..11]
@@ -98,18 +96,18 @@ module Toycol
98
96
 
99
97
  if (custom_message = @protocol.status_message(status_number.to_i)) != status_message
100
98
  response_message = response_message.sub(status_message, custom_message)
101
- puts "[Toycol] Status message has been translated to custom status message: #{custom_message}"
99
+ logger "Status message has been translated to custom status message: #{custom_message}"
102
100
  end
103
101
 
104
102
  @client.write response_message
105
103
  @client.close_write
106
- puts "[Toycol] Finished to response to client"
104
+ logger "Finished to response to client"
107
105
  server.close
108
106
  end
109
107
  end
110
108
 
111
109
  def shutdown
112
- puts "[Toycol] Catched SIGINT -> Stop to server"
110
+ logger "Caught SIGINT -> Stop to server"
113
111
  exit
114
112
  end
115
113
  end
data/lib/toycol/server.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "stringio"
4
-
5
3
  module Toycol
6
4
  class Server
7
5
  BACKLOG = 1024
@@ -50,19 +48,19 @@ module Toycol
50
48
 
51
49
  def default_env
52
50
  {
53
- ::Toycol::PATH_INFO => "",
54
- ::Toycol::QUERY_STRING => "",
55
- ::Toycol::REQUEST_METHOD => "",
56
- ::Toycol::SERVER_NAME => "toycol_server",
57
- ::Toycol::SERVER_PORT => @port.to_s,
58
- ::Toycol::CONTENT_LENGTH => "0",
59
- ::Toycol::RACK_VERSION => Rack::VERSION,
60
- ::Toycol::RACK_INPUT => stringio(""),
61
- ::Toycol::RACK_ERRORS => $stderr,
62
- ::Toycol::RACK_MULTITHREAD => false,
63
- ::Toycol::RACK_MULTIPROCESS => false,
64
- ::Toycol::RACK_RUN_ONCE => false,
65
- ::Toycol::RACK_URL_SCHEME => "http"
51
+ PATH_INFO => "",
52
+ QUERY_STRING => "",
53
+ REQUEST_METHOD => "",
54
+ SERVER_NAME => "toycol_server",
55
+ SERVER_PORT => @port.to_s,
56
+ CONTENT_LENGTH => "0",
57
+ RACK_VERSION => Rack::VERSION,
58
+ RACK_INPUT => stringio(""),
59
+ RACK_ERRORS => $stderr,
60
+ RACK_MULTITHREAD => false,
61
+ RACK_MULTIPROCESS => false,
62
+ RACK_RUN_ONCE => false,
63
+ RACK_URL_SCHEME => "http"
66
64
  }
67
65
  end
68
66
 
@@ -71,7 +69,7 @@ module Toycol
71
69
  end
72
70
 
73
71
  def response_status_code
74
- "HTTP/1.1 #{@returned_status} #{::Toycol::DEFAULT_HTTP_STATUS_CODES[@returned_status.to_i] || "CUSTOM"}\r\n"
72
+ "HTTP/1.1 #{@returned_status} #{DEFAULT_HTTP_STATUS_CODES[@returned_status.to_i] || "CUSTOM"}\r\n"
75
73
  end
76
74
 
77
75
  def response_headers
@@ -108,17 +106,17 @@ module Toycol
108
106
  request_method, request_path, = request_line.split
109
107
  request_path, query_string = request_path.split("?")
110
108
 
111
- @env[::Toycol::REQUEST_METHOD] = request_method
112
- @env[::Toycol::PATH_INFO] = request_path
113
- @env[::Toycol::QUERY_STRING] = query_string || ""
114
- @env[::Toycol::CONTENT_LENGTH]
109
+ @env[REQUEST_METHOD] = request_method
110
+ @env[PATH_INFO] = request_path
111
+ @env[QUERY_STRING] = query_string || ""
112
+ @env[CONTENT_LENGTH]
115
113
 
116
114
  request_headers.each do |request_header|
117
115
  k, v = request_header.split(":").map(&:strip)
118
- @env["::Toycol::#{k.tr("-", "_").upcase}"] = v
116
+ @env[k.tr("-", "_").upcase.to_s] = v
119
117
  end
120
118
 
121
- @env[::Toycol::RACK_INPUT] = stringio(request_body)
119
+ @env[RACK_INPUT] = stringio(request_body)
122
120
  end
123
121
  end
124
122
  end
@@ -2,9 +2,11 @@
2
2
 
3
3
  module Toycol
4
4
  class TemplateGenerator
5
+ include Helper
6
+
5
7
  class << self
6
8
  def generate!(type:, name:)
7
- raise StandardError, "Unknown Type: This type of template can't be generated" unless valid? type
9
+ raise Error, "Unknown Type: This type of template can't be generated" unless valid? type
8
10
 
9
11
  if type == "all"
10
12
  new(name, "protocol").generate!
@@ -27,10 +29,10 @@ module Toycol
27
29
  end
28
30
 
29
31
  def generate!
30
- raise StandardError, "#{filename} already exists" unless Dir.glob(filename).empty?
32
+ raise Error, "#{filename} already exists" unless Dir.glob(filename).empty?
31
33
 
32
34
  File.open(filename, "w") { |f| f.print template_text_for_new }
33
- puts "Generate #{filename} in #{FileUtils.pwd}"
35
+ logger "Generate #{filename} in #{FileUtils.pwd}"
34
36
  end
35
37
 
36
38
  private
@@ -52,8 +54,8 @@ module Toycol
52
54
 
53
55
  def template_text
54
56
  case @type
55
- when "protocol" then File.open("lib/toycol/templates/protocol.txt", "r", &:read)
56
- when "app" then File.open("lib/toycol/templates/application.txt", "r", &:read)
57
+ when "protocol" then File.open("#{__dir__}/templates/protocol.txt", "r", &:read)
58
+ when "app" then File.open("#{__dir__}/templates/application.txt", "r", &:read)
57
59
  end
58
60
  end
59
61
  end
@@ -1,19 +1,24 @@
1
1
  require "rack"
2
2
  require "toycol"
3
3
 
4
- # Specify which protocol to use
5
4
  Toycol::Protocol.use(:PROTOCOL_NAME)
6
5
 
7
6
  class App
8
7
  def call(env)
9
8
  # Define your app on request method, request path, request query etc
10
9
  # For example:
11
- # case env['REQUEST_METHOD']
12
- # when 'GET'
10
+ # case env["REQUEST_METHOD"]
11
+ # when "GET"
13
12
  # [
14
13
  # 200,
15
- # { 'Content-Type' => 'text/html' },
16
- # ["Hello, This app is running by :#{name} protocol."]
14
+ # { "Content-Type" => "text/html" },
15
+ # ["Hello, This app is running by new protocol."]
16
+ # ]
17
+ # when "OTHER"
18
+ # [
19
+ # 600,
20
+ # { "Content-Type" => "text/html" },
21
+ # ["This is response message for additional request method"]
17
22
  # ]
18
23
  # end
19
24
  end
@@ -8,11 +8,11 @@ Toycol::Protocol.define(:PROTOCOL_NAME) do
8
8
 
9
9
  # [OPTIONAL] You can define your additional request methods:
10
10
  # For example:
11
- # additional_request_methods OTHER
11
+ # additional_request_methods "OTHER"
12
12
 
13
13
  # [OPTIONAL] You can define your own response status code:
14
14
  # For example:
15
- # define_status_codes(
15
+ # custom_status_codes(
16
16
  # 600 => "I'm afraid you are not a duck..."
17
17
  # )
18
18
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Toycol
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.1"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: toycol
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Misaki Shioi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-25 00:00:00.000000000 Z
11
+ date: 2021-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack