functions_framework 0.0.0 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +0 -1
- data/CHANGELOG.md +3 -0
- data/README.md +202 -2
- data/bin/functions-framework +19 -0
- data/lib/functions_framework/cli.rb +113 -0
- data/lib/functions_framework/cloud_events/binary_content.rb +59 -0
- data/lib/functions_framework/cloud_events/content_type.rb +139 -0
- data/lib/functions_framework/cloud_events/event.rb +277 -0
- data/lib/functions_framework/cloud_events/json_structure.rb +88 -0
- data/lib/functions_framework/cloud_events.rb +143 -0
- data/lib/functions_framework/function.rb +75 -0
- data/lib/functions_framework/registry.rb +137 -0
- data/lib/functions_framework/server.rb +423 -0
- data/lib/functions_framework/testing.rb +244 -0
- data/lib/functions_framework/version.rb +1 -1
- data/lib/functions_framework.rb +217 -1
- metadata +15 -4
- data/CONTRIBUTING.md +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d09328e219e183d857a5b0e5cbb4a9aac77dcb2b6deea96ecb4909729aac5e46
|
4
|
+
data.tar.gz: 2ffdd21b22c08193b64610ac42ee05f6b5627c25b99ee6d2d45eb52408438e33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 93cf161a26eee8349e99ec7012698374206e333099c94ecff07609bdefe9f7a33bf331aa3a0b305fd005e0a89ba9988bcc3ed3ce4c2c1edadd587975561b5fe8
|
7
|
+
data.tar.gz: 64eea2572a244dca261c090b6e51445b2f59ad2ad985edb29dc2a52aaa902fd9d9afa44e65e6ca51a6653c16d5532ace49cb9a6defd09c45195d9d81e9238707
|
data/.yardopts
CHANGED
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,61 @@
|
|
1
1
|
# Functions Framework
|
2
2
|
|
3
|
+
An open source framework for writing lightweight, portable Ruby functions that
|
4
|
+
run in a serverless environment. Functions written to this Framework will run
|
5
|
+
in many different environments, including:
|
6
|
+
|
7
|
+
* [Google Cloud Functions](https://cloud.google.com/functions) *(coming soon)*
|
8
|
+
* [Cloud Run or Cloud Run for Anthos](https://cloud.google.com/run)
|
9
|
+
* Any other [Knative](https://github.com/knative)-based environment
|
10
|
+
* Your local development machine
|
11
|
+
|
12
|
+
The framework allows you to go from:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
FunctionsFramework.http do |request|
|
16
|
+
"Hello, world!\n"
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
To:
|
21
|
+
|
22
|
+
```sh
|
23
|
+
curl http://my-url
|
24
|
+
# Output: Hello, world!
|
25
|
+
```
|
26
|
+
|
27
|
+
All without needing to worry about writing an HTTP server or complicated
|
28
|
+
request handling logic.
|
29
|
+
|
30
|
+
For more information about the Functions Framework, see
|
31
|
+
https://github.com/GoogleCloudPlatform/functions-framework
|
32
|
+
|
33
|
+
## Features
|
34
|
+
|
35
|
+
* Define named functions using normal Ruby constructs.
|
36
|
+
* Invoke functions in response to requests.
|
37
|
+
* Automatically unmarshal events conforming to the
|
38
|
+
[CloudEvents](https://cloudevents.io) spec.
|
39
|
+
* Spin up a local development server for quick testing.
|
40
|
+
* Integrate with standard Ruby libraries such as Rack and Minitest.
|
41
|
+
* Portable between serverless platforms.
|
42
|
+
|
3
43
|
## Installation
|
4
44
|
|
45
|
+
Install the Functions Framework via Rubygems:
|
46
|
+
|
47
|
+
```sh
|
48
|
+
gem install functions_framework
|
5
49
|
```
|
6
|
-
|
50
|
+
|
51
|
+
Or add it to your Gemfile for installation using Bundler:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
# Gemfile
|
55
|
+
gem "functions_framework", "~> 0.1"
|
7
56
|
```
|
8
57
|
|
9
|
-
|
58
|
+
### Supported Ruby versions
|
10
59
|
|
11
60
|
This library is supported on Ruby 2.4+.
|
12
61
|
|
@@ -16,3 +65,154 @@ in security maintenance, and not end of life. Currently, this means Ruby 2.4
|
|
16
65
|
and later. Older versions of Ruby _may_ still work, but are unsupported and not
|
17
66
|
recommended. See https://www.ruby-lang.org/en/downloads/branches/ for details
|
18
67
|
about the Ruby support schedule.
|
68
|
+
|
69
|
+
## Quickstart
|
70
|
+
|
71
|
+
### Running Hello World on your local machine
|
72
|
+
|
73
|
+
Create a `Gemfile` listing the Functions Framework as a dependency:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
# Gemfile
|
77
|
+
source "https://rubygems.org"
|
78
|
+
gem "functions_framework", "~> 0.1"
|
79
|
+
```
|
80
|
+
|
81
|
+
Create a file called `app.rb` and include the following code. This defines a
|
82
|
+
simple function called "hello".
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
# app.rb
|
86
|
+
require "functions_framework"
|
87
|
+
|
88
|
+
FunctionsFramework.http("hello") do |request|
|
89
|
+
"Hello, world!\n"
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
Install the bundle, and start the framework. This spins up a local web server
|
94
|
+
running your "hello" function:
|
95
|
+
|
96
|
+
```sh
|
97
|
+
bundle install
|
98
|
+
# ...installs the functions_framework gem and other dependencies
|
99
|
+
bundle exec functions-framework --target hello
|
100
|
+
# ...starts the web server in the foreground
|
101
|
+
```
|
102
|
+
|
103
|
+
In a separate shell, you can send requests to this function using curl:
|
104
|
+
|
105
|
+
```sh
|
106
|
+
curl localhost:8080
|
107
|
+
# Output: Hello, world!
|
108
|
+
```
|
109
|
+
|
110
|
+
Stop the web server with `CTRL+C`.
|
111
|
+
|
112
|
+
### Deploying a function to Google Cloud Functions
|
113
|
+
|
114
|
+
(Google Cloud Functions does not yet support the Ruby Function Framework)
|
115
|
+
|
116
|
+
### Deploying a function to Cloud Run
|
117
|
+
|
118
|
+
Create a `Dockerfile` for your function:
|
119
|
+
|
120
|
+
```dockerfile
|
121
|
+
# Dockerfile
|
122
|
+
FROM ruby:2.6
|
123
|
+
|
124
|
+
WORKDIR /app
|
125
|
+
COPY . .
|
126
|
+
RUN gem install --no-document bundler \
|
127
|
+
&& bundle config --local frozen true \
|
128
|
+
&& bundle install
|
129
|
+
|
130
|
+
ENTRYPOINT ["bundle", "exec", "functions-framework"]
|
131
|
+
CMD ["--target", "hello"]
|
132
|
+
```
|
133
|
+
|
134
|
+
Build your function into a Docker image:
|
135
|
+
|
136
|
+
```sh
|
137
|
+
gcloud builds submit --tag=gcr.io/[YOUR-PROJECT]/hello:build-1
|
138
|
+
```
|
139
|
+
|
140
|
+
Deploy to Cloud Run:
|
141
|
+
|
142
|
+
```sh
|
143
|
+
gcloud run deploy hello --image=gcr.io/[YOUR-PROJECT]/hello:build-1 \
|
144
|
+
--platform=managed --allow-unauthenticated --region=us-central1
|
145
|
+
```
|
146
|
+
|
147
|
+
You can use a similar approach to deploy to any other Knative-based serverless
|
148
|
+
environment.
|
149
|
+
|
150
|
+
### Responding to CloudEvents
|
151
|
+
|
152
|
+
You can also define a function that response to
|
153
|
+
[CloudEvents](https://cloudevents.io). The Functions Framework will handle
|
154
|
+
unmarshalling of the event data.
|
155
|
+
|
156
|
+
Change `app.rb` to read:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
# app.rb
|
160
|
+
require "functions_framework"
|
161
|
+
|
162
|
+
FunctionsFramework.event("my-handler") do |data, context|
|
163
|
+
FunctionsFramework.logger.info "I received #{data.inspect}"
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
Start up the framework with this new function:
|
168
|
+
|
169
|
+
```sh
|
170
|
+
bundle install
|
171
|
+
bundle exec functions-framework --target my-handler
|
172
|
+
```
|
173
|
+
|
174
|
+
In a separate shell, you can send a CloudEvent to this function using curl:
|
175
|
+
|
176
|
+
```sh
|
177
|
+
curl --header "Content-Type: text/plain; charset=utf-8" \
|
178
|
+
--header "CE-ID: 12345" \
|
179
|
+
--header "CE-Source: curl" \
|
180
|
+
--header "CE-Type: com.example.test" \
|
181
|
+
--header "CE-Specversion: 1.0" \
|
182
|
+
--data "Hello, world!" \
|
183
|
+
http://localhost:8080
|
184
|
+
```
|
185
|
+
|
186
|
+
CloudEvents functions do not return meaningful results, but you will see the
|
187
|
+
log message from the web server.
|
188
|
+
|
189
|
+
### Configuring the Functions Framework
|
190
|
+
|
191
|
+
The Ruby Functions Framework recognizes the standard command line arguments to
|
192
|
+
the `functions-framework` executable. Each argument also corresponds to an
|
193
|
+
environment variable. If you specify both, the environment variable will be
|
194
|
+
ignored.
|
195
|
+
|
196
|
+
Command-line flag | Environment variable | Description
|
197
|
+
----------------- | -------------------- | -----------
|
198
|
+
`--port` | `PORT` | The port on which the Functions Framework listens for requests. Default: `8080`
|
199
|
+
`--target` | `FUNCTION_TARGET` | The name of the exported function to be invoked in response to requests. Default: `function`
|
200
|
+
`--source` | `FUNCTION_SOURCE` | The path to the file containing your function. Default: `app.rb` (in the current working directory)
|
201
|
+
|
202
|
+
Note: the flag `--signature-type` and corresponding environment variable
|
203
|
+
`FUNCTION_SIGNATURE_TYPE` are not used by the Ruby Function Framework, because
|
204
|
+
you specify the signature type when defining the function in the source.
|
205
|
+
|
206
|
+
The Ruby `functions-framework` executable also recognizes several additional
|
207
|
+
flags that can be used to control logging verbosity, binding, and other
|
208
|
+
parameters. For details, see the online help:
|
209
|
+
|
210
|
+
```sh
|
211
|
+
functions-framework --help
|
212
|
+
```
|
213
|
+
|
214
|
+
## For more information
|
215
|
+
|
216
|
+
* See the `examples` directory for additional examples
|
217
|
+
* Consult https://rubydoc.info/gems/functions_framework for full reference
|
218
|
+
documentation.
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Copyright 2020 Google LLC
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require "functions_framework/cli"
|
18
|
+
|
19
|
+
::FunctionsFramework::CLI.new.parse_args(::ARGV).run
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# Copyright 2020 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "optparse"
|
16
|
+
|
17
|
+
require "functions_framework"
|
18
|
+
|
19
|
+
module FunctionsFramework
|
20
|
+
##
|
21
|
+
# Implementation of the functions-framework executable.
|
22
|
+
#
|
23
|
+
class CLI
|
24
|
+
##
|
25
|
+
# Create a new CLI, setting arguments to their defaults.
|
26
|
+
#
|
27
|
+
def initialize
|
28
|
+
@target = ::ENV["FUNCTION_TARGET"] || ::FunctionsFramework::DEFAULT_TARGET
|
29
|
+
@source = ::ENV["FUNCTION_SOURCE"] || ::FunctionsFramework::DEFAULT_SOURCE
|
30
|
+
@env = nil
|
31
|
+
@port = nil
|
32
|
+
@bind = nil
|
33
|
+
@min_threads = nil
|
34
|
+
@max_threads = nil
|
35
|
+
@detailed_errors = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Parse the given command line arguments.
|
40
|
+
# Exits if argument parsing failed.
|
41
|
+
#
|
42
|
+
# @param argv [Array<String>]
|
43
|
+
# @return [self]
|
44
|
+
#
|
45
|
+
def parse_args argv # rubocop:disable Metrics/MethodLength
|
46
|
+
option_parser = ::OptionParser.new do |op| # rubocop:disable Metrics/BlockLength
|
47
|
+
op.on "-t", "--target TARGET",
|
48
|
+
"Set the name of the function to execute (defaults to #{DEFAULT_TARGET})" do |val|
|
49
|
+
@target = val
|
50
|
+
end
|
51
|
+
op.on "-s", "--source SOURCE",
|
52
|
+
"Set the source file to load (defaults to #{DEFAULT_SOURCE})" do |val|
|
53
|
+
@source = val
|
54
|
+
end
|
55
|
+
op.on "-p", "--port PORT", "Set the port to listen to (defaults to 8080)" do |val|
|
56
|
+
@port = val.to_i
|
57
|
+
end
|
58
|
+
op.on "-b", "--bind BIND", "Set the address to bind to (defaults to 0.0.0.0)" do |val|
|
59
|
+
@bind = val
|
60
|
+
end
|
61
|
+
op.on "-e", "--environment ENV", "Set the Rack environment" do |val|
|
62
|
+
@env = val
|
63
|
+
end
|
64
|
+
op.on "--min-threads NUM", "Set the minimum threead pool size" do |val|
|
65
|
+
@min_threads = val
|
66
|
+
end
|
67
|
+
op.on "--max-threads NUM", "Set the maximum threead pool size" do |val|
|
68
|
+
@max_threads = val
|
69
|
+
end
|
70
|
+
op.on "--[no-]detailed-errors", "Set whether to show error details" do |val|
|
71
|
+
@detailed_errors = val
|
72
|
+
end
|
73
|
+
op.on "-v", "--verbose", "Increase log verbosity" do
|
74
|
+
::FunctionsFramework.logger.level -= 1
|
75
|
+
end
|
76
|
+
op.on "-q", "--quiet", "Decrease log verbosity" do
|
77
|
+
::FunctionsFramework.logger.level += 1
|
78
|
+
end
|
79
|
+
op.on "--help", "Display help" do
|
80
|
+
puts op
|
81
|
+
exit
|
82
|
+
end
|
83
|
+
end
|
84
|
+
option_parser.parse! argv
|
85
|
+
unless argv.empty?
|
86
|
+
warn "Unrecognized arguments: #{argv}"
|
87
|
+
puts op
|
88
|
+
exit 1
|
89
|
+
end
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Run the configured server, and block until it stops.
|
95
|
+
# @return [self]
|
96
|
+
#
|
97
|
+
def run
|
98
|
+
FunctionsFramework.logger.info \
|
99
|
+
"FunctionsFramework: Loading functions from #{@source.inspect}..."
|
100
|
+
load @source
|
101
|
+
server = ::FunctionsFramework.start @target do |config|
|
102
|
+
config.rack_env = @env
|
103
|
+
config.port = @port
|
104
|
+
config.bind_addr = @bind
|
105
|
+
config.show_error_details = @detailed_errors
|
106
|
+
config.min_threads = @min_threads
|
107
|
+
config.max_threads = @max_threads
|
108
|
+
end
|
109
|
+
server.wait_until_stopped
|
110
|
+
self
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Copyright 2020 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module FunctionsFramework
|
16
|
+
module CloudEvents
|
17
|
+
##
|
18
|
+
# A content handler for the binary mode.
|
19
|
+
# See https://github.com/cloudevents/spec/blob/master/http-protocol-binding.md
|
20
|
+
#
|
21
|
+
module BinaryContent
|
22
|
+
class << self
|
23
|
+
##
|
24
|
+
# Decode an event from the given Rack environment
|
25
|
+
#
|
26
|
+
# @param env [Hash] Rack environment hash
|
27
|
+
# @param content_type [FunctionsFramework::CloudEvents::ContentType]
|
28
|
+
# the content type from the Rack environment
|
29
|
+
# @return [FunctionsFramework::CloudEvents::Event]
|
30
|
+
#
|
31
|
+
def decode_rack_env env, content_type
|
32
|
+
data = env["rack.input"]&.read
|
33
|
+
spec_version = interpret_header env, "HTTP_CE_SPECVERSION"
|
34
|
+
raise "Unrecognized specversion: #{spec_version}" unless spec_version == "1.0"
|
35
|
+
Event.new \
|
36
|
+
id: interpret_header(env, "HTTP_CE_ID"),
|
37
|
+
source: interpret_header(env, "HTTP_CE_SOURCE"),
|
38
|
+
type: interpret_header(env, "HTTP_CE_TYPE"),
|
39
|
+
spec_version: spec_version,
|
40
|
+
data: data,
|
41
|
+
data_content_type: content_type,
|
42
|
+
data_schema: interpret_header(env, "HTTP_CE_DATASCHEMA"),
|
43
|
+
subject: interpret_header(env, "HTTP_CE_SUBJECT"),
|
44
|
+
time: interpret_header(env, "HTTP_CE_TIME")
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def interpret_header env, key
|
50
|
+
escaped_value = env[key]
|
51
|
+
return nil if escaped_value.nil?
|
52
|
+
escaped_value.gsub(/%([0-9a-fA-F]{2})/) do
|
53
|
+
[$1.to_i(16)].pack "C" # rubocop:disable Style/PerlBackrefs
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# Copyright 2020 Google LLC
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
module FunctionsFramework
|
16
|
+
module CloudEvents
|
17
|
+
##
|
18
|
+
# A parsed content-type header.
|
19
|
+
#
|
20
|
+
# This object represents the information contained in a Content-Type,
|
21
|
+
# obtained by parsing the header according to RFC 2045.
|
22
|
+
#
|
23
|
+
# Case-insensitive fields, such as media_type and subtype, are normalized
|
24
|
+
# to lower case.
|
25
|
+
#
|
26
|
+
class ContentType
|
27
|
+
##
|
28
|
+
# Parse the given header value
|
29
|
+
#
|
30
|
+
# @param string [String] Content-Type header value in RFC 2045 format
|
31
|
+
#
|
32
|
+
def initialize string
|
33
|
+
@string = string
|
34
|
+
# TODO: This handles simple cases but is not RFC-822 compliant.
|
35
|
+
sections = string.to_s.split ";"
|
36
|
+
media_type, subtype = sections.shift.split "/"
|
37
|
+
subtype_prefix, subtype_format = subtype.split "+"
|
38
|
+
@media_type = media_type.strip.downcase
|
39
|
+
@subtype = subtype.strip.downcase
|
40
|
+
@subtype_prefix = subtype_prefix.strip.downcase
|
41
|
+
@subtype_format = subtype_format&.strip&.downcase
|
42
|
+
@params = initialize_params sections
|
43
|
+
@canonical_string = "#{@media_type}/#{@subtype}" +
|
44
|
+
@params.map { |k, v| "; #{k}=#{v}" }.join
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# The original header content string
|
49
|
+
# @return [String]
|
50
|
+
#
|
51
|
+
attr_reader :string
|
52
|
+
alias to_s string
|
53
|
+
|
54
|
+
##
|
55
|
+
# A "canonical" header content string with spacing and capitalization
|
56
|
+
# normalized.
|
57
|
+
# @return [String]
|
58
|
+
#
|
59
|
+
attr_reader :canonical_string
|
60
|
+
|
61
|
+
##
|
62
|
+
# The media type.
|
63
|
+
# @return [String]
|
64
|
+
#
|
65
|
+
attr_reader :media_type
|
66
|
+
|
67
|
+
##
|
68
|
+
# The entire content subtype (which could include an extension delimited
|
69
|
+
# by a plus sign)
|
70
|
+
# @return [String]
|
71
|
+
#
|
72
|
+
attr_reader :subtype
|
73
|
+
|
74
|
+
##
|
75
|
+
# The portion of the content subtype before any plus sign.
|
76
|
+
# @return [String]
|
77
|
+
#
|
78
|
+
attr_reader :subtype_prefix
|
79
|
+
|
80
|
+
##
|
81
|
+
# The portion of the content subtype after any plus sign, or nil if there
|
82
|
+
# is no plus sign in the subtype.
|
83
|
+
# @return [String,nil]
|
84
|
+
#
|
85
|
+
attr_reader :subtype_format
|
86
|
+
|
87
|
+
##
|
88
|
+
# An array of parameters, each element as a two-element array of the
|
89
|
+
# parameter name and value.
|
90
|
+
# @return [Array<Array(String,String)>]
|
91
|
+
#
|
92
|
+
attr_reader :params
|
93
|
+
|
94
|
+
##
|
95
|
+
# An array of values for the given parameter name
|
96
|
+
# @param key [String]
|
97
|
+
# @return [Array<String>]
|
98
|
+
#
|
99
|
+
def param_values key
|
100
|
+
key = key.downcase
|
101
|
+
@params.inject([]) { |a, (k, v)| key == k ? a << v : a }
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# The first value of the "charset" parameter, or nil if there is no
|
106
|
+
# charset.
|
107
|
+
# @return [String,nil]
|
108
|
+
#
|
109
|
+
def charset
|
110
|
+
param_values("charset").first
|
111
|
+
end
|
112
|
+
|
113
|
+
## @private
|
114
|
+
def == other
|
115
|
+
other.is_a?(ContentType) && canonical_string == other.canonical_string
|
116
|
+
end
|
117
|
+
alias eql? ==
|
118
|
+
|
119
|
+
## @private
|
120
|
+
def hash
|
121
|
+
canonical_string.hash
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def initialize_params sections
|
127
|
+
params = sections.map do |s|
|
128
|
+
k, v = s.split "="
|
129
|
+
[k.strip.downcase, v.strip]
|
130
|
+
end
|
131
|
+
params.sort! do |(k1, v1), (k2, v2)|
|
132
|
+
a = k1 <=> k2
|
133
|
+
a.zero? ? v1 <=> v2 : a
|
134
|
+
end
|
135
|
+
params
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|