pakyow-core 0.11.3 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +0 -0
- data/LICENSE +4 -0
- data/{pakyow-core/README.md → README.md} +3 -4
- data/commands/pakyow +13 -0
- data/lib/pakyow/app/connection/behavior/session.rb +33 -0
- data/lib/pakyow/app/connection/behavior/values.rb +42 -0
- data/lib/pakyow/app/connection/behavior/verifier.rb +34 -0
- data/lib/pakyow/app/connection/session/abstract.rb +24 -0
- data/lib/pakyow/app/connection/session/cookie.rb +56 -0
- data/lib/pakyow/app/connection.rb +57 -0
- data/lib/pakyow/app.rb +219 -0
- data/lib/pakyow/behavior/aspects.rb +45 -0
- data/lib/pakyow/behavior/config.rb +72 -0
- data/lib/pakyow/behavior/endpoints.rb +41 -0
- data/lib/pakyow/behavior/frameworks.rb +45 -0
- data/lib/pakyow/behavior/helpers.rb +107 -0
- data/lib/pakyow/behavior/initializers.rb +19 -0
- data/lib/pakyow/behavior/isolating.rb +69 -0
- data/lib/pakyow/behavior/operations.rb +46 -0
- data/lib/pakyow/behavior/pipeline.rb +58 -0
- data/lib/pakyow/behavior/plugins.rb +148 -0
- data/lib/pakyow/behavior/rescuing.rb +51 -0
- data/lib/pakyow/behavior/restarting.rb +68 -0
- data/lib/pakyow/behavior/sessions.rb +34 -0
- data/lib/pakyow/behavior/verification.rb +47 -0
- data/lib/pakyow/cli.rb +199 -0
- data/lib/pakyow/connection/multipart_input.rb +24 -0
- data/lib/pakyow/connection/multipart_parser.rb +112 -0
- data/lib/pakyow/connection/params.rb +29 -0
- data/lib/pakyow/connection/query_parser.rb +174 -0
- data/lib/pakyow/connection/statuses.rb +119 -0
- data/lib/pakyow/connection.rb +568 -0
- data/lib/pakyow/endpoints.rb +94 -0
- data/lib/pakyow/environment/actions/dispatch.rb +37 -0
- data/lib/pakyow/environment/actions/input_parser.rb +21 -0
- data/lib/pakyow/environment/actions/logger.rb +33 -0
- data/lib/pakyow/environment/actions/normalizer.rb +73 -0
- data/lib/pakyow/environment/behavior/config.rb +166 -0
- data/lib/pakyow/environment/behavior/initializers.rb +21 -0
- data/lib/pakyow/environment/behavior/input_parsing.rb +54 -0
- data/lib/pakyow/environment/behavior/plugins.rb +23 -0
- data/lib/pakyow/environment/behavior/restarting.rb +54 -0
- data/lib/pakyow/environment/behavior/running.rb +129 -0
- data/lib/pakyow/environment/behavior/silencing.rb +23 -0
- data/lib/pakyow/environment/behavior/timezone.rb +21 -0
- data/lib/pakyow/environment/behavior/watching.rb +75 -0
- data/lib/pakyow/environment.rb +381 -0
- data/lib/pakyow/error.rb +273 -0
- data/lib/pakyow/errors.rb +97 -0
- data/lib/pakyow/framework.rb +39 -0
- data/lib/pakyow/generator.rb +138 -0
- data/lib/pakyow/generators/project/default/%dot%env.erb +1 -0
- data/lib/pakyow/generators/project/default/%dot%gitignore +11 -0
- data/lib/pakyow/generators/project/default/%dot%ruby-version.erb +1 -0
- data/lib/pakyow/generators/project/default/Gemfile.erb +28 -0
- data/lib/pakyow/generators/project/default/README.md +14 -0
- data/lib/pakyow/generators/project/default/config/application.rb.erb +17 -0
- data/lib/pakyow/generators/project/default/config/environment.rb +11 -0
- data/lib/pakyow/generators/project/default/config/initializers/application/keep +0 -0
- data/lib/pakyow/generators/project/default/config/initializers/environment/keep +0 -0
- data/lib/pakyow/generators/project/default/database/keep +0 -0
- data/lib/pakyow/generators/project/default/frontend/assets/images/keep +0 -0
- data/lib/pakyow/generators/project/default/frontend/assets/packs/keep +0 -0
- data/lib/pakyow/generators/project/default/frontend/assets/styles/keep +0 -0
- data/lib/pakyow/generators/project/default/frontend/includes/keep +0 -0
- data/lib/pakyow/generators/project/default/frontend/layouts/default.html.erb +19 -0
- data/lib/pakyow/generators/project/default/frontend/pages/keep +0 -0
- data/lib/pakyow/generators/project/default/public/favicon.ico +0 -0
- data/lib/pakyow/generators/project/default/public/robots.txt +1 -0
- data/lib/pakyow/generators/project.rb +29 -0
- data/lib/pakyow/helpers/app.rb +28 -0
- data/lib/pakyow/helpers/connection.rb +45 -0
- data/lib/pakyow/info.rb +26 -0
- data/lib/pakyow/integrations/bootsnap.rb +18 -0
- data/lib/pakyow/integrations/bundler/require.rb +7 -0
- data/lib/pakyow/integrations/bundler/setup.rb +7 -0
- data/lib/pakyow/integrations/dotenv.rb +9 -0
- data/lib/pakyow/loader.rb +31 -0
- data/lib/pakyow/logger/colorizer.rb +38 -0
- data/lib/pakyow/logger/destination.rb +17 -0
- data/lib/pakyow/logger/formatter.rb +22 -0
- data/lib/pakyow/logger/formatters/human.rb +101 -0
- data/lib/pakyow/logger/formatters/json.rb +99 -0
- data/lib/pakyow/logger/formatters/logfmt.rb +50 -0
- data/lib/pakyow/logger/multiplexed.rb +19 -0
- data/lib/pakyow/logger/thread_local.rb +29 -0
- data/lib/pakyow/logger/timekeeper.rb +64 -0
- data/lib/pakyow/logger.rb +125 -0
- data/lib/pakyow/operation.rb +71 -0
- data/lib/pakyow/plugin/helper_caller.rb +20 -0
- data/lib/pakyow/plugin/lookup.rb +33 -0
- data/lib/pakyow/plugin/state.rb +68 -0
- data/lib/pakyow/plugin.rb +367 -0
- data/lib/pakyow/process_manager.rb +53 -0
- data/lib/pakyow/processes/proxy.rb +67 -0
- data/lib/pakyow/processes/server.rb +32 -0
- data/lib/pakyow/rack/compatibility.rb +68 -0
- data/lib/pakyow/task.rb +315 -0
- data/lib/pakyow/tasks/boot.rake +21 -0
- data/lib/pakyow/tasks/create.rake +32 -0
- data/lib/pakyow/tasks/help.rake +17 -0
- data/lib/pakyow/tasks/info/endpoints.rake +72 -0
- data/lib/pakyow/tasks/info.rake +31 -0
- data/lib/pakyow/tasks/irb.rake +11 -0
- data/lib/pakyow/tasks/prelaunch.rake +37 -0
- data/lib/pakyow/types.rb +32 -0
- data/lib/pakyow/validations/acceptance.rb +37 -0
- data/lib/pakyow/validations/email.rb +28 -0
- data/lib/pakyow/validations/inline.rb +32 -0
- data/lib/pakyow/validations/length.rb +44 -0
- data/lib/pakyow/validations/presence.rb +41 -0
- data/lib/pakyow/validations.rb +8 -0
- data/lib/pakyow/validator.rb +81 -0
- data/lib/pakyow/verifier.rb +177 -0
- data/lib/pakyow.rb +5 -0
- metadata +267 -65
- data/pakyow-core/CHANGELOG.md +0 -128
- data/pakyow-core/LICENSE +0 -20
- data/pakyow-core/lib/pakyow/core/app.rb +0 -82
- data/pakyow-core/lib/pakyow/core/app_context.rb +0 -10
- data/pakyow-core/lib/pakyow/core/base.rb +0 -61
- data/pakyow-core/lib/pakyow/core/call_context.rb +0 -171
- data/pakyow-core/lib/pakyow/core/config/app.rb +0 -49
- data/pakyow-core/lib/pakyow/core/config/cookies.rb +0 -4
- data/pakyow-core/lib/pakyow/core/config/logger.rb +0 -34
- data/pakyow-core/lib/pakyow/core/config/reloader.rb +0 -10
- data/pakyow-core/lib/pakyow/core/config/server.rb +0 -10
- data/pakyow-core/lib/pakyow/core/config/session.rb +0 -41
- data/pakyow-core/lib/pakyow/core/config.rb +0 -95
- data/pakyow-core/lib/pakyow/core/errors.rb +0 -16
- data/pakyow-core/lib/pakyow/core/helpers/configuring.rb +0 -142
- data/pakyow-core/lib/pakyow/core/helpers/hooks.rb +0 -106
- data/pakyow-core/lib/pakyow/core/helpers/running.rb +0 -124
- data/pakyow-core/lib/pakyow/core/helpers.rb +0 -61
- data/pakyow-core/lib/pakyow/core/loader.rb +0 -32
- data/pakyow-core/lib/pakyow/core/middleware/logger.rb +0 -146
- data/pakyow-core/lib/pakyow/core/middleware/override.rb +0 -3
- data/pakyow-core/lib/pakyow/core/middleware/reloader.rb +0 -23
- data/pakyow-core/lib/pakyow/core/middleware/req_path_normalizer.rb +0 -49
- data/pakyow-core/lib/pakyow/core/middleware/session.rb +0 -5
- data/pakyow-core/lib/pakyow/core/middleware/static.rb +0 -76
- data/pakyow-core/lib/pakyow/core/multilog.rb +0 -19
- data/pakyow-core/lib/pakyow/core/request.rb +0 -119
- data/pakyow-core/lib/pakyow/core/response.rb +0 -135
- data/pakyow-core/lib/pakyow/core/route_eval.rb +0 -254
- data/pakyow-core/lib/pakyow/core/route_expansion_eval.rb +0 -115
- data/pakyow-core/lib/pakyow/core/route_lookup.rb +0 -41
- data/pakyow-core/lib/pakyow/core/route_merger.rb +0 -76
- data/pakyow-core/lib/pakyow/core/route_module.rb +0 -21
- data/pakyow-core/lib/pakyow/core/route_set.rb +0 -74
- data/pakyow-core/lib/pakyow/core/route_template_defaults.rb +0 -43
- data/pakyow-core/lib/pakyow/core/route_template_eval.rb +0 -72
- data/pakyow-core/lib/pakyow/core/router.rb +0 -181
- data/pakyow-core/lib/pakyow/core.rb +0 -8
- data/pakyow-core/lib/pakyow-core.rb +0 -1
data/lib/pakyow/cli.rb
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "optparse"
|
5
|
+
|
6
|
+
require "pakyow/support/cli/style"
|
7
|
+
|
8
|
+
require "pakyow/error"
|
9
|
+
require "pakyow/environment"
|
10
|
+
|
11
|
+
module Pakyow
|
12
|
+
# The Pakyow command line interface.
|
13
|
+
#
|
14
|
+
# @api private
|
15
|
+
class CLI
|
16
|
+
class InvalidInput < Error; end
|
17
|
+
|
18
|
+
GLOBAL_OPTIONS = {
|
19
|
+
app: {
|
20
|
+
description: "The app to run the command on",
|
21
|
+
global: true
|
22
|
+
},
|
23
|
+
env: {
|
24
|
+
description: "What environment to use",
|
25
|
+
global: true
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
def initialize(argv = ARGV.dup)
|
30
|
+
@argv = argv
|
31
|
+
@options = {}
|
32
|
+
@task = nil
|
33
|
+
@command = nil
|
34
|
+
|
35
|
+
parse_global_options
|
36
|
+
|
37
|
+
if project_context?
|
38
|
+
setup_environment
|
39
|
+
end
|
40
|
+
|
41
|
+
load_tasks
|
42
|
+
|
43
|
+
if @command
|
44
|
+
find_task_for_command
|
45
|
+
set_app_for_command
|
46
|
+
call_task
|
47
|
+
else
|
48
|
+
puts_help
|
49
|
+
end
|
50
|
+
rescue StandardError => error
|
51
|
+
if $stdout.isatty
|
52
|
+
puts_error(error)
|
53
|
+
|
54
|
+
if @task
|
55
|
+
puts @task.help(describe: false)
|
56
|
+
else
|
57
|
+
puts_help(banner: false)
|
58
|
+
end
|
59
|
+
|
60
|
+
::Process.exit(0)
|
61
|
+
else
|
62
|
+
raise error
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def tasks
|
69
|
+
Pakyow.tasks.select { |task|
|
70
|
+
(task.global? && !project_context?) || (!task.global? && project_context?)
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def project_context?
|
75
|
+
File.exist?(Pakyow.config.environment_path + ".rb")
|
76
|
+
end
|
77
|
+
|
78
|
+
def parse_global_options
|
79
|
+
parse_with_unknown_args do
|
80
|
+
OptionParser.new do |opts|
|
81
|
+
opts.on("-eENV", "--env=ENV") do |e|
|
82
|
+
@options[:env] = e
|
83
|
+
end
|
84
|
+
|
85
|
+
opts.on("-aAPP", "--app=APP") do |a|
|
86
|
+
@options[:app] = a
|
87
|
+
end
|
88
|
+
|
89
|
+
opts.on("-h", "--help") do
|
90
|
+
@options[:help] = true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
@options[:env] ||= ENV["APP_ENV"] || ENV["RACK_ENV"] || "development"
|
96
|
+
ENV["APP_ENV"] = ENV["RACK_ENV"] = @options[:env]
|
97
|
+
end
|
98
|
+
|
99
|
+
def parse_with_unknown_args
|
100
|
+
parser, unknown, original, unparsed = yield, Array.new, @argv.dup, Array.new
|
101
|
+
|
102
|
+
begin
|
103
|
+
parser.order!(@argv) do |arg|
|
104
|
+
if @command
|
105
|
+
unparsed << arg
|
106
|
+
else
|
107
|
+
@command = arg
|
108
|
+
end
|
109
|
+
end
|
110
|
+
rescue OptionParser::InvalidOption => error
|
111
|
+
unknown.concat(error.args); retry
|
112
|
+
end
|
113
|
+
|
114
|
+
@argv = (original & (@argv | unknown)) + unparsed
|
115
|
+
end
|
116
|
+
|
117
|
+
def setup_environment
|
118
|
+
Pakyow.setup(env: @options[:env])
|
119
|
+
end
|
120
|
+
|
121
|
+
def load_tasks
|
122
|
+
Pakyow.load_tasks
|
123
|
+
end
|
124
|
+
|
125
|
+
def find_task_for_command
|
126
|
+
unless @task = tasks.find { |task| task.name == @command }
|
127
|
+
raise UnknownCommand.new_with_message(
|
128
|
+
command: @command
|
129
|
+
)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def set_app_for_command
|
134
|
+
if @task.app?
|
135
|
+
Pakyow.boot
|
136
|
+
@options[:app] = if @options.key?(:app)
|
137
|
+
Pakyow.app(@options[:app]) || raise("`#{@options[:app]}' is not a known app")
|
138
|
+
elsif Pakyow.apps.count == 1
|
139
|
+
Pakyow.apps.first
|
140
|
+
elsif Pakyow.apps.count > 0
|
141
|
+
raise "multiple apps were found; please specify one with the --app option"
|
142
|
+
else
|
143
|
+
raise "couldn't find an app to run this command for"
|
144
|
+
end
|
145
|
+
elsif @options.key?(:app)
|
146
|
+
puts_warning "app was ignored by command #{Support::CLI.style.blue(@command)}"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def call_task
|
151
|
+
if @options[:help]
|
152
|
+
puts @task.help
|
153
|
+
else
|
154
|
+
@task.call(@options.select { |key, _|
|
155
|
+
(key == :app && @task.app?) || key != :app
|
156
|
+
}, @argv.dup)
|
157
|
+
end
|
158
|
+
rescue InvalidInput => error
|
159
|
+
puts_error(error)
|
160
|
+
puts @task.help(describe: false)
|
161
|
+
end
|
162
|
+
|
163
|
+
def puts_error(error)
|
164
|
+
puts " #{Support::CLI.style.red("›")} #{Error::CLIFormatter.format(error.to_s)}"
|
165
|
+
end
|
166
|
+
|
167
|
+
def puts_warning(warning)
|
168
|
+
puts " #{Support::CLI.style.yellow("›")} #{warning}"
|
169
|
+
end
|
170
|
+
|
171
|
+
def puts_help(banner: true)
|
172
|
+
if banner
|
173
|
+
puts_banner
|
174
|
+
end
|
175
|
+
|
176
|
+
puts_usage
|
177
|
+
puts_commands
|
178
|
+
end
|
179
|
+
|
180
|
+
def puts_banner
|
181
|
+
puts Support::CLI.style.blue.bold("Pakyow Command Line Interface")
|
182
|
+
end
|
183
|
+
|
184
|
+
def puts_usage
|
185
|
+
puts
|
186
|
+
puts Support::CLI.style.bold("USAGE")
|
187
|
+
puts " $ pakyow [COMMAND]"
|
188
|
+
end
|
189
|
+
|
190
|
+
def puts_commands
|
191
|
+
puts
|
192
|
+
puts Support::CLI.style.bold("COMMANDS")
|
193
|
+
longest_name_length = tasks.map(&:name).max_by(&:length).length
|
194
|
+
tasks.sort { |a, b| a.name <=> b.name }.each do |task|
|
195
|
+
puts " #{task.name}".ljust(longest_name_length + 4) + Support::CLI.style.yellow(task.description) + "\n"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
|
5
|
+
module Pakyow
|
6
|
+
class Connection
|
7
|
+
class MultipartInput < SimpleDelegator
|
8
|
+
attr_reader :filename, :headers, :type
|
9
|
+
|
10
|
+
def initialize(filename:, headers:, type:)
|
11
|
+
@filename, @headers, @type = filename, headers, type
|
12
|
+
__setobj__(Tempfile.new(["PakyowMultipart", File.extname(filename)]))
|
13
|
+
end
|
14
|
+
|
15
|
+
alias media_type type
|
16
|
+
|
17
|
+
# Fixes an issue using pp inside a delegator.
|
18
|
+
#
|
19
|
+
def pp(*args)
|
20
|
+
Kernel.pp(*args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "multipart_parser/reader"
|
4
|
+
require "protocol/http/headers"
|
5
|
+
|
6
|
+
require "pakyow/error"
|
7
|
+
|
8
|
+
require "pakyow/connection/multipart_input"
|
9
|
+
|
10
|
+
module Pakyow
|
11
|
+
class Connection
|
12
|
+
class MultipartParser
|
13
|
+
class LimitExceeded < Error; end
|
14
|
+
class ParseError < Error; end
|
15
|
+
|
16
|
+
DEFAULT_MULTIPART_LIMIT = 100
|
17
|
+
|
18
|
+
attr_reader :values
|
19
|
+
|
20
|
+
def initialize(params, boundary:)
|
21
|
+
@params, @boundary = params, boundary.to_s.gsub(/[\"\']/, "")
|
22
|
+
@reader = ::MultipartParser::Reader.new(@boundary)
|
23
|
+
@reader.on_part(&method(:on_part))
|
24
|
+
@reader.on_error(&method(:on_error))
|
25
|
+
@values = []
|
26
|
+
@size = 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse(input)
|
30
|
+
while data = input.read
|
31
|
+
@reader.write(data)
|
32
|
+
end
|
33
|
+
|
34
|
+
finalize
|
35
|
+
@params
|
36
|
+
rescue StandardError => error
|
37
|
+
ensure_closed
|
38
|
+
if error.is_a?(LimitExceeded)
|
39
|
+
raise error
|
40
|
+
else
|
41
|
+
raise ParseError.build(error)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def finalize
|
48
|
+
@values.select { |value|
|
49
|
+
value.is_a?(MultipartInput)
|
50
|
+
}.each(&:rewind)
|
51
|
+
end
|
52
|
+
|
53
|
+
def add(value)
|
54
|
+
@values << value
|
55
|
+
|
56
|
+
if value.is_a?(MultipartInput)
|
57
|
+
@size += 1
|
58
|
+
end
|
59
|
+
|
60
|
+
if @size > DEFAULT_MULTIPART_LIMIT
|
61
|
+
raise LimitExceeded, "multipart limit (#{DEFAULT_MULTIPART_LIMIT}) exceeded"
|
62
|
+
end
|
63
|
+
|
64
|
+
value
|
65
|
+
end
|
66
|
+
|
67
|
+
def on_part(part)
|
68
|
+
headers = Protocol::HTTP::Headers.new(part.headers).to_h
|
69
|
+
disposition = QueryParser.new.tap { |parser|
|
70
|
+
parser.parse(headers["content-disposition"].to_s)
|
71
|
+
}.params
|
72
|
+
content_type = QueryParser.new.tap { |parser|
|
73
|
+
parser.parse(headers["content-type"].to_s)
|
74
|
+
}.params
|
75
|
+
|
76
|
+
if filename = disposition["filename"]
|
77
|
+
value = add(MultipartInput.new(filename: filename, headers: headers, type: part.mime))
|
78
|
+
|
79
|
+
part.on_data do |data|
|
80
|
+
value << data
|
81
|
+
end
|
82
|
+
else
|
83
|
+
value = add(String.new)
|
84
|
+
encoding = if charset = content_type["charset"]
|
85
|
+
Encoding.find(charset.gsub(/[^a-zA-Z0-9\-_]/, ""))
|
86
|
+
else
|
87
|
+
Encoding::UTF_8
|
88
|
+
end
|
89
|
+
|
90
|
+
value.force_encoding(encoding)
|
91
|
+
|
92
|
+
part.on_data do |data|
|
93
|
+
value << data
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
@params.add_value_for_key(value, part.name || disposition["filename"])
|
98
|
+
end
|
99
|
+
|
100
|
+
def on_error(error)
|
101
|
+
ensure_closed
|
102
|
+
raise ParseError, error
|
103
|
+
end
|
104
|
+
|
105
|
+
def ensure_closed
|
106
|
+
@values.each do |value|
|
107
|
+
value.close if value.is_a?(MultipartInput)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
require "forwardable"
|
5
|
+
|
6
|
+
require "pakyow/support/indifferentize"
|
7
|
+
|
8
|
+
require "pakyow/connection/query_parser"
|
9
|
+
|
10
|
+
module Pakyow
|
11
|
+
class Connection
|
12
|
+
class Params < DelegateClass(Support::IndifferentHash)
|
13
|
+
extend Forwardable
|
14
|
+
def_delegators :@parser, :parse, :add, :add_value_for_key
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
params = Support::IndifferentHash.new
|
18
|
+
@parser = QueryParser.new(params: params)
|
19
|
+
super(params)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Fixes an issue using pp inside a delegator.
|
23
|
+
#
|
24
|
+
def pp(*args)
|
25
|
+
Kernel.pp(*args)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cgi"
|
4
|
+
|
5
|
+
require "pakyow/error"
|
6
|
+
|
7
|
+
module Pakyow
|
8
|
+
class Connection
|
9
|
+
# Parses one or more query strings, building up a params hash. Supports nested query strings,
|
10
|
+
# and enforces limits for key space size and total nested parameter depth.
|
11
|
+
#
|
12
|
+
# Aspects of this were inspired by Rack's query parser, including key space and depth limits. We
|
13
|
+
# decided it was worth writing our own for several reasons:
|
14
|
+
#
|
15
|
+
# 1) Avoid Rack as a dependency for the majority use-case.
|
16
|
+
#
|
17
|
+
# 2) Improve the interface so you don't have to know ahead of time if you're dealing with a
|
18
|
+
# nested query string or not, and to allow for params to be built up from many strings.
|
19
|
+
#
|
20
|
+
# 3) Improve performance (up to 90% faster for a simple query string, 10% for nested).
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
class QueryParser
|
24
|
+
class InvalidParameter < Error; end
|
25
|
+
class KeySpaceLimitExceeded < Error; end
|
26
|
+
class DepthLimitExceeded < Error; end
|
27
|
+
|
28
|
+
DEFAULT_DELIMETER = /[&;,]/
|
29
|
+
DEFAULT_KEY_SPACE_LIMIT = 102_400
|
30
|
+
DEFAULT_DEPTH_LIMIT = 100
|
31
|
+
|
32
|
+
attr_reader :params, :key_space_limit, :depth_limit
|
33
|
+
|
34
|
+
def initialize(key_space_limit: DEFAULT_KEY_SPACE_LIMIT, depth_limit: DEFAULT_DEPTH_LIMIT, params: {})
|
35
|
+
@params = params
|
36
|
+
@key_space_limit = key_space_limit
|
37
|
+
@key_space_size = 0
|
38
|
+
@depth_limit = depth_limit
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse(input, delimiter = DEFAULT_DELIMETER)
|
42
|
+
input.to_s.split(delimiter).each do |part|
|
43
|
+
key, value = part.split("=", 2)
|
44
|
+
key = unescape(key).strip if key
|
45
|
+
value = unescape(value).strip if value
|
46
|
+
add_value_for_key(value, key)
|
47
|
+
end
|
48
|
+
|
49
|
+
@params
|
50
|
+
end
|
51
|
+
|
52
|
+
def add(key, value, params = @params)
|
53
|
+
unless params.key?(key)
|
54
|
+
@key_space_size += key.size
|
55
|
+
end
|
56
|
+
|
57
|
+
if @key_space_size > @key_space_limit
|
58
|
+
raise KeySpaceLimitExceeded, "key space limit (#{@key_space_limit}) exceeded by `#{key}'"
|
59
|
+
else
|
60
|
+
params[key] = value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_value_for_key(value, key, params = @params, depth = 0)
|
65
|
+
if depth > @depth_limit
|
66
|
+
raise DepthLimitExceeded, "depth limit (#{@depth_limit}) exceeded by `#{key}'"
|
67
|
+
end
|
68
|
+
|
69
|
+
if key && key.include?("[") && key.include?("]")
|
70
|
+
opened = false
|
71
|
+
read, nested = String.new, nil
|
72
|
+
|
73
|
+
key.length.times do |i|
|
74
|
+
char = key[i]
|
75
|
+
|
76
|
+
if char == "["
|
77
|
+
opened = true
|
78
|
+
elsif char == "]" && opened
|
79
|
+
opened = false
|
80
|
+
|
81
|
+
case params
|
82
|
+
when Array
|
83
|
+
nested_value = if nested
|
84
|
+
if current_nested_value = params.last
|
85
|
+
unless current_nested_value.is_a?(@params.class)
|
86
|
+
raise InvalidParameter, "expected `#{read}' to be #{@params.class} (got #{current_nested_value.class})"
|
87
|
+
end
|
88
|
+
|
89
|
+
if current_nested_value.key?(nested)
|
90
|
+
(params << @params.class.new).last
|
91
|
+
else
|
92
|
+
current_nested_value
|
93
|
+
end
|
94
|
+
else
|
95
|
+
(params << @params.class.new).last
|
96
|
+
end
|
97
|
+
else
|
98
|
+
if current_nested_value = params[read]
|
99
|
+
unless current_nested_value.is_a?(Array)
|
100
|
+
raise InvalidParameter, "expected `#{read}' to be Array (got #{current_nested_value.class})"
|
101
|
+
end
|
102
|
+
|
103
|
+
current_nested_value
|
104
|
+
else
|
105
|
+
(params << []).last
|
106
|
+
end
|
107
|
+
end
|
108
|
+
when @params.class
|
109
|
+
nested_value = if nested
|
110
|
+
if current_nested_value = params[read]
|
111
|
+
unless current_nested_value.is_a?(@params.class)
|
112
|
+
raise InvalidParameter, "expected `#{read}' to be #{@params.class} (got #{current_nested_value.class})"
|
113
|
+
end
|
114
|
+
|
115
|
+
current_nested_value
|
116
|
+
else
|
117
|
+
@params.class.new
|
118
|
+
end
|
119
|
+
else
|
120
|
+
if current_nested_value = params[read]
|
121
|
+
unless current_nested_value.is_a?(Array)
|
122
|
+
raise InvalidParameter, "expected `#{read}' to be Array (got #{current_nested_value.class})"
|
123
|
+
end
|
124
|
+
|
125
|
+
current_nested_value
|
126
|
+
else
|
127
|
+
[]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
add(read, nested_value, params)
|
132
|
+
end
|
133
|
+
|
134
|
+
j = i + 1
|
135
|
+
if (next_char = key[j]) && next_char != "["
|
136
|
+
raise InvalidParameter, "expected `#{nested}' to be #{params.class} (got String)"
|
137
|
+
else
|
138
|
+
add_value_for_key(value, (nested || String.new) << key[j..-1], nested_value, depth + 1); break
|
139
|
+
end
|
140
|
+
elsif opened
|
141
|
+
(nested ||= String.new) << char
|
142
|
+
else
|
143
|
+
read << char
|
144
|
+
end
|
145
|
+
end
|
146
|
+
else
|
147
|
+
case params
|
148
|
+
when Array
|
149
|
+
params << value
|
150
|
+
when @params.class
|
151
|
+
if depth == 0 && (current_value = params[key]) && !(current_value.is_a?(Array) || current_value.is_a?(@params.class))
|
152
|
+
if current_value.is_a?(Array)
|
153
|
+
current_value << value
|
154
|
+
else
|
155
|
+
current_value = [current_value, value]
|
156
|
+
add(key, current_value, params)
|
157
|
+
end
|
158
|
+
else
|
159
|
+
if key && !key.empty?
|
160
|
+
add(key, value, params)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def unescape(string)
|
170
|
+
CGI.unescape(string)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pakyow
|
4
|
+
class Connection
|
5
|
+
# @api private
|
6
|
+
class Statuses
|
7
|
+
CODE_TO_DESCRIPTION = {
|
8
|
+
100 => "Continue",
|
9
|
+
101 => "Switching Protocols",
|
10
|
+
102 => "Processing",
|
11
|
+
103 => "Early Hints",
|
12
|
+
|
13
|
+
200 => "OK",
|
14
|
+
201 => "Created",
|
15
|
+
202 => "Accepted",
|
16
|
+
203 => "Non-Authoritative Information",
|
17
|
+
204 => "No Content",
|
18
|
+
205 => "Reset Content",
|
19
|
+
206 => "Partial Content",
|
20
|
+
207 => "Multi-Status",
|
21
|
+
208 => "Already Reported",
|
22
|
+
226 => "IM Used",
|
23
|
+
|
24
|
+
300 => "Multiple Choices",
|
25
|
+
301 => "Moved Permanently",
|
26
|
+
302 => "Found",
|
27
|
+
303 => "See Other",
|
28
|
+
304 => "Not Modified",
|
29
|
+
305 => "Use Proxy",
|
30
|
+
# no longer used, but included for completeness
|
31
|
+
306 => "Switch Proxy",
|
32
|
+
307 => "Temporary Redirect",
|
33
|
+
308 => "Permanent Redirect",
|
34
|
+
|
35
|
+
400 => "Bad Request",
|
36
|
+
401 => "Unauthorized",
|
37
|
+
402 => "Payment Required",
|
38
|
+
403 => "Forbidden",
|
39
|
+
404 => "Not Found",
|
40
|
+
405 => "Method Not Allowed",
|
41
|
+
406 => "Not Acceptable",
|
42
|
+
407 => "Proxy Authentication Required",
|
43
|
+
408 => "Request Timeout",
|
44
|
+
409 => "Conflict",
|
45
|
+
410 => "Gone",
|
46
|
+
411 => "Length Required",
|
47
|
+
412 => "Precondition Failed",
|
48
|
+
413 => "Payload Too Large",
|
49
|
+
414 => "URI Too Long",
|
50
|
+
415 => "Unsupported Media Type",
|
51
|
+
416 => "Range Not Satisfiable",
|
52
|
+
417 => "Expectation Failed",
|
53
|
+
421 => "Misdirected Request",
|
54
|
+
422 => "Unprocessable Entity",
|
55
|
+
423 => "Locked",
|
56
|
+
424 => "Failed Dependency",
|
57
|
+
426 => "Upgrade Required",
|
58
|
+
428 => "Precondition Required",
|
59
|
+
429 => "Too Many Requests",
|
60
|
+
431 => "Request Header Fields Too Large",
|
61
|
+
451 => "Unavailable for Legal Reasons",
|
62
|
+
|
63
|
+
500 => "Internal Server Error",
|
64
|
+
501 => "Not Implemented",
|
65
|
+
502 => "Bad Gateway",
|
66
|
+
503 => "Service Unavailable",
|
67
|
+
504 => "Gateway Timeout",
|
68
|
+
505 => "HTTP Version Not Supported",
|
69
|
+
506 => "Variant Also Negotiates",
|
70
|
+
507 => "Insufficient Storage",
|
71
|
+
508 => "Loop Detected",
|
72
|
+
510 => "Not Extended",
|
73
|
+
511 => "Network Authentication Required"
|
74
|
+
}.freeze
|
75
|
+
|
76
|
+
SYMBOL_TO_CODE = Hash[CODE_TO_DESCRIPTION.map { |code, message|
|
77
|
+
[message.downcase.gsub(/[^a-z]/, "_").to_sym, code]
|
78
|
+
}].freeze
|
79
|
+
|
80
|
+
class << self
|
81
|
+
# Returns the string representation for a status code or symbolized status name.
|
82
|
+
#
|
83
|
+
# @example
|
84
|
+
# Pakyow::Connection::Statuses.describe(200)
|
85
|
+
# => "OK"
|
86
|
+
#
|
87
|
+
# Pakyow::Connection::Statuses.describe(:ok)
|
88
|
+
# => "OK"
|
89
|
+
#
|
90
|
+
def describe(code_or_symbol)
|
91
|
+
CODE_TO_DESCRIPTION[code(code_or_symbol)] || "?"
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the status code for the symbolized status name.
|
95
|
+
#
|
96
|
+
# @example
|
97
|
+
# Pakyow::Connection::Statuses.code(:ok)
|
98
|
+
# => 200
|
99
|
+
#
|
100
|
+
# Pakyow::Connection::Statuses.code(200)
|
101
|
+
# => 200
|
102
|
+
#
|
103
|
+
def code(code_or_symbol)
|
104
|
+
case code_or_symbol
|
105
|
+
when Symbol
|
106
|
+
SYMBOL_TO_CODE[code_or_symbol]
|
107
|
+
else
|
108
|
+
code_or_symbol = code_or_symbol.to_i
|
109
|
+
if CODE_TO_DESCRIPTION.key?(code_or_symbol)
|
110
|
+
code_or_symbol
|
111
|
+
else
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|