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.
Files changed (156) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +0 -0
  3. data/LICENSE +4 -0
  4. data/{pakyow-core/README.md → README.md} +3 -4
  5. data/commands/pakyow +13 -0
  6. data/lib/pakyow/app/connection/behavior/session.rb +33 -0
  7. data/lib/pakyow/app/connection/behavior/values.rb +42 -0
  8. data/lib/pakyow/app/connection/behavior/verifier.rb +34 -0
  9. data/lib/pakyow/app/connection/session/abstract.rb +24 -0
  10. data/lib/pakyow/app/connection/session/cookie.rb +56 -0
  11. data/lib/pakyow/app/connection.rb +57 -0
  12. data/lib/pakyow/app.rb +219 -0
  13. data/lib/pakyow/behavior/aspects.rb +45 -0
  14. data/lib/pakyow/behavior/config.rb +72 -0
  15. data/lib/pakyow/behavior/endpoints.rb +41 -0
  16. data/lib/pakyow/behavior/frameworks.rb +45 -0
  17. data/lib/pakyow/behavior/helpers.rb +107 -0
  18. data/lib/pakyow/behavior/initializers.rb +19 -0
  19. data/lib/pakyow/behavior/isolating.rb +69 -0
  20. data/lib/pakyow/behavior/operations.rb +46 -0
  21. data/lib/pakyow/behavior/pipeline.rb +58 -0
  22. data/lib/pakyow/behavior/plugins.rb +148 -0
  23. data/lib/pakyow/behavior/rescuing.rb +51 -0
  24. data/lib/pakyow/behavior/restarting.rb +68 -0
  25. data/lib/pakyow/behavior/sessions.rb +34 -0
  26. data/lib/pakyow/behavior/verification.rb +47 -0
  27. data/lib/pakyow/cli.rb +199 -0
  28. data/lib/pakyow/connection/multipart_input.rb +24 -0
  29. data/lib/pakyow/connection/multipart_parser.rb +112 -0
  30. data/lib/pakyow/connection/params.rb +29 -0
  31. data/lib/pakyow/connection/query_parser.rb +174 -0
  32. data/lib/pakyow/connection/statuses.rb +119 -0
  33. data/lib/pakyow/connection.rb +568 -0
  34. data/lib/pakyow/endpoints.rb +94 -0
  35. data/lib/pakyow/environment/actions/dispatch.rb +37 -0
  36. data/lib/pakyow/environment/actions/input_parser.rb +21 -0
  37. data/lib/pakyow/environment/actions/logger.rb +33 -0
  38. data/lib/pakyow/environment/actions/normalizer.rb +73 -0
  39. data/lib/pakyow/environment/behavior/config.rb +166 -0
  40. data/lib/pakyow/environment/behavior/initializers.rb +21 -0
  41. data/lib/pakyow/environment/behavior/input_parsing.rb +54 -0
  42. data/lib/pakyow/environment/behavior/plugins.rb +23 -0
  43. data/lib/pakyow/environment/behavior/restarting.rb +54 -0
  44. data/lib/pakyow/environment/behavior/running.rb +129 -0
  45. data/lib/pakyow/environment/behavior/silencing.rb +23 -0
  46. data/lib/pakyow/environment/behavior/timezone.rb +21 -0
  47. data/lib/pakyow/environment/behavior/watching.rb +75 -0
  48. data/lib/pakyow/environment.rb +381 -0
  49. data/lib/pakyow/error.rb +273 -0
  50. data/lib/pakyow/errors.rb +97 -0
  51. data/lib/pakyow/framework.rb +39 -0
  52. data/lib/pakyow/generator.rb +138 -0
  53. data/lib/pakyow/generators/project/default/%dot%env.erb +1 -0
  54. data/lib/pakyow/generators/project/default/%dot%gitignore +11 -0
  55. data/lib/pakyow/generators/project/default/%dot%ruby-version.erb +1 -0
  56. data/lib/pakyow/generators/project/default/Gemfile.erb +28 -0
  57. data/lib/pakyow/generators/project/default/README.md +14 -0
  58. data/lib/pakyow/generators/project/default/config/application.rb.erb +17 -0
  59. data/lib/pakyow/generators/project/default/config/environment.rb +11 -0
  60. data/lib/pakyow/generators/project/default/config/initializers/application/keep +0 -0
  61. data/lib/pakyow/generators/project/default/config/initializers/environment/keep +0 -0
  62. data/lib/pakyow/generators/project/default/database/keep +0 -0
  63. data/lib/pakyow/generators/project/default/frontend/assets/images/keep +0 -0
  64. data/lib/pakyow/generators/project/default/frontend/assets/packs/keep +0 -0
  65. data/lib/pakyow/generators/project/default/frontend/assets/styles/keep +0 -0
  66. data/lib/pakyow/generators/project/default/frontend/includes/keep +0 -0
  67. data/lib/pakyow/generators/project/default/frontend/layouts/default.html.erb +19 -0
  68. data/lib/pakyow/generators/project/default/frontend/pages/keep +0 -0
  69. data/lib/pakyow/generators/project/default/public/favicon.ico +0 -0
  70. data/lib/pakyow/generators/project/default/public/robots.txt +1 -0
  71. data/lib/pakyow/generators/project.rb +29 -0
  72. data/lib/pakyow/helpers/app.rb +28 -0
  73. data/lib/pakyow/helpers/connection.rb +45 -0
  74. data/lib/pakyow/info.rb +26 -0
  75. data/lib/pakyow/integrations/bootsnap.rb +18 -0
  76. data/lib/pakyow/integrations/bundler/require.rb +7 -0
  77. data/lib/pakyow/integrations/bundler/setup.rb +7 -0
  78. data/lib/pakyow/integrations/dotenv.rb +9 -0
  79. data/lib/pakyow/loader.rb +31 -0
  80. data/lib/pakyow/logger/colorizer.rb +38 -0
  81. data/lib/pakyow/logger/destination.rb +17 -0
  82. data/lib/pakyow/logger/formatter.rb +22 -0
  83. data/lib/pakyow/logger/formatters/human.rb +101 -0
  84. data/lib/pakyow/logger/formatters/json.rb +99 -0
  85. data/lib/pakyow/logger/formatters/logfmt.rb +50 -0
  86. data/lib/pakyow/logger/multiplexed.rb +19 -0
  87. data/lib/pakyow/logger/thread_local.rb +29 -0
  88. data/lib/pakyow/logger/timekeeper.rb +64 -0
  89. data/lib/pakyow/logger.rb +125 -0
  90. data/lib/pakyow/operation.rb +71 -0
  91. data/lib/pakyow/plugin/helper_caller.rb +20 -0
  92. data/lib/pakyow/plugin/lookup.rb +33 -0
  93. data/lib/pakyow/plugin/state.rb +68 -0
  94. data/lib/pakyow/plugin.rb +367 -0
  95. data/lib/pakyow/process_manager.rb +53 -0
  96. data/lib/pakyow/processes/proxy.rb +67 -0
  97. data/lib/pakyow/processes/server.rb +32 -0
  98. data/lib/pakyow/rack/compatibility.rb +68 -0
  99. data/lib/pakyow/task.rb +315 -0
  100. data/lib/pakyow/tasks/boot.rake +21 -0
  101. data/lib/pakyow/tasks/create.rake +32 -0
  102. data/lib/pakyow/tasks/help.rake +17 -0
  103. data/lib/pakyow/tasks/info/endpoints.rake +72 -0
  104. data/lib/pakyow/tasks/info.rake +31 -0
  105. data/lib/pakyow/tasks/irb.rake +11 -0
  106. data/lib/pakyow/tasks/prelaunch.rake +37 -0
  107. data/lib/pakyow/types.rb +32 -0
  108. data/lib/pakyow/validations/acceptance.rb +37 -0
  109. data/lib/pakyow/validations/email.rb +28 -0
  110. data/lib/pakyow/validations/inline.rb +32 -0
  111. data/lib/pakyow/validations/length.rb +44 -0
  112. data/lib/pakyow/validations/presence.rb +41 -0
  113. data/lib/pakyow/validations.rb +8 -0
  114. data/lib/pakyow/validator.rb +81 -0
  115. data/lib/pakyow/verifier.rb +177 -0
  116. data/lib/pakyow.rb +5 -0
  117. metadata +267 -65
  118. data/pakyow-core/CHANGELOG.md +0 -128
  119. data/pakyow-core/LICENSE +0 -20
  120. data/pakyow-core/lib/pakyow/core/app.rb +0 -82
  121. data/pakyow-core/lib/pakyow/core/app_context.rb +0 -10
  122. data/pakyow-core/lib/pakyow/core/base.rb +0 -61
  123. data/pakyow-core/lib/pakyow/core/call_context.rb +0 -171
  124. data/pakyow-core/lib/pakyow/core/config/app.rb +0 -49
  125. data/pakyow-core/lib/pakyow/core/config/cookies.rb +0 -4
  126. data/pakyow-core/lib/pakyow/core/config/logger.rb +0 -34
  127. data/pakyow-core/lib/pakyow/core/config/reloader.rb +0 -10
  128. data/pakyow-core/lib/pakyow/core/config/server.rb +0 -10
  129. data/pakyow-core/lib/pakyow/core/config/session.rb +0 -41
  130. data/pakyow-core/lib/pakyow/core/config.rb +0 -95
  131. data/pakyow-core/lib/pakyow/core/errors.rb +0 -16
  132. data/pakyow-core/lib/pakyow/core/helpers/configuring.rb +0 -142
  133. data/pakyow-core/lib/pakyow/core/helpers/hooks.rb +0 -106
  134. data/pakyow-core/lib/pakyow/core/helpers/running.rb +0 -124
  135. data/pakyow-core/lib/pakyow/core/helpers.rb +0 -61
  136. data/pakyow-core/lib/pakyow/core/loader.rb +0 -32
  137. data/pakyow-core/lib/pakyow/core/middleware/logger.rb +0 -146
  138. data/pakyow-core/lib/pakyow/core/middleware/override.rb +0 -3
  139. data/pakyow-core/lib/pakyow/core/middleware/reloader.rb +0 -23
  140. data/pakyow-core/lib/pakyow/core/middleware/req_path_normalizer.rb +0 -49
  141. data/pakyow-core/lib/pakyow/core/middleware/session.rb +0 -5
  142. data/pakyow-core/lib/pakyow/core/middleware/static.rb +0 -76
  143. data/pakyow-core/lib/pakyow/core/multilog.rb +0 -19
  144. data/pakyow-core/lib/pakyow/core/request.rb +0 -119
  145. data/pakyow-core/lib/pakyow/core/response.rb +0 -135
  146. data/pakyow-core/lib/pakyow/core/route_eval.rb +0 -254
  147. data/pakyow-core/lib/pakyow/core/route_expansion_eval.rb +0 -115
  148. data/pakyow-core/lib/pakyow/core/route_lookup.rb +0 -41
  149. data/pakyow-core/lib/pakyow/core/route_merger.rb +0 -76
  150. data/pakyow-core/lib/pakyow/core/route_module.rb +0 -21
  151. data/pakyow-core/lib/pakyow/core/route_set.rb +0 -74
  152. data/pakyow-core/lib/pakyow/core/route_template_defaults.rb +0 -43
  153. data/pakyow-core/lib/pakyow/core/route_template_eval.rb +0 -72
  154. data/pakyow-core/lib/pakyow/core/router.rb +0 -181
  155. data/pakyow-core/lib/pakyow/core.rb +0 -8
  156. 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