functions_framework 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|