diff --git a/.gitignore b/.gitignore index cb309cf..04f10f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ vendor build .idea +docs/_site .phpunit.cache composer.lock .php-cs-fixer.cache diff --git a/docs/1.0/documentation.md b/docs/1.0/documentation.md new file mode 100644 index 0000000..444398d --- /dev/null +++ b/docs/1.0/documentation.md @@ -0,0 +1,776 @@ +--- +layout: default +title: Documentation +--- + +# Documentation + +### Foreword + +

+While this package parses and serializes the HTTP value, it does not validate its content +against any conformance rule. You are still required to perform a compliance check +against the constraints of the corresponding field. Content validation is +out of scope for this library even though you can leverage some of its feature to +ease the required validation. +

+ +### Parsing and Serializing Structured Fields + +#### Basic Usage + +

new in version 1.2.0

+ +To quickly parse or serialize one of the five (5) available data type according to the RFC, you can use the `DataType` enum. +Apart from listing the data types (`List`, `InnerList`, `Parameters`, `Dictionary` and `Item`) you can give to +its `parse` method a string or a stringable object representing a field text representation. On success, +it will return an object representing the structured field otherwise an exception will be thrown. + +```php +$headerLine = 'bar;baz=42'; //the raw header line is a structured field item +$field = DataType::Item->parse($headerLine); +$field->value(); // returns Token::fromString('bar); the found token value +$field->parameter('baz'); // returns 42; the value of the parameter or null if the parameter is not defined. +``` + +To complement the behaviour, you can use its `serialize` method to turn an iterable structure +composed of pair values that matches any structured field data type and returns its +text representation. + +```php +use Bakame\Http\StructuredFields\Item; +use Bakame\Http\StructuredFields\DataType; + +echo DataType::List->serialize([ + [ + 'dumela lefatshe', + [['a', false]] + ], + [ + ['a', 'b', Item::fromDateString('+30 minutes')], + [['a', true]] + ], +]); +// display "dumela lefatshe";a=?0, ("a" "b" @1703319068);a +``` + +The `serialize` method is a shortcut to converting the iterable structure into a `StructuredField` via +the `create` method and calling on the newly created object its `toHttpValue` method. With that +in mind, it is possible to rewrite The last example: + +```php +use Bakame\Http\StructuredFields\Item; +use Bakame\Http\StructuredFields\DataType; + +$list = DataType::List->create([ + [ + 'dumela lefatshe', + [['a', false]] + ], + [ + ['a', 'b', Item::fromDateString('+30 minutes')], + [['a', true]] + ], +]); + +echo $list->toHttpValue(); +// display "dumela lefatshe";a=?0, ("a" "b" @1703319068);a +``` + +

+While the format can be overwhelming at first, you will come to understand it while reading +the rest of the documentation. Under the hood, the `DataType` enum uses the mechanism discussed hereafter. +

+ +#### Using specific data type classes + +The package provides specific classes for each data type. Parsing is done their respective +`fromHttpValue` named constructor. A example of how the method works can be seen below +using the `Item` class: + +```php +declare(strict_types=1); + +use Bakame\Http\StructuredFields\DataType; + +require 'vendor/autoload.php'; + +// the raw HTTP field value is given by your application +// via any given framework, package or super global. + +$headerLine = 'bar;baz=42'; //the raw header line is a structured field item +$field = Item::fromHttpValue($headerLine); +$field->value(); // returns Token::fromString('bar); the found token value +$field->parameter('baz'); // returns 42; the value of the parameter or null if the parameter is not defined. +``` + +

+The DataType::parse method uses the fromHttpValue named constructor for +each specific class to generate the structured field PHP representation. +

+ +The `fromHttpValue` method returns an instance which implements the `StructuredField` interface. +The interface provides the `toHttpValue` method that serializes it into a normalized RFC +compliant HTTP field string value. To ease integration, the `__toString` method is +implemented as an alias to the `toHttpValue` method. + +````php +$field = Item::fromHttpValue('bar; baz=42; secure=?1'); +echo $field->toHttpValue(); // return 'bar;baz=42;secure' +// on serialization the field has been normalized + +// the HTTP response is build by your application +// via any given framework, package or PHP native function. + +header('foo: '. $field->toHttpValue()); +//or +header('foo: '. $field); +```` + +

+This is the mechanism used by the DataType::serialize method. Once the Structured +field has been created, the method calls its toHttpValue method. +

+> + +All five (5) structured data type as defined in the RFC are provided inside the +`Bakame\Http\StructuredFields` namespace. They all implement the +`StructuredField` interface and expose a `fromHttpValue` named constructor: + +- `Item` +- `Parameters` +- `Dictionary` +- `OuterList` (named `List` in the RFC but renamed in the package because `list` is a reserved word in PHP.) +- `InnerList` + +### Accessing Structured Fields Values + +#### RFC Value type + +Per the RFC, items value can have different types that are translated to PHP using: + +- native type or classes where possible; +- specific classes defined in the package namespace to represent non-native type + +The table below summarizes the item value type. + +| RFC Type | PHP Type | Package Enum Name | Package Enum Value | +|-------------------|---------------------------|-----------------------|--------------------| +| Integer | `int` | `Type::Integer` | `ìnteger` | +| Decimal | `float` | `Type::Decimal` | `decimal` | +| String | `string` | `Type::String` | `string` | +| Boolean | `bool` | `Type::Boolean` | `boolean` | +| Token | class `Token` | `Type::Token` | `token` | +| Byte Sequence | class `ByteSequence` | `Type::ByteSequence` | `binary` | +| Date (*) | class `DateTimeImmutable` | `Type::Date` | `date` | +| DisplayString (*) | class `DisplayString` | `Type::DisplayString` | `displaystring` | + +

+The Date and DisplayString type are not yet part of any accepted +RFC. But they are already added as new types in the super-seeding +RFC proposal. +

+ +> +> See https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-sfbis +> for more information. + +The Enum `Type` list all available types and can be used to determine the RFC type +corresponding to a PHP structure using the `Type::fromVariable` static method. +The method will throw if the structure is not recognized. Alternatively +it is possible to use the `Type::tryFromVariable` which will instead +return `null` on unidentified type. On success both methods +return the corresponding enum `Type`. + +```php +use Bakame\Http\StructuredFields\Type; + +echo Type::fromVariable(42)->value; // returns 'integer' +echo Type::fromVariable(42.0)->name; // returns 'Decimal' +echo Type::fromVariable(new SplTempFileObject()); // throws InvalidArgument +echo Type::tryFromVariable(new SplTempFileObject()); // returns null +``` + +To ease validation a `Type::equals` method is exposed to check if the `Item` has +the expected type. It can also be used to compare types. + +```php +use Bakame\Http\StructuredFields\DataType; +use Bakame\Http\StructuredFields\Type; + +$field = DataType::Item->parse('"foo"'); +Type::Date->equals($field); // returns false +Type::String->equals($field); // returns true; +Type::Boolean->equals(Type::String); // returns false +``` + +The RFC defines three (3) specific data types that can not be represented by +PHP default type system, for them, we have defined three classes `Token`, +`ByteSequence` and `DisplayString` to help with their representation. + +```php +use Bakame\Http\StructuredFields\ByteSequence; +use Bakame\Http\StructuredFields\DisplayString; +use Bakame\Http\StructuredFields\Token; + +Token::fromString(string|Stringable $value): Token +ByteSequence::fromDecoded(string|Stringable $value): ByteSequence; +ByteSequence::fromEncoded(string|Stringable $value): ByteSequence; +DisplayString::fromDecoded(string|Stringable $value): DisplayString; +DisplayString::fromEncoded(string|Stringable $value): DisplayString; +``` + +All classes are final and immutable; their value can not be modified once +instantiated. To access their value, they expose the following API: + +```php +use Bakame\Http\StructuredFields\Token; +use Bakame\Http\StructuredFields\ByteSequence; +use Bakame\Http\StructuredFields\DisplayString; + +$token = Token::fromString('application/text+xml'); +echo $token->toString(); // returns 'application/text+xml' + +$byte = DisplayString::fromDecoded('füü'); +$byte->decoded(); // returns 'füü' +$byte->encoded(); // returns 'f%c3%bc%c3%bc' + +$displayString = ByteSequence::fromDecoded('Hello world!'); +$byte->decoded(); // returns 'Hello world!' +$byte->encoded(); // returns 'SGVsbG8gd29ybGQh' + +$token->equals($byte); // will return false; +$displayString->equals($byte); // will return false; +$byte->equals(ByteSequence::fromEncoded('SGVsbG8gd29ybGQh')); // will return true + +$token->type(); // returns Type::Token enum +$byte->type(); // returns Type::ByteSequence +$displayString->type(); // returns Type::DisplayString +``` + +

+The classes DO NOT expose the Stringable interface to help distinguish +them from a string or a stringable object +

+ +#### Item + +The defined types are all attached to an `Item` object where their value and +type are accessible using the following methods: + +```php +use Bakame\Http\StructuredFields\Item; +use Bakame\Http\StructuredFields\Type; + +$item = Item::fromHttpValue('@1234567890'); +$item->type(); // return Type::Date; +$item->value() // return the equivalent to DateTimeImmutable('@1234567890'); +``` + +#### Containers + +All containers objects implement PHP `IteratorAggregate`, `Countable` and `ArrayAccess` +interfaces. Their members can be accessed using the following shared methods + +```php +$container->keys(): array; +$container->has(string|int ...$offsets): bool; +$container->get(string|int $offset): StrucuredField; +$container->hasMembers(): bool; +$container->hasNoMembers(): bool; +``` + +

+The get method will throw an InvalidOffset exception if no member exists for the given $offset. +

+ + +To avoid invalid states, `ArrayAccess` modifying methods throw a `ForbiddenOperation` +if you try to use them on any container object: + +```php +use Bakame\Http\StructuredFields\Parameters; + +$value = Parameters::fromHttpValue(';a=foobar'); +$value->has('b'); // return false +$value['a']->value(); // return 'foobar' +$value['b']; // triggers a InvalidOffset exception, the index does not exist +$value['a'] = 23 // triggers a ForbiddenOperation exception +unset($value['a']); // triggers a ForbiddenOperation exception +``` + +The `Dictionary` and `Parameters` classes also allow accessing its members as pairs: + +```php +$container->hasPair(int ...$offsets): bool; +$container->pair(int $offset): array{0:string, 1:StructuredField}; +$container->toPairs(): iterable; +``` + +

+The pair method will throw an InvalidOffset exception if no member exists for the given $offset. +

+ +#### Accessing the parameters values + +Accessing the associated `Parameters` instance attached to an `InnerList` or a `Item` instances +is done using the following methods: + +```php +use Bakame\Http\StructuredFields\InnerList; +use Bakame\Http\StructuredFields\Item; +use Bakame\Http\StructuredFields\Parameters; + +$field->parameter(string $key): ByteSequence|Token|DisplayString|DateTimeImmutable|Stringable|string|int|float|bool|null; +$field->parameters(): Parameters; +$field->parameterByIndex(int $index): array{0:string, 1:ByteSequence|Token|DisplayString|DateTimeImmutable|Stringable|string|int|float|boo} +InnerList::toPair(): array{0:list, 1:Parameters}>}; +Item::toPair(): array{0:ByteSequence|Token|DisplayString|DateTimeImmutable|Stringable|string|int|float|bool, 1:Parameters}>}; +``` + +
    +
  • The parameter method will return null if no value is found for the given key.
  • +
  • The parameterByIndex method is added in version 1.1.0 and returns an empty array if no parameter is found for the given index.
  • +
+ +### Building and Updating Structured Fields Values + +Every value object can be used as a builder to create an HTTP field value. Because we are +using immutable value objects any change to the value object will return a new instance +with the changes applied and leave the original instance unchanged. + +#### Items value + +The `Item` value object exposes the following named constructors to instantiate +bare items (ie: item without parameters attached to them). + +```php +use Bakame\Http\StructuredFields\ByteSequence; +use Bakame\Http\StructuredFields\Item; +use Bakame\Http\StructuredFields\Token; + +Item:new(DateTimeInterface|ByteSequence|Token|DisplayString|string|int|array|float|bool $value): self +Item::fromDecodedByteSequence(Stringable|string $value): self; +Item::fromEncodedDisplayString(Stringable|string $value): self; +Item::fromDecodedDisplayString(Stringable|string $value): self; +Item::fromEncodedByteSequence(Stringable|string $value): self; +Item::fromToken(Stringable|string $value): self; +Item::fromString(Stringable|string $value): self; +Item::fromDate(DateTimeInterface $datetime): self; +Item::fromDateFormat(string $format, string $datetime): self; +Item::fromDateString(string $datetime, DateTimeZone|string|null $timezone = null): self; +Item::fromTimestamp(int $value): self; +Item::fromDecimal(int|float $value): self; +Item::fromInteger(int|float $value): self; +Item::true(): self; +Item::false(): self; +``` + +To update the `Item` instance value, use the `withValue` method: + +```php +use Bakame\Http\StructuredFields\Item; + +Item::withValue(DateTimeInterface|ByteSequence|Token|DisplayString|string|int|float|bool $value): static +``` + +#### Ordered Maps + +The `Dictionary` and `Parameters` are ordered map instances. They can be built using their keys with an associative iterable structure as shown below + +```php +use Bakame\Http\StructuredFields\Dictionary; + +$value = Dictionary::fromAssociative([ + 'b' => Item::false(), + 'a' => Item::fromToken('bar'), + 'c' => new DateTimeImmutable('2022-12-23 13:00:23'), +]); + +echo $value->toHttpValue(); //"b=?0, a=bar, c=@1671800423" +echo $value; //"b=?0, a=bar, c=@1671800423" +``` + +or using their indexes with an iterable structure of pairs (tuple) as defined in the RFC: + +```php +use Bakame\Http\StructuredFields\Parameters; +use Bakame\Http\StructuredFields\Item; + +$value = Parameters::fromPairs(new ArrayIterator([ + ['b', Item::false()], + ['a', Item::fromToken('bar')], + ['c', new DateTime('2022-12-23 13:00:23')] +])); + +echo $value->toHttpValue(); //;b=?0;a=bar;c=@1671800423 +echo $value; //;b=?0;a=bar;c=@1671800423 +``` + +If the preference is to use the builder pattern, the same result can be achieved with the +following steps: + +- First create a `Parameters` or a `Dictionary` instance using the `new` named constructor which + returns a new instance with no members. +- And then, use any of the following modifying methods to populate it. + +```php +$map->add(string $key, $value): static; +$map->append(string $key, $value): static; +$map->prepend(string $key, $value): static; +$map->mergeAssociative(...$others): static; +$map->mergePairs(...$others): static; +$map->remove(string|int ...$key): static; +``` + +As shown below: +` +```php +use Bakame\Http\StructuredFields\Dictionary; +use Bakame\Http\StructuredFields\Item; +use Bakame\Http\StructuredFields\Token; + +$value = Dictionary::new() + ->add('a', InnerList::new( + Item::fromToken('bar'), + Item::fromString('42'), + Item::fromInteger(42), + Item::fromDecimal(42) + )) + ->prepend('b', Item::false()) + ->append('c', Item::fromDateString('2022-12-23 13:00:23')) +; + +echo $value->toHttpValue(); //b=?0, a=(bar "42" 42 42.0), c=@1671800423 +echo $value; //b=?0, a=(bar "42" 42 42.0), c=@1671800423 +``` + +Since version `1.1.0` it is possible to also build `Dictionary` and `Parameters` instances +using indexes and pair as per described in the RFC. + +The `$pair` parameter is a tuple (ie: an array as list with exactly two members) where: + +- the first array member is the parameter `$key` +- the second array member is the parameter `$value` + +```php +// since version 1.1 +$map->unshift(array ...$pairs): static; +$map->push(array ...$pairs): static; +$map->insert(int $key, array ...$pairs): static; +$map->replace(int $key, array $pair): static; +$map->removeByKeys(string ...$keys): static; +$map->removeByIndices(int ...$indices): static; +``` + +We can rewrite the previous example + +```php +use Bakame\Http\StructuredFields\Dictionary; +use Bakame\Http\StructuredFields\Item; +use Bakame\Http\StructuredFields\Token; + +$value = Dictionary::new() + ->push( + ['a', InnerList::new( + Item::fromToken('bar'), + Item::fromString('42'), + Item::fromInteger(42), + Item::fromDecimal(42) + )], + ['c', Item::true()] + ) + ->unshift(['b', Item::false()]) + ->replace(2, ['c', Item::fromDateString('2022-12-23 13:00:23')]) +; + +echo $value->toHttpValue(); //b=?0, a=(bar "42" 42 42.0), c=@1671800423 +echo $value; //b=?0, a=(bar "42" 42 42.0), c=@1671800423 +``` + +

on duplicate `keys` pair values are merged as per RFC logic.

+ +The `remove` always accepted string or integer as input. Since version `1.1` the method is fixed to +remove the corresponding pair if its index is given to the method. + +```diff +remove('b', 2)->toHttpValue(); // returns a=(bar "42" 42 42.0), c=@1671800423 ++ echo $field->remove('b', 2)->toHttpValue(); // returns a=(bar "42" 42 42.0) +``` + +If a stricter approach is needed, use the following new methods `removeByIndices` and/or `removeByKeys`: + +```php +use Bakame\Http\StructuredFields\Parameters; + +$field = Parameters::fromHttpValue(';expire=@1681504328;path="/";max-age=2500;secure;httponly=?0;samesite=lax'); +echo $field->removeByIndices(4, 2, 0)->toHttpValue(); // returns ;path="/";secure;samesite=lax +echo $field->removeByKeys('expire', 'httponly', 'max-age')->toHttpValue(); // returns ;path="/";secure;samesite=lax +``` + +#### Automatic conversion + +For all containers, to ease instantiation the following automatic conversion are applied on +the member argument of each modifying methods. + +If the submitted type is: + +- a `StructuredField` implementing object, it will be passed as is +- an iterable structure, it will be converted to an `InnerList` instance using `InnerList::new` +- otherwise, it is converted into an `Item` using the `Item::new` named constructor. + +If no conversion is possible an `InvalidArgument` exception will be thrown. + +This means that both constructs below built equal objects + +```php +use Bakame\Http\StructuredFields\Dictionary; +use Bakame\Http\StructuredFields\Item; +use Bakame\Http\StructuredFields\Token; + +echo Dictionary::new() + ->add('a', InnerList::new( + Item::fromToken('bar'), + Item::fromString('42'), + Item::fromInteger(42), + Item::fromDecimal(42) + )) + ->prepend('b', Item::false()) + ->append('c', Item::fromDateString('2022-12-23 13:00:23')) + ->toHttpValue() +; + +echo Dictionary::new() + ->add('a', [Token::fromString('bar'), '42', 42, 42.0]) + ->prepend('b', false) + ->append('c', new DateTimeImmutable('2022-12-23 13:00:23')) + ->toHttpValue() +; + + // both will return 'b=?0, a=(bar "42" 42 42.0), c=@1671800423 +``` + +Of course, it is possible to mix both notations. + +#### Lists + +To create `OuterList` and `InnerList` instances you can use the `new` named constructor +which takes a single variadic parameter `$members`: + +```php +use Bakame\Http\StructuredFields\InnerList; +use Bakame\Http\StructuredFields\ByteSequence; + +$list = InnerList::new( + ByteSequence::fromDecoded('Hello World'), + 42.0, + 42 +); + +echo $list->toHttpValue(); //'(:SGVsbG8gV29ybGQ=: 42.0 42)' +echo $list; //'(:SGVsbG8gV29ybGQ=: 42.0 42)' +``` + +Once again, the builder pattern can be used via a combination of the `new` +named constructor and the use any of the following modifying methods. + +```php +$list->unshift(...$members): static; +$list->push(...$members): static; +$list->insert(int $key, ...$members): static; +$list->replace(int $key, $member): static; +$list->remove(int ...$key): static; +``` + +as shown below + +```php +use Bakame\Http\StructuredFields\ByteSequence; +use Bakame\Http\StructuredFields\InnerList; + +$list = InnerList::new() + ->unshift('42') + ->push(42) + ->insert(1, 42.0) + ->replace(0, ByteSequence::fromDecoded('Hello World')); + +echo $list->toHttpValue(); //'(:SGVsbG8gV29ybGQ=: 42.0 42)' +echo $list; //'(:SGVsbG8gV29ybGQ=: 42.0 42)' +``` + +

New in version 1.2.0

+ +It is also possible to create an `OuterList` based on an iterable structure +of pairs. + +```php +use Bakame\Http\StructuredFields\OuterList; + +$list = OuterList::fromPairs([ + [ + ['foo', 'bar'], + [ + ['expire', new DateTime('2024-01-01 12:33:45')], + ['path', '/'], + [ 'max-age', 2500], + ['secure', true], + ['httponly', true], + ['samesite', Token::fromString('lax')], + ] + ], + [ + 'coucoulesamis', + [['a', false]], + ] +]); +``` + +The pairs definitions are the same as for creating either a `InnerList` or an `Item` using +their respective `fromPair` method. + +#### Adding and updating parameters + +To ease working with instances that have a `Parameters` object attached to, the following +methods are added: + +```php +use Bakame\Http\StructuredFields\ByteSequence; +use Bakame\Http\StructuredFields\InnerList; +use Bakame\Http\StructuredFields\Item; +use Bakame\Http\StructuredFields\Token; + +//@type SfItemInput ByteSequence|Token|DateTimeInterface|string|int|float|bool + +Item::fromAssociative(SfItemInput $value, Parameters|iterable $parameters): self; +Item::fromPair(array{0:SfItemInput, 1:Parameters|iterable} $pair): self; + +InnerList::fromAssociative(iterable $value, Parameters|iterable $parameters): self; +InnerList::fromPair(array{0:iterable, Parameters|iterable} $pair): self; +``` + +The following example illustrate how to use those methods: + +```php +use Bakame\Http\StructuredFields\Dictionary; +use Bakame\Http\StructuredFields\Item; + +echo Item::fromAssociative( + Token::fromString('bar'), + ['baz' => 42] + )->toHttpValue(), PHP_EOL; + +echo Item::fromPair([ + Token::fromString('bar'), + [['baz', 42]], + ])->toHttpValue(), PHP_EOL; + +//both methods return `bar;baz=42` +``` + +Both objects provide additional modifying methods to help deal with parameters. +You can attach and update the associated `Parameters` instance using the following methods. + +```php +$field->addParameter(string $key, mixed $value): static; +$field->appendParameter(string $key, mixed $value): static; +$field->prependParameter(string $key, mixed $value): static; +$field->withoutParameters(string ...$keys): static; // this method is deprecated as of version 1.1 use withoutParametersByKeys instead +$field->withoutAnyParameter(): static; +$field->withParameters(Parameters $parameters): static; +``` +Since version `1.1` it is also possible to use the index of each member to perform additional +modifications. + +```php +$field->pushParameters(array ...$pairs): static +$field->unshiftParameters(array ...$pairs): static +$field->insertParameters(int $index, array ...$pairs): static +$field->replaceParameter(int $index, array $pair): static +$field->withoutParametersByKeys(string ...$keys): static +$field->withoutParametersByIndices(int ...$indices): static +``` + +The `$pair` parameter is a tuple (ie: an array as list with exactly two members) where: + +- the first array member is the parameter `$key` +- the second array member is the parameter `$value` + +

The return value will be the parent class an NOT a Parameters instance

+ +```php +use Bakame\Http\StructuredFields\InnerList; +use Bakame\Http\StructuredFields\Item; + +echo InnerList::new('foo', 'bar') + ->addParameter('expire', Item::fromDateString('+30 minutes')) + ->addParameter('path', '/') + ->addParameter('max-age', 2500) + ->toHttpValue(); + +echo InnerList::new('foo', 'bar') + ->pushParameter( + ['expire', Item::fromDateString('+30 minutes')], + ['path', '/'], + ['max-age', 2500], + ) + ->toHttpValue(); + +// both flow return the InnerList HTTP value +// ("foo" "bar");expire=@1681538756;path="/";max-age=2500 +``` + +### Advance parsing usage + +Starting with version `1.1` the internal parser has been made public in order to allow: + +- clearer decoupling between parsing and objet building +- different parsers implementations +- improve the package usage in testing. + +Each `fromHttpValue` method signature has been updated to take a second optional argument +that represents the parser interface to use in order to allow parsing of the HTTP string +representation value. + +By default, if no parser is provided, the package will default to use the package `Parser` class, + +```php +Item::fromHttpValue(Stringable|string $httpValue, ItemParser $parser = new Parser()): Item; +InnerList::fromHttpValue(Stringable|string $httpValue, InnerListParser $parser = new Parser()): InnerList; +Dictionary::fromHttpValue(Stringable|string $httpValue, DictionaryParser $parser = new Parser()): Dictionary; +OuterList::fromHttpValue(Stringable|string $httpValue, ListParser $parser = new Parser()): OuterList; +Parameters::fromHttpValue(Stringable|string $httpValue, ParametersParser $parser = new Parser()): Parameters; +``` + +The `Parser` class exposes the following method each belonging to a different contract or interface. + +```php +Parser::parseValue(Stringable|string $httpValue): ByteSequence|Token|DateTimeImmutable|string|int|float|bool; +Parser::parseItem(Stringable|string $httpValue): array; +Parser::parseParameters(Stringable|string $httpValue): array; +Parser::parseInnerList(Stringable|string $httpValue): array; +Parser::parseList(Stringable|string $httpValue): array; +Parser::parseDictionary(Stringable|string $httpValue): array; +``` + +Once instantiated, calling one of the above listed method is straightforward: + +```php +use Bakame\Http\StructuredFields\Parser; + +$parser = new Parser(); +$parser->parseValue('text/csv'); //returns Token::fromString('text/csv') +$parser->parseItem('@1234567890;file=24'); +//returns an array +// [ +// new DateTimeImmutable('@1234567890'), +// ['file' => 24], +// ] +``` + +

While the provided default Parser class implements all these methods you are +free to only implement the methods you need.

diff --git a/docs/1.0/index.md b/docs/1.0/index.md new file mode 100644 index 0000000..e35644f --- /dev/null +++ b/docs/1.0/index.md @@ -0,0 +1,52 @@ +--- +layout: default +title: Overview +--- + +# Overview + +`bakame/http-structured-fields` is a framework-agnostic PHP library that allows you to parse, serialize +create and update HTTP Structured Fields in PHP according to the [RFC8941](https://www.rfc-editor.org/rfc/rfc8941.html). + +Once installed you will be able to do the following: + +```php +use Bakame\Http\StructuredFields\DataType; +use Bakame\Http\StructuredFields\Token; + +//1 - parsing an Accept Header +$fieldValue = 'text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8'; +$field = DataType::List->parse($fieldValue); +$field[2]->value()->toString(); // returns 'application/xml' +$field[2]->parameter('q'); // returns (float) 0.9 +$field[0]->value()->toString(); // returns 'text/html' +$field[0]->parameter('q'); // returns null + +//2 - building a retrofit Cookie Header +echo DataType::List->serialize([ + [ + ['foo', 'bar'], + [ + ['expire', new DateTimeImmutable('2023-04-14 20:32:08')], + ['path', '/'], + [ 'max-age', 2500], + ['secure', true], + ['httponly', true], + ['samesite', Token::fromString('lax')], + ] + ], +]); +// returns ("foo" "bar");expire=@1681504328;path="/";max-age=2500;secure;httponly=?0;samesite=lax +``` + +## System Requirements + +**PHP >= 8.1** is required but the latest stable version of PHP is recommended. + +## Installation + +Use composer: + +``` +composer require bakame/http-structured-fields +``` diff --git a/docs/containers.md b/docs/2.0/containers.md similarity index 90% rename from docs/containers.md rename to docs/2.0/containers.md index 9fdfa78..bc1c663 100644 --- a/docs/containers.md +++ b/docs/2.0/containers.md @@ -1,4 +1,5 @@ --- +layout: default title: The Structured Field containers Data Types order: 5 --- @@ -29,8 +30,10 @@ $permissions->isNotEmpty():; // returns true $permissions->isEmpty(); // returns false ``` -> [!IMPORTANT] -> The `getByIndex` method will throw an `InvalidOffset` exception if no member exists for the given `$offset`. +

+The getByIndex method will throw an InvalidOffset exception if no +member exists for the given $offset. +

Here's an example with a `List` container: @@ -46,10 +49,11 @@ $permissions->isNotEmpty():; // returns true $permissions->isEmpty(); // returns false ``` -> [!IMPORTANT] -> For ordered maps, the `getByIndex` method returns a list containing exactly 2 entries. -> The first entry is the member key, the second entry is the member value. -> For lists, the method directly returns the value. +

+For ordered maps, the getByIndex method returns a list containing exactly 2 entries. +The first entry is the member key, the second entry is the member value. For lists, the method +directly returns the value. +

To avoid invalid states, `ArrayAccess` modifying methods throw a `ForbiddenOperation` if you try to use them on any container object: @@ -61,9 +65,10 @@ $permissions['a'] = 23 // triggers a ForbiddenOperation exception unset($permissions['a']); // triggers a ForbiddenOperation exception ``` -> [!IMPORTANT] -> For ordered map the ArrayAccess interface will use the member name -> whereas for lists the interface will use the member index. +

+For ordered map the ArrayAccess interface will use the member name +whereas for lists the interface will use the member index. +

The `Dictionary` and `Parameters` classes also allow accessing their members as value using their name: @@ -83,12 +88,16 @@ $permissions->indexByKey('foobar'): // returns null because there's no member wi $permissions->indexByKey('geolocation'): // returns 1 ``` -> [!IMPORTANT] -> The `getByKey` method will throw an `InvalidOffset` exception if no member exists for the given `$offset`. +

+The getByKey method will throw an InvalidOffset exception if no +member exists for the given $offset. +

-> [!TIP] -> The `ArrayAccess` interface proxy the result from `getByIndex` and `hasIndices` with `OuterList` and `InnerList`. -> The `ArrayAccess` interface proxy the result from `getByNKey` and `hasKeys` with `Dictionary` and `Parameters`. + +
    +
  • The ArrayAccess interface proxy the result from getByIndex and hasIndices with OuterList and InnerList.
  • +
  • The ArrayAccess interface proxy the result from getByKey and hasKeys with Dictionary and Parameters.
  • +
### Accessing the parameters values @@ -99,9 +108,10 @@ On the other hand if you already have a `Parameters` instance you can use the `valueByKey` and `valueByIndex` methods to directly access the value from a single parameter. -> [!TIP] -> The `parameterByKey` proxy the result from `valueByKey`. -> The `parameterByIndex` proxy the result from `valueByIndex`. +
    +
  • The parameterByKey proxy the result from valuerByKey.
  • +
  • The parameterByIndex proxy the result from valuerByIndex.
  • +
## Building and Updating Containers @@ -220,8 +230,8 @@ echo $value->toHttpValue(); //b=?0, a=(bar "42" 42 42.0), c=@1671800423 echo $value; //b=?0, a=(bar "42" 42 42.0), c=@1671800423 ``` -> [!CAUTION] -> on duplicate `names` pair values are merged as per RFC logic. +

on duplicate names pair values are merged as per RFC logic.

+ The following methods `removeByIndices` and/or `removeByKeys` allow removing members per indices or per names. @@ -413,8 +423,7 @@ The `$pair` parameter is an array as list with exactly two members where: - the first array member is the parameter `$key` - the second array member is the parameter `$value` -> [!WARNING] -> The return value will be the parent class an NOT a `Parameters` instance +

The return value will be the parent class an NOT a Parameters` instance

```php use Bakame\Http\StructuredFields\InnerList; @@ -455,5 +464,3 @@ Item::new($cache->name) ->when(null !== $cache->key, fn (Item $item) => $item->appendParameter('key', $cache->key)) ->when(null !== $cache->detail, fn (Item $item) => $item->appendParameter('detail', $cache->detail)); ``` - -← [Accessing Field Values](field-values.md) | [Validation](validation.md) → diff --git a/docs/extensions.md b/docs/2.0/extensions.md similarity index 97% rename from docs/extensions.md rename to docs/2.0/extensions.md index b488109..31e37cc 100644 --- a/docs/extensions.md +++ b/docs/2.0/extensions.md @@ -1,4 +1,5 @@ --- +layout: default title: Interacting with the PHP ecosystem order: 7 --- @@ -100,5 +101,3 @@ contract. To show how this can be achieved you can check the codebase from [HTTP Cache Status](https://github.com/bakame-php/http-cache-status) which uses the interface. Of note by using this interface you can completely hide the presence of this package to your end users if needed. - -← [Validation](validation.md) | [Upgrading to v2.0](migration.md) → diff --git a/docs/field-values.md b/docs/2.0/field-values.md similarity index 90% rename from docs/field-values.md rename to docs/2.0/field-values.md index f458cb9..9e96b5e 100644 --- a/docs/field-values.md +++ b/docs/2.0/field-values.md @@ -1,4 +1,5 @@ --- +layout: default title: Accessing the HTTP field values order: 4 --- @@ -21,10 +22,11 @@ or provides a class based alternative. The table below summarizes the value type | Date | class `DateTimeImmutable` | `Type::Date` | `date` | RFC9651 | | DisplayString | class `DisplayString` | `Type::DisplayString` | `displaystring` | RFC9651 | -> [!WARNING] -> The translation to PHP native type does not mean that all PHP values are usable. For instance, in the -> following example, what is considered to be a valid string in PHP is not considered as compliant -> to the string type according to the RFC. +

+The translation to PHP native type does not mean that all PHP values are usable. For instance, in the +following example, what is considered to be a valid string in PHP is not considered as compliant +to the string type according to the RFC. +

```php Item::fromString("https://a.bébé.com"); @@ -33,9 +35,10 @@ Item::fromString("https://a.bébé.com"); // contain UTF-8 characters ``` -> [!NOTE] -> The `Date` and `DisplayString` types were added in the accepted RFC9651 -> but are not part of the obsolete RFC8941 specification. +

+The Date and DisplayString types were added in the accepted RFC9651 +but are not part of the obsolete RFC8941 specification. +

The Enum `Type` list all available types and can be used to determine the RFC type corresponding to a PHP structure using the `Type::fromVariable` static method. @@ -113,13 +116,11 @@ $byte->type(); // returns Type::Byte $displayString->type(); // returns Type::DisplayString ``` -> [!WARNING] -> The classes DO NOT expose the `Stringable` interface to help distinguish -> them from the string type or a stringable object +

The classes DO NOT expose the Stringable interface to help distinguish +them from the string type or a stringable object +

-> [!IMPORTANT] -> Values are not directly accessible. They can only be retrieved from an Item -> Data type. +

Values are not directly accessible. They can only be retrieved from an Item Data type.

## The Item Data Type @@ -195,5 +196,3 @@ By default, you can access the member `Item` of a parameters using the following It is possible to alter and modify the `Parameters` attached to an `Item` but this will be explored in the next section about the containers. - -← [Parsing and Serializing](parsing-serializing.md) | [Containers](containers.md) → diff --git a/docs/2.0/index.md b/docs/2.0/index.md new file mode 100644 index 0000000..727c2a0 --- /dev/null +++ b/docs/2.0/index.md @@ -0,0 +1,28 @@ +--- +layout: default +title: Installation +--- + +# Installation + +## System Requirements + +**PHP >= 8.1** is required but the latest stable version of PHP is recommended. + +## Installation + +Use composer: + +``` +composer require bakame/http-structured-fields:^2.0 +``` + +## Foreword + +

+While this package parses and serializes HTTP field value, it does not validate its content +against any conformance rule out of the box. You are still required to perform such a +compliance check against the constraints of the corresponding field. While Content +validation is still possible and highly encouraged when using this library. Because +of the wide variety of HTTP fields it can not be made mandatory. +

diff --git a/docs/parsing-serializing.md b/docs/2.0/parsing-serializing.md similarity index 98% rename from docs/parsing-serializing.md rename to docs/2.0/parsing-serializing.md index 01dc9ab..2d773fc 100644 --- a/docs/parsing-serializing.md +++ b/docs/2.0/parsing-serializing.md @@ -1,4 +1,5 @@ --- +layout: default title: Parsing and Serializing HTTP Fields order: 3 --- @@ -164,5 +165,3 @@ The `toHttpValue` method applies by default all the normalizations recommended b > [!TIP] > This is the mechanism used by the `DataType::serialize` method. Once the HTTP Structured > Field has been created, the method calls its `toHttpValue` method. - -← [Basic Usage](basic-usage.md) | [Accessing Field Values](field-values.md) → diff --git a/docs/basic-usage.md b/docs/2.0/rfc-usage.md similarity index 85% rename from docs/basic-usage.md rename to docs/2.0/rfc-usage.md index 97bc02f..974f5fb 100644 --- a/docs/basic-usage.md +++ b/docs/2.0/rfc-usage.md @@ -1,11 +1,11 @@ --- -title: Basic usage -order: 2 +layout: default +title: RFC Usage --- -# Basic Usage +# RFC Usage -## Parsing the Field +## Parsing a field The first way to use the package is to enable HTTP header or HTTP trailer parsing. We will refer to them as HTTP fields for the rest of the documentation as it is how they are named in the IETF RFC. @@ -38,13 +38,12 @@ $permissions->isEmpty(); // returns false the dictionary c echo $permissions; // returns 'picture-in-picture=(), geolocation=(self "https://example.com/"), camera=*' ``` -> [!WARNING] -> If parsing fails a `SyntaxError` exception is thrown with the information about why it failed. +

If parsing fails a SyntaxError exception is thrown with the information about why it failed.

-## Creating a new field +## Serializing a field Conversely, if you need to quickly create a permission policy HTTP field text representation, the package -provides the following ways to do so: +provides the following ways to do by strictly following the RFC: ```php echo DataType::Dictionary->serialize([ @@ -60,10 +59,9 @@ pairs to respect value position. As such we can turn the iterable construct we h proper HTTP field text representation by applying the serialization mechanism described in the RFC. -While field building may look overwhelming, at first, it follows a fully described and tested -process that the package can simplify for you once you read the documentation. +While field building may look overwhelming, at first, it follows the fully described and tested +process from the RFC. The package exposes numerous methods to simplify this process for you. The goal of the examples are to show that even without dwelling too much into the ins and out -of the package you can easily and quickly parse or serialize compliant fields in PHP. - -← [Intro](index.md) | [Parsing and Serializing](parsing-serializing.md) → +of the package you can easily and quickly parse or serialize compliant fields in PHP base solely +on the RFC. diff --git a/docs/migration.md b/docs/2.0/upgrading.md similarity index 98% rename from docs/migration.md rename to docs/2.0/upgrading.md index 31e1473..91e1586 100644 --- a/docs/migration.md +++ b/docs/2.0/upgrading.md @@ -1,4 +1,5 @@ --- +layout: default title: Migrating from version 1.x order: 8 --- @@ -149,5 +150,3 @@ OuterList::fromPairs([ > [!NOTE] > The v1 syntax is still supported. - -← [Extending the package functionalities](extensions.md) | [Intro](index.md) → diff --git a/docs/validation.md b/docs/2.0/validation.md similarity index 88% rename from docs/validation.md rename to docs/2.0/validation.md index 054d722..13d1362 100644 --- a/docs/validation.md +++ b/docs/2.0/validation.md @@ -1,4 +1,5 @@ --- +layout: default title: Structured Field validation order: 6 --- @@ -37,8 +38,7 @@ $field->last()->value(); // returns 12.8 So far so good, the field is successfully parsed by the package. -> [!NOTE] -> The field would fail parsing with the obsolete RFC8941 definition +

The field would fail parsing with the obsolete RFC8941 definition.

### Validating each entry separately @@ -131,9 +131,10 @@ $value = $member // new Violation("The value 'foo' failed the RFC validation."); ``` -> [!NOTE] -> we used `mixed` as parameter type for convenience but the effective parameter type should be -> `Byte|Token|DisplayString|DateTimeImmutable|string|int|float|bool` +

+we used mixed as parameter type for convenience but the effective parameter type should be +Byte|Token|DisplayString|DateTimeImmutable|string|int|float|bool +

### Validating the Item parameters. @@ -155,11 +156,8 @@ if (!$member->parameters()->allowedKeys(['location', 'longitude', 'latitude', 'd } ``` -> [!TIP] -> The `Dictionary` class also exposes an `allowedKeys` method which behave the same way. - -> [!WARNING] -> if the parameters container is empty no error will be triggered +

The Dictionary class also exposes an allowedKeys method which behave the same way.

+

if the parameters container is empty no error will be triggered

### Validating single parameters @@ -182,10 +180,11 @@ $member->parameterByKey( ); ``` -> [!NOTE] -> `parameterByIndex` uses the same parameter only the callback parameter are -> different as a second parameter the string name is added to the callback -> for validation purpose. +

+parameterByIndex uses the same parameter only the callback parameter are +different as a second parameter the string name is added to the callback +for validation purpose. +

### Validating the complete Parameter container @@ -322,12 +321,14 @@ if ($validation->isSucces()) { } ``` -> [!NOTE] -> If we only had validated the `longitude` parameter. it would have been -> the only one present in the returned data. +

+If we only had validated the `longitude` parameter. it would have been +the only one present in the returned data. +

-> [!NOTE] -> If we only use the `filterByCriteria` method the full parameter data is returned. +

+If we only use the `filterByCriteria` method the full parameter data is returned. +

A `filterByIndices` method exists and behave exactly as the `filterByKeys` method. There are two differences when it is used: @@ -345,9 +346,11 @@ if ($validation->isSucces()) { } ``` -> [!IMPORTANT] -> Both methods are mutually exclusive if you use them both, the last one used will -> be the one which format the returned data. +

+Both methods are mutually exclusive if you use them both, the last one used will +be the one which format the returned data. +

+ ### Validating the full Item @@ -417,19 +420,21 @@ The `ParametersValidator` and the `ItemValidator` are invokable, so we can use t directly with the `getBy*` methods. So once you have configured your validator in a class it becomes easier to reuse it to validate your data. -> [!NOTE] -> When used as invokable the validators return `true` on success and the aggregated -> error messages as string on error. +

+When used as invokable the validators return true on success and the aggregated +error messages as string on error. +

-> [!TIP] -> A best practice is to move all this validation definition in its own class, and use that -> class instead to ease maintenance and testing. +

+A best practice is to move all this validation definition in its own class, and use that +class instead to ease maintenance and testing. +

-> [!TIP] -> Once you have a specific class to validate a single entry for your list or dictionary it is -> easy to validate all the container using either the `map`, `filter` or the `reduce` method -> associated with each container. +

+Once you have a specific class to validate a single entry for your list or dictionary it is +easy to validate all the container using either the map, filter +or the recude method associated with each container. +

-To show how this can be achieved you can check the codebase from [HTTP Cache Status](https://github.com/bakame-php/http-cache-status) -← [Containers](containers.md) | [Extending the package functionalities](extensions.md) → +To show how this can be achieved you can check the codebase from [HTTP Cache Status](https://github.com/bakame-php/http-cache-status) diff --git a/docs/Gemfile b/docs/Gemfile deleted file mode 100644 index 719d7fd..0000000 --- a/docs/Gemfile +++ /dev/null @@ -1,8 +0,0 @@ -source 'https://rubygems.org' - -group :jekyll_plugins do - gem "jekyll-gfm-admonitions" - gem "kramdown-parser-gfm" - gem "minima" - gem "jekyll-seo-tag" -end diff --git a/docs/_config.yml b/docs/_config.yml index dd8eab5..db458d1 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,5 +1,5 @@ --- permalink: pretty +baseurl: /http-structured-fields plugins: - - jekyll-gfm-admonitions - - jekyll-seo-tag + - jekyll-redirect-from diff --git a/docs/_data/manifest.yml b/docs/_data/manifest.yml new file mode 100644 index 0000000..8a767b9 --- /dev/null +++ b/docs/_data/manifest.yml @@ -0,0 +1,4 @@ +{ + "docs.css": "/styles.0004.css", + "docs.js": "/scripts.0002.js" +} diff --git a/docs/_data/menu.yml b/docs/_data/menu.yml new file mode 100644 index 0000000..3eb7eac --- /dev/null +++ b/docs/_data/menu.yml @@ -0,0 +1,20 @@ +version: + '2.0': + Getting Started: + Installation: '/2.0/' + Upgrading from 1.x: '/2.0/upgrading/' + Documentation: + RFC Usage: '/2.0/rfc-usage/' + Parsing & Serializing: '/2.0/parsing-serializing/' + Field Values: '/2.0/field-values/' + Containers: '/2.0/containers/' + Validation: '/2.0/validation/' + Extending: '/2.0/extensions/' + '1.0': + Getting Started: + Overview: '/1.0/' + Documentation: '/1.0/documentation/' +upgrading: + 'Upgrading guide': + Release Notes: '/releases/' + From 1.x to 2.x: '/2.0/upgrading/' diff --git a/docs/_data/project.yml b/docs/_data/project.yml new file mode 100644 index 0000000..94dc5f7 --- /dev/null +++ b/docs/_data/project.yml @@ -0,0 +1,10 @@ +# Documentation website +title: HTTP Structured Fields +tagline: "Work with HTTP Structured Fields in PHP." +description: "A framework-agnostic PHP library that allows you to parse, serialize, create, update and validate HTTP Structured Fields in PHP following RFC9651 and RFC8941." +# Github repository +package: 'bakame/http-structured-fields' +repository_url: 'https://github.com/bakame-php/http-structured-fields' +release_major: '2.0' +version: 2.0.0 +support: 'Once a new major version is released, the previous stable release remains supported for six more months with patches and security fixes.' diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html new file mode 100644 index 0000000..1eb650a --- /dev/null +++ b/docs/_layouts/default.html @@ -0,0 +1,129 @@ +{% assign version = page.url | remove_first: "/" | split: "/" | first %} +{% assign upgrading = false %} +{% if version == '' or version == 'releases' %} + {% assign version = site.data.project.releases.current.version %} + {% assign upgrading = true %} +{% endif %} +{% capture version_home %}/{{ version }}/{% endcapture %} + + + + + + + + + {{ page.title }} - {{ site.data.project.title }} + + + + + + + +
+
+ +
+
+ + + +
+
+ {{ content }} +
+
+
+ +
+ + diff --git a/docs/_layouts/homepage.html b/docs/_layouts/homepage.html new file mode 100644 index 0000000..4d50201 --- /dev/null +++ b/docs/_layouts/homepage.html @@ -0,0 +1,97 @@ + + + + + + + + {{ site.data.project.tagline }} - {{ site.data.project.title }} + + + + + + +
+
+ +
+ +
+
+

HTTP Structured Fields

+

Work with HTTP Structured Fields in PHP.

+ +
+ +
+ composer require {{ site.data.project.package }}:^{{ site.data.project.version }} +
+
+
+ +
+
Love this package ? Sponsor its development
+
+ +
+
!
+

Once a new major version is released, the previous stable release remains supported for six more months with patches and security fixes.

+
+ +
+ {{ content }} +
+
+
+ + +
+ + + diff --git a/docs/_layouts/redirect.html b/docs/_layouts/redirect.html new file mode 100644 index 0000000..0427457 --- /dev/null +++ b/docs/_layouts/redirect.html @@ -0,0 +1,80 @@ + + + + + + + + + + {{ site.data.project.tagline }} - {{ site.data.project.title }} + + + + +
+
+

{{ site.data.project.title }}

+

{{ site.data.project.tagline }}

+

{{ site.data.project.composer }}

+
+
+ +
+
+
+

Redirecting…

+
+ Click here if you are not redirected. + +
+
+
+
+
+ {{ content }} +
+
+
+ + + diff --git a/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Bold.ttf b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Bold.ttf new file mode 100644 index 0000000..2e437e2 Binary files /dev/null and b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Bold.ttf differ diff --git a/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-BoldItalic.ttf b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-BoldItalic.ttf new file mode 100644 index 0000000..f2695fc Binary files /dev/null and b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-BoldItalic.ttf differ diff --git a/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ExtraLight.ttf b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ExtraLight.ttf new file mode 100644 index 0000000..573ef76 Binary files /dev/null and b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ExtraLight.ttf differ diff --git a/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ExtraLightItalic.ttf b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ExtraLightItalic.ttf new file mode 100644 index 0000000..ea13f86 Binary files /dev/null and b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ExtraLightItalic.ttf differ diff --git a/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Italic.ttf b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Italic.ttf new file mode 100644 index 0000000..3cb28a3 Binary files /dev/null and b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Italic.ttf differ diff --git a/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Light.ttf b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Light.ttf new file mode 100644 index 0000000..df167f0 Binary files /dev/null and b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Light.ttf differ diff --git a/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-LightItalic.ttf b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-LightItalic.ttf new file mode 100644 index 0000000..c9072e9 Binary files /dev/null and b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-LightItalic.ttf differ diff --git a/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Medium.ttf b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Medium.ttf new file mode 100644 index 0000000..39f178d Binary files /dev/null and b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Medium.ttf differ diff --git a/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-MediumItalic.ttf b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-MediumItalic.ttf new file mode 100644 index 0000000..0d887f7 Binary files /dev/null and b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-MediumItalic.ttf differ diff --git a/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Regular.ttf b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Regular.ttf new file mode 100644 index 0000000..81ca3dc Binary files /dev/null and b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Regular.ttf differ diff --git a/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-SemiBold.ttf b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-SemiBold.ttf new file mode 100644 index 0000000..73dd5a4 Binary files /dev/null and b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-SemiBold.ttf differ diff --git a/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-SemiBoldItalic.ttf b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-SemiBoldItalic.ttf new file mode 100644 index 0000000..a41b0d3 Binary files /dev/null and b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-SemiBoldItalic.ttf differ diff --git a/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Thin.ttf b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Thin.ttf new file mode 100644 index 0000000..e173f5a Binary files /dev/null and b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Thin.ttf differ diff --git a/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ThinItalic.ttf b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ThinItalic.ttf new file mode 100644 index 0000000..8529275 Binary files /dev/null and b/docs/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ThinItalic.ttf differ diff --git a/docs/assets/fonts/IBM_Plex_Mono/OFL.txt b/docs/assets/fonts/IBM_Plex_Mono/OFL.txt new file mode 100644 index 0000000..245d5f4 --- /dev/null +++ b/docs/assets/fonts/IBM_Plex_Mono/OFL.txt @@ -0,0 +1,93 @@ +Copyright © 2017 IBM Corp. with Reserved Font Name "Plex" + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/assets/fonts/Onest/OFL.txt b/docs/assets/fonts/Onest/OFL.txt new file mode 100644 index 0000000..fee7fd4 --- /dev/null +++ b/docs/assets/fonts/Onest/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2021 The Onest Project Authors (https://github.com/googlefonts/onest) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/assets/fonts/Onest/Onest-VariableFont_wght.ttf b/docs/assets/fonts/Onest/Onest-VariableFont_wght.ttf new file mode 100644 index 0000000..6c77d20 Binary files /dev/null and b/docs/assets/fonts/Onest/Onest-VariableFont_wght.ttf differ diff --git a/docs/assets/fonts/Onest/README.txt b/docs/assets/fonts/Onest/README.txt new file mode 100644 index 0000000..55637d4 --- /dev/null +++ b/docs/assets/fonts/Onest/README.txt @@ -0,0 +1,71 @@ +Onest Variable Font +=================== + +This download contains Onest as both a variable font and static fonts. + +Onest is a variable font with this axis: + wght + +This means all the styles are contained in a single file: + Onest-VariableFont_wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for Onest: + static/Onest-Thin.ttf + static/Onest-ExtraLight.ttf + static/Onest-Light.ttf + static/Onest-Regular.ttf + static/Onest-Medium.ttf + static/Onest-SemiBold.ttf + static/Onest-Bold.ttf + static/Onest-ExtraBold.ttf + static/Onest-Black.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/docs/assets/fonts/Onest/static/Onest-Black.ttf b/docs/assets/fonts/Onest/static/Onest-Black.ttf new file mode 100644 index 0000000..d11a9c7 Binary files /dev/null and b/docs/assets/fonts/Onest/static/Onest-Black.ttf differ diff --git a/docs/assets/fonts/Onest/static/Onest-Bold.ttf b/docs/assets/fonts/Onest/static/Onest-Bold.ttf new file mode 100644 index 0000000..d210dda Binary files /dev/null and b/docs/assets/fonts/Onest/static/Onest-Bold.ttf differ diff --git a/docs/assets/fonts/Onest/static/Onest-ExtraBold.ttf b/docs/assets/fonts/Onest/static/Onest-ExtraBold.ttf new file mode 100644 index 0000000..0c94fb5 Binary files /dev/null and b/docs/assets/fonts/Onest/static/Onest-ExtraBold.ttf differ diff --git a/docs/assets/fonts/Onest/static/Onest-ExtraLight.ttf b/docs/assets/fonts/Onest/static/Onest-ExtraLight.ttf new file mode 100644 index 0000000..8b310c0 Binary files /dev/null and b/docs/assets/fonts/Onest/static/Onest-ExtraLight.ttf differ diff --git a/docs/assets/fonts/Onest/static/Onest-Light.ttf b/docs/assets/fonts/Onest/static/Onest-Light.ttf new file mode 100644 index 0000000..75534d2 Binary files /dev/null and b/docs/assets/fonts/Onest/static/Onest-Light.ttf differ diff --git a/docs/assets/fonts/Onest/static/Onest-Medium.ttf b/docs/assets/fonts/Onest/static/Onest-Medium.ttf new file mode 100644 index 0000000..af129b9 Binary files /dev/null and b/docs/assets/fonts/Onest/static/Onest-Medium.ttf differ diff --git a/docs/assets/fonts/Onest/static/Onest-Regular.ttf b/docs/assets/fonts/Onest/static/Onest-Regular.ttf new file mode 100644 index 0000000..1ef71b6 Binary files /dev/null and b/docs/assets/fonts/Onest/static/Onest-Regular.ttf differ diff --git a/docs/assets/fonts/Onest/static/Onest-SemiBold.ttf b/docs/assets/fonts/Onest/static/Onest-SemiBold.ttf new file mode 100644 index 0000000..57d25c3 Binary files /dev/null and b/docs/assets/fonts/Onest/static/Onest-SemiBold.ttf differ diff --git a/docs/assets/fonts/Onest/static/Onest-Thin.ttf b/docs/assets/fonts/Onest/static/Onest-Thin.ttf new file mode 100644 index 0000000..a6dc078 Binary files /dev/null and b/docs/assets/fonts/Onest/static/Onest-Thin.ttf differ diff --git a/docs/assets/img/csv-logo-big.svg b/docs/assets/img/csv-logo-big.svg new file mode 100644 index 0000000..a704702 --- /dev/null +++ b/docs/assets/img/csv-logo-big.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/img/csv-logo.svg b/docs/assets/img/csv-logo.svg new file mode 100644 index 0000000..70bb6fa --- /dev/null +++ b/docs/assets/img/csv-logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/assets/img/github-logo.svg b/docs/assets/img/github-logo.svg new file mode 100644 index 0000000..96ee44e --- /dev/null +++ b/docs/assets/img/github-logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/docs/assets/img/period-logo-big.svg b/docs/assets/img/period-logo-big.svg new file mode 100644 index 0000000..27637bc --- /dev/null +++ b/docs/assets/img/period-logo-big.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/img/period-logo.svg b/docs/assets/img/period-logo.svg new file mode 100644 index 0000000..a8f600e --- /dev/null +++ b/docs/assets/img/period-logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/assets/img/uri-logo-big.svg b/docs/assets/img/uri-logo-big.svg new file mode 100644 index 0000000..2e97b79 --- /dev/null +++ b/docs/assets/img/uri-logo-big.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/img/uri-logo.svg b/docs/assets/img/uri-logo.svg new file mode 100644 index 0000000..e441790 --- /dev/null +++ b/docs/assets/img/uri-logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/docs/index.md b/docs/index.md index c1a05d3..2505fdb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,5 @@ --- -title: Introduction -order: 1 +layout: homepage --- # Introduction @@ -19,7 +18,7 @@ $container[1]->value()->toString(); // returns 'application/xhtml+xml' $container[1]->parameterByKey(key: 'q', default: 1.0); // returns 1.0 if the parameter is not defined ``` -## Motivation +# Motivation While they are plenty of HTTP headers and trailers, they have historically come each with their own set of rules and constraints when it came to parsing and serializing them. Trying to use the parsing logic of a cookie header @@ -30,34 +29,3 @@ parsing and serializing. New HTTP headers or trailers (called HTTP fields) are encouraged to use the RFC algorithm, data and value types and ongoing discussions are happening to [retrofit existing headers that do not match the RFC](https://httpwg.org/http-extensions/draft-ietf-httpbis-retrofit.html) into new shapes that would be compatible with it. - -## Foreword - -> [!CAUTION] -> While this package parses and serializes HTTP field value, it does not validate its content -> against any conformance rule out of the box. You are still required to perform such a -> compliance check against the constraints of the corresponding field. While Content -> validation is still possible and highly encouraged when using this library. Because -> of the wide variety of HTTP fields it can not be made mandatory. - -## System Requirements - -**PHP >= 8.1** is required but the latest stable version of PHP is recommended. - -## Installation - -Use composer: - -``` -composer require bakame/http-structured-fields:^2.0 -``` - -## Using the package - -- [Basic usage](basic-usage.md) -- [Parsing and Serializing](parsing-serializing.md) -- [Accessing The Field Values](field-values.md) -- [Working with The Containers](containers.md) -- [Structured Field Validation](validation.md) -- [Interacting with the PHP Ecosystem](extensions.md) -- [Upgrading from 1.x to 2.0](migration.md) diff --git a/docs/input.css b/docs/input.css new file mode 100644 index 0000000..c038e55 --- /dev/null +++ b/docs/input.css @@ -0,0 +1,561 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer utilities { + .top-50 { + top: 50px; + } +} + +.top-logo { + margin-top: 225px; +} +.-right-logo { + margin-left: 45%; +} + +@media only screen and (max-width: 600px) { + .top-logo { + margin-top: 100px; + } + .-right-logo { + margin-left: 75%; + } +} + +pre { + @apply p-6 overflow-auto bg-gray-100 rounded-lg my-4 leading-normal; +} + +.hover-button:hover { + @apply opacity-90 drop-shadow-lg; +} + +.hover-block:hover { + @apply drop-shadow-lg; +} + +.hover-text:hover { + @apply no-underline text-light; +} + +.hover-text:hover .underline { + @apply no-underline text-light; +} + +.hover-block:hover div.hover-action { + @apply bg-gradient-to-tr from-csv-base via-uri-base to-period-base text-white; +} + +.hover-block:hover img.hover-action { + @apply bg-none opacity-100; +} + +code { + @apply text-sm sm:text-base font-mono; +} + +.language-php.highlighter-rouge { + @apply relative +} + +.language-php.highlighter-rouge .copy-snippet { + @apply absolute top-1 right-1 px-4 py-2 bg-white hidden no-underline cursor-pointer +} + +.language-php.highlighter-rouge .copy-snippet-notification { + @apply absolute top-14 right-1 text-white +} + +.language-php.highlighter-rouge:hover .copy-snippet { + @apply block; +} + +.language-php .string { + color: #ff4143 +} + +.language-php .this,.language-php .keyword { + color: #2b3d50 +} + +.language-php .comment { + color: #b6b6b6 +} + +.language-php .property,.language-php .function,.language-php .class-name,.language-php .variable { + color: #07a +} + +.language-php .punctuation { + color: #2b3d50 +} + +.language-php .php .keyword,.language-php .php .string { + color: #ff4143 +} + +.language-php .php .function,.language-php .php .property,.language-php .php .this,.language-php .php .variable,.language-php .php .operator,.language-php .php .punctuation { + color: #07a +} + +.language-php .markup .token { + color: #2b3d50 +} + +.language-php .markup .token.comment { + color: #b6b6b6 +} + +.language-php .markup .token .php .keyword,.language-php .markup .token .php .string,.language-php .markup .token .php .punctuation { + color: #690 +} + +.language-php .markup .token .php .function,.language-php .markup .token .php .property,.language-php .markup .token .php .this,.language-php .markup .token .php .variable,.language-php .markup .token .php .operator { + color: #07a +} + +.language-javascript .punctuation { + color: #007e74 +} + +.language-javascript .string { + color: #ff4143 +} + +.language-bash .comment { + color: #b6b6b6 +} + +.highlight .hll { + background-color: #ffc +} + +.highlight .c { + color: #999988; + font-style: italic; +} + +.highlight .err { + color: #a61717; + background-color: #e3d2d2 +} + +.highlight .k { + color: #000000; +} + +.highlight .o { + color: #000000; +} + +.highlight .cm { + color: #999988; +} + +.highlight .cp { + color: #999999; + font-style: italic +} + +.highlight .c1 { + color: #999988; + font-style: italic +} + +.highlight .cs { + color: #999999; + font-style: italic +} + +.highlight .gd { + color: #000000; + background-color: #fdd +} + +.highlight .ge { + color: #000000; + font-style: italic +} + +.highlight .gr { + color: #a00 +} + +.highlight .gh { + color: #999 +} + +.highlight .gi { + color: #000000; + background-color: #dfd +} + +.highlight .go { + color: #888 +} + +.highlight .gp { + color: #555 +} + +.highlight .gs { + font-weight: normal +} + +.highlight .gu { + color: #aaa +} + +.highlight .gt { + color: #a00 +} + +.highlight .kc { + color: #000000; +} + +.highlight .kd { + color: #000000; +} + +.highlight .kn { + color: #000000; +} + +.highlight .kp { + color: #000000; +} + +.highlight .kr { + color: #000000; +} + +.highlight .kt { + color: #445588; +} + +.highlight .m { + color: #099 +} + +.highlight .s { + color: #d01040 +} + +.highlight .na { + color: teal +} + +.highlight .nb { + color: #0086B3 +} + +.highlight .nc { + color: #445588; +} + +.highlight .no { + color: teal +} + +.highlight .nd { + color: #3c5d5d; +} + +.highlight .ni { + color: purple +} + +.highlight .ne { + color: #990000; +} + +.highlight .nf { + color: #990000; +} + +.highlight .nl { + color: #990000; +} + +.highlight .nn { + color: #555 +} + +.highlight .nt { + color: navy +} + +.highlight .nv { + color: teal +} + +.highlight .ow { + color: #000000; +} + +.highlight .w { + color: #bbb +} + +.highlight .mf { + color: #099 +} + +.highlight .mh { + color: #099 +} + +.highlight .mi { + color: #099 +} + +.highlight .mo { + color: #099 +} + +.highlight .sb { + color: #d01040 +} + +.highlight .sc { + color: #d01040 +} + +.highlight .sd { + color: #d01040 +} + +.highlight .s2 { + color: #d01040 +} + +.highlight .se { + color: #d01040 +} + +.highlight .sh { + color: #d01040 +} + +.highlight .si { + color: #d01040 +} + +.highlight .sx { + color: #d01040 +} + +.highlight .sr { + color: #009926 +} + +.highlight .s1 { + color: #d01040 +} + +.highlight .ss { + color: #990073 +} + +.highlight .bp { + color: #999 +} + +.highlight .vc { + color: teal +} + +.highlight .vg { + color: teal +} + +.highlight .vi { + color: teal +} + +.highlight .il { + color: #099 +} + +.message-info, +.message-notice, +.message-warning { + @apply p-5 my-3 rounded-lg text-light +} + +.message-info a, +.message-notice a, +.message-warning a { + color: black +} + +.message-info code, +.message-notice code, +.message-warning code { + margin-left:.3rem; + margin-right: .3rem; + padding-right: .3rem; + padding-left: .3rem; + background: rgba(255,255,255,0.8); +} + +.message-info { + color: #196090; + background: #cce5f6; +} + +.message-info code { + border-color: #5faee3; + color: #196090; +} + +.message-notice { + color: #927608; + background: #fbedb8; +} + +.message-notice code { + border-color: #f4d03f; + color: #927608; +} + +.message-warning { + color: #a82315; + background: #fbdedb; +} + +.message-warning code { + border-color: #ed7669; + color: #a82315; +} + +.content h1 { + @apply font-black text-6xl tracking-tighter mb-6 +} + +.content h2 { + @apply text-3xl tracking-tight text-csv-base mt-10 mb-3 +} + +.content h3 { + @apply text-2xl tracking-tight text-gray-800 mt-10 mb-3 py-3 +} + +.content h4 { + @apply text-xl tracking-tight text-csv-dark mt-10 mb-3 py-3 +} + +.content { + @apply leading-loose +} + +.content code.language-plaintext { + @apply text-orange-600 +} + +.content table { + @apply border-collapse border border-slate-400 w-full +} + +.content thead { + @apply bg-gray-800 text-white bg-period-base +} + +.content th, +.content td { + @apply border border-slate-300 p-2 +} + +.content tr:nth-child(even) { + @apply bg-period-light +} + +.header-permalink { + text-decoration: none; + color:transparent; + font-size:.8em; + vertical-align: super; +} + +.header-permalink:hover, +h1:hover .header-permalink, +h2:hover .header-permalink, +h3:hover .header-permalink, +h4:hover .header-permalink, +h5:hover .header-permalink { + text-decoration: none; + color:#777; +} + +nav li a:hover { + @apply underline +} + +nav li.selected a { + @apply text-csv-dark underline +} + +.content ul li { + @apply list-disc mx-4 +} + +.content ol { + @apply my-4 pl-4 +} + +.content ol li { + @apply list-decimal mx-4 +} + +.content a { + @apply underline; +} + +img { + @apply m-auto inline +} + +.content p > img { + @apply object-none object-center +} + +.algolia-autocomplete { + width: 100% !important +} + +.algolia-autocomplete input { + font-weight: normal; + font-size: 16px; + border:1px solid #e8e8e8; + background-color:#f4f4f4; + padding:.5em 1em; + margin-left:.3em; + border-radius: .3em; + width: 80%; +} + +@media (max-width: 768px) { + .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu { + max-width: calc(100vw - 2rem) !important; + width: calc(100vw - 2rem) !important; + min-width: 0 !important; + margin-left: 16px !important; + right: -3rem !important; + } + .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu:before { + right: 58px; + } + .algolia-autocomplete .algolia-docsearch-suggestion--content { + width: 100% !important; + padding-left: 0 !important; + } + .algolia-autocomplete .algolia-docsearch-suggestion--content:before { + display: none !important; + } + .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column { + display: none !important; + } +} diff --git a/docs/releases.md b/docs/releases.md new file mode 100644 index 0000000..cc72f83 --- /dev/null +++ b/docs/releases.md @@ -0,0 +1,20 @@ +--- +layout: default +title: Release Notes +redirect_from: + - /changelog/ + - /upgrading/ +--- + +# Release Notes + +These are the release notes from `bakame/http-structured-fields`. We've tried to cover all changes, +including backward compatible breaks from the first commit through to the current stable release. +If we've missed anything, feel free to create an issue, or send a pull request. + +{% for release in site.github.releases %} + +## {{ release.name }} - {{ release.published_at | date: "%Y-%m-%d" }} + +{{ release.body | replace:'```':'~~~' | markdownify }} +{% endfor %} diff --git a/docs/scripts.0002.js b/docs/scripts.0002.js new file mode 100644 index 0000000..55bcbcb --- /dev/null +++ b/docs/scripts.0002.js @@ -0,0 +1,51 @@ +(() => { + let contentHeaders= document.querySelectorAll("main h2[id]"); + if (!document.querySelector('html').classList.contains('homepage') && contentHeaders) { + const uri = new URL(location.href); + contentHeaders.forEach((header) => { + uri.hash = header.id; + let link = document.createElement("a"); + link.classList.add("header-permalink"); + link.title = "Permalink"; + link.href = uri.toString(); + link.innerHTML = "¶"; + header.appendChild(link); + }); + } + + let codeSnippet = document.querySelectorAll('.content .language-php.highlighter-rouge'); + codeSnippet.forEach((snippet) => { + let notification = document.createElement("div"); + notification.classList.add('copy-snippet-notification', 'hidden', 'rounded', 'p-2'); + snippet.appendChild(notification); + + let link = document.createElement("span"); + link.classList.add("copy-snippet"); + link.innerHTML = "copy 📋"; + link.addEventListener('click', function (e) { + let snippetParent = e.target.parentNode; + let notification = snippetParent.querySelector('.copy-snippet-notification'); + let content = snippetParent.querySelector('pre').textContent; + try { + navigator.clipboard.writeText(content); + notification.innerHTML = 'Copied!'; + notification.classList.add('bg-black'); + notification.classList.remove('hidden'); + setTimeout(() => { + notification.classList.add('hidden'); + notification.classList.remove('bg-black'); + }, 500); + } catch (err) { + console.error('Failed to copy: ', err); + notification.innerHTML = 'Copy failed!'; + notification.classList.add('bg-red-800'); + notification.classList.remove('hidden'); + setTimeout(() => { + notification.classList.add('hidden'); + notification.classList.remove('bg-red-800'); + }, 500); + } + }, false); + snippet.appendChild(link); + }); +})(); diff --git a/docs/styles.0004.css b/docs/styles.0004.css new file mode 100644 index 0000000..dde0237 --- /dev/null +++ b/docs/styles.0004.css @@ -0,0 +1,2076 @@ +/* +! tailwindcss v3.4.4 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. +7. Disable tap highlights on iOS +*/ + +html, +:host { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ + -webkit-tap-highlight-color: transparent; + /* 7 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font-family by default. +2. Use the user's configured `mono` font-feature-settings by default. +3. Use the user's configured `mono` font-variation-settings by default. +4. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: "IBM Plex Mono", mono, source-code-pro, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace; + /* 1 */ + font-feature-settings: normal; + /* 2 */ + font-variation-settings: normal; + /* 3 */ + font-size: 1em; + /* 4 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-feature-settings: inherit; + /* 1 */ + font-variation-settings: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + letter-spacing: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +input:where([type='button']), +input:where([type='reset']), +input:where([type='submit']) { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Reset default styling for dialogs. +*/ + +dialog { + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden] { + display: none; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +.static { + position: static; +} + +.fixed { + position: fixed; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.sticky { + position: sticky; +} + +.inset-y-0 { + top: 0px; + bottom: 0px; +} + +.left-0 { + left: 0px; +} + +.right-0 { + right: 0px; +} + +.right-5 { + right: 1.25rem; +} + +.top-0 { + top: 0px; +} + +.top-5 { + top: 1.25rem; +} + +.top-\[3\.5rem\] { + top: 3.5rem; +} + +.top-\[4\.5rem\] { + top: 4.5rem; +} + +.z-0 { + z-index: 0; +} + +.z-10 { + z-index: 10; +} + +.z-50 { + z-index: 50; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.my-16 { + margin-top: 4rem; + margin-bottom: 4rem; +} + +.-ml-0 { + margin-left: -0px; +} + +.-ml-0\.5 { + margin-left: -0.125rem; +} + +.mb-16 { + margin-bottom: 4rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.ml-4 { + margin-left: 1rem; +} + +.mt-3 { + margin-top: 0.75rem; +} + +.mt-6 { + margin-top: 1.5rem; +} + +.flex { + display: flex; +} + +.table { + display: table; +} + +.grid { + display: grid; +} + +.contents { + display: contents; +} + +.hidden { + display: none; +} + +.h-12 { + height: 3rem; +} + +.h-6 { + height: 1.5rem; +} + +.h-\[40px\] { + height: 40px; +} + +.h-\[calc\(100vh-4\.5rem\)\] { + height: calc(100vh - 4.5rem); +} + +.h-px { + height: 1px; +} + +.h-screen { + height: 100vh; +} + +.w-1\/2 { + width: 50%; +} + +.w-12 { + width: 3rem; +} + +.w-6 { + width: 1.5rem; +} + +.w-64 { + width: 16rem; +} + +.w-\[40px\] { + width: 40px; +} + +.w-full { + width: 100%; +} + +.min-w-0 { + min-width: 0px; +} + +.max-w-2xl { + max-width: 42rem; +} + +.max-w-5xl { + max-width: 64rem; +} + +.max-w-7xl { + max-width: 80rem; +} + +.flex-auto { + flex: 1 1 auto; +} + +.shrink-0 { + flex-shrink: 0; +} + +.rotate-6 { + --tw-rotate: 6deg; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.cursor-pointer { + cursor: pointer; +} + +.flex-col { + flex-direction: column; +} + +.items-start { + align-items: flex-start; +} + +.items-center { + align-items: center; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.gap-0 { + gap: 0px; +} + +.gap-12 { + gap: 3rem; +} + +.gap-2 { + gap: 0.5rem; +} + +.gap-3 { + gap: 0.75rem; +} + +.gap-6 { + gap: 1.5rem; +} + +.space-y-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} + +.space-y-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); +} + +.overflow-hidden { + overflow: hidden; +} + +.overflow-y-auto { + overflow-y: auto; +} + +.whitespace-nowrap { + white-space: nowrap; +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.rounded-xl { + border-radius: 0.75rem; +} + +.border { + border-width: 1px; +} + +.border-t { + border-top-width: 1px; +} + +.border-csv-base { + --tw-border-opacity: 1; + border-color: rgb(56 193 99 / var(--tw-border-opacity)); +} + +.border-light { + --tw-border-opacity: 1; + border-color: rgb(128 128 128 / var(--tw-border-opacity)); +} + +.border-slate-300 { + --tw-border-opacity: 1; + border-color: rgb(203 213 225 / var(--tw-border-opacity)); +} + +.border-opacity-50 { + --tw-border-opacity: 0.5; +} + +.bg-black { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); +} + +.bg-csv-base { + --tw-bg-opacity: 1; + background-color: rgb(56 193 99 / var(--tw-bg-opacity)); +} + +.bg-csv-light { + --tw-bg-opacity: 1; + background-color: rgb(116 212 146 / var(--tw-bg-opacity)); +} + +.bg-gray-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + +.bg-gray-200 { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + +.bg-red-800 { + --tw-bg-opacity: 1; + background-color: rgb(153 27 27 / var(--tw-bg-opacity)); +} + +.bg-slate-100 { + --tw-bg-opacity: 1; + background-color: rgb(241 245 249 / var(--tw-bg-opacity)); +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.bg-gradient-to-r { + background-image: linear-gradient(to right, var(--tw-gradient-stops)); +} + +.from-csv-base { + --tw-gradient-from: #38C163 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(56 193 99 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); +} + +.to-white { + --tw-gradient-to: #fff var(--tw-gradient-to-position); +} + +.p-2 { + padding: 0.5rem; +} + +.p-3 { + padding: 0.75rem; +} + +.p-6 { + padding: 1.5rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.py-10 { + padding-top: 2.5rem; + padding-bottom: 2.5rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + +.py-7 { + padding-top: 1.75rem; + padding-bottom: 1.75rem; +} + +.py-9 { + padding-top: 2.25rem; + padding-bottom: 2.25rem; +} + +.pb-16 { + padding-bottom: 4rem; +} + +.pb-32 { + padding-bottom: 8rem; +} + +.pb-6 { + padding-bottom: 1.5rem; +} + +.pl-0 { + padding-left: 0px; +} + +.pl-0\.5 { + padding-left: 0.125rem; +} + +.pl-3 { + padding-left: 0.75rem; +} + +.pl-6 { + padding-left: 1.5rem; +} + +.pr-6 { + padding-right: 1.5rem; +} + +.pr-8 { + padding-right: 2rem; +} + +.pt-24 { + padding-top: 6rem; +} + +.font-mono { + font-family: "IBM Plex Mono", mono, source-code-pro, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace; +} + +.font-onest { + font-family: "Onest", sans-serif; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.text-6xl { + font-size: 3.75rem; + line-height: 1; +} + +.text-8xl { + font-size: 6rem; + line-height: 1; +} + +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.font-black { + font-weight: 900; +} + +.font-bold { + font-weight: 700; +} + +.font-medium { + font-weight: 500; +} + +.font-semibold { + font-weight: 600; +} + +.tracking-tight { + letter-spacing: -0.025em; +} + +.tracking-tighter { + letter-spacing: -0.05em; +} + +.text-csv-base { + --tw-text-opacity: 1; + color: rgb(56 193 99 / var(--tw-text-opacity)); +} + +.text-csv-dark { + --tw-text-opacity: 1; + color: rgb(39 135 69 / var(--tw-text-opacity)); +} + +.text-dark { + --tw-text-opacity: 1; + color: rgb(44 44 44 / var(--tw-text-opacity)); +} + +.text-light { + --tw-text-opacity: 1; + color: rgb(128 128 128 / var(--tw-text-opacity)); +} + +.text-slate-600 { + --tw-text-opacity: 1; + color: rgb(71 85 105 / var(--tw-text-opacity)); +} + +.text-slate-900 { + --tw-text-opacity: 1; + color: rgb(15 23 42 / var(--tw-text-opacity)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.underline { + text-decoration-line: underline; +} + +.antialiased { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.shadow-lg { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-xl { + --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-200 { + transition-duration: 200ms; +} + +.ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +.top-logo { + margin-top: 225px; +} + +.-right-logo { + margin-left: 45%; +} + +@media only screen and (max-width: 600px) { + .top-logo { + margin-top: 100px; + } + + .-right-logo { + margin-left: 75%; + } +} + +pre { + margin-top: 1rem; + margin-bottom: 1rem; + overflow: auto; + border-radius: 0.5rem; + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); + padding: 1.5rem; + line-height: 1.5; +} + +.hover-button:hover { + opacity: 0.9; + --tw-drop-shadow: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.hover-block:hover { + --tw-drop-shadow: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.hover-text:hover { + --tw-text-opacity: 1; + color: rgb(128 128 128 / var(--tw-text-opacity)); + text-decoration-line: none; +} + +.hover-text:hover .underline { + --tw-text-opacity: 1; + color: rgb(128 128 128 / var(--tw-text-opacity)); + text-decoration-line: none; +} + +.hover-block:hover div.hover-action { + background-image: linear-gradient(to top right, var(--tw-gradient-stops)); + --tw-gradient-from: #38C163 var(--tw-gradient-from-position); + --tw-gradient-to: rgb(56 193 99 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); + --tw-gradient-to: rgb(55 111 255 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), #376FFF var(--tw-gradient-via-position), var(--tw-gradient-to); + --tw-gradient-to: #FFC61D var(--tw-gradient-to-position); + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.hover-block:hover img.hover-action { + background-image: none; + opacity: 1; +} + +code { + font-family: "IBM Plex Mono", mono, source-code-pro, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace; + font-size: 0.875rem; + line-height: 1.25rem; +} + +@media (min-width: 640px) { + code { + font-size: 1rem; + line-height: 1.5rem; + } +} + +.language-php.highlighter-rouge { + position: relative; +} + +.language-php.highlighter-rouge .copy-snippet { + position: absolute; + top: 0.25rem; + right: 0.25rem; + display: none; + cursor: pointer; + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + text-decoration-line: none; +} + +.language-php.highlighter-rouge .copy-snippet-notification { + position: absolute; + top: 3.5rem; + right: 0.25rem; + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.language-php.highlighter-rouge:hover .copy-snippet { + display: block; +} + +.language-php .string { + color: #ff4143 +} + +.language-php .this,.language-php .keyword { + color: #2b3d50 +} + +.language-php .comment { + color: #b6b6b6 +} + +.language-php .property,.language-php .function,.language-php .class-name,.language-php .variable { + color: #07a +} + +.language-php .punctuation { + color: #2b3d50 +} + +.language-php .php .keyword,.language-php .php .string { + color: #ff4143 +} + +.language-php .php .function,.language-php .php .property,.language-php .php .this,.language-php .php .variable,.language-php .php .operator,.language-php .php .punctuation { + color: #07a +} + +.language-php .markup .token { + color: #2b3d50 +} + +.language-php .markup .token.comment { + color: #b6b6b6 +} + +.language-php .markup .token .php .keyword,.language-php .markup .token .php .string,.language-php .markup .token .php .punctuation { + color: #690 +} + +.language-php .markup .token .php .function,.language-php .markup .token .php .property,.language-php .markup .token .php .this,.language-php .markup .token .php .variable,.language-php .markup .token .php .operator { + color: #07a +} + +.language-javascript .punctuation { + color: #007e74 +} + +.language-javascript .string { + color: #ff4143 +} + +.language-bash .comment { + color: #b6b6b6 +} + +.highlight .hll { + background-color: #ffc +} + +.highlight .c { + color: #999988; + font-style: italic; +} + +.highlight .err { + color: #a61717; + background-color: #e3d2d2 +} + +.highlight .k { + color: #000000; +} + +.highlight .o { + color: #000000; +} + +.highlight .cm { + color: #999988; +} + +.highlight .cp { + color: #999999; + font-style: italic +} + +.highlight .c1 { + color: #999988; + font-style: italic +} + +.highlight .cs { + color: #999999; + font-style: italic +} + +.highlight .gd { + color: #000000; + background-color: #fdd +} + +.highlight .ge { + color: #000000; + font-style: italic +} + +.highlight .gr { + color: #a00 +} + +.highlight .gh { + color: #999 +} + +.highlight .gi { + color: #000000; + background-color: #dfd +} + +.highlight .go { + color: #888 +} + +.highlight .gp { + color: #555 +} + +.highlight .gs { + font-weight: normal +} + +.highlight .gu { + color: #aaa +} + +.highlight .gt { + color: #a00 +} + +.highlight .kc { + color: #000000; +} + +.highlight .kd { + color: #000000; +} + +.highlight .kn { + color: #000000; +} + +.highlight .kp { + color: #000000; +} + +.highlight .kr { + color: #000000; +} + +.highlight .kt { + color: #445588; +} + +.highlight .m { + color: #099 +} + +.highlight .s { + color: #d01040 +} + +.highlight .na { + color: teal +} + +.highlight .nb { + color: #0086B3 +} + +.highlight .nc { + color: #445588; +} + +.highlight .no { + color: teal +} + +.highlight .nd { + color: #3c5d5d; +} + +.highlight .ni { + color: purple +} + +.highlight .ne { + color: #990000; +} + +.highlight .nf { + color: #990000; +} + +.highlight .nl { + color: #990000; +} + +.highlight .nn { + color: #555 +} + +.highlight .nt { + color: navy +} + +.highlight .nv { + color: teal +} + +.highlight .ow { + color: #000000; +} + +.highlight .w { + color: #bbb +} + +.highlight .mf { + color: #099 +} + +.highlight .mh { + color: #099 +} + +.highlight .mi { + color: #099 +} + +.highlight .mo { + color: #099 +} + +.highlight .sb { + color: #d01040 +} + +.highlight .sc { + color: #d01040 +} + +.highlight .sd { + color: #d01040 +} + +.highlight .s2 { + color: #d01040 +} + +.highlight .se { + color: #d01040 +} + +.highlight .sh { + color: #d01040 +} + +.highlight .si { + color: #d01040 +} + +.highlight .sx { + color: #d01040 +} + +.highlight .sr { + color: #009926 +} + +.highlight .s1 { + color: #d01040 +} + +.highlight .ss { + color: #990073 +} + +.highlight .bp { + color: #999 +} + +.highlight .vc { + color: teal +} + +.highlight .vg { + color: teal +} + +.highlight .vi { + color: teal +} + +.highlight .il { + color: #099 +} + +.message-info, +.message-notice, +.message-warning { + margin-top: 0.75rem; + margin-bottom: 0.75rem; + border-radius: 0.5rem; + padding: 1.25rem; + --tw-text-opacity: 1; + color: rgb(128 128 128 / var(--tw-text-opacity)); +} + +.message-info a, +.message-notice a, +.message-warning a { + color: black +} + +.message-info code, +.message-notice code, +.message-warning code { + margin-left:.3rem; + margin-right: .3rem; + padding-right: .3rem; + padding-left: .3rem; + background: rgba(255,255,255,0.8); +} + +.message-info { + color: #196090; + background: #cce5f6; +} + +.message-info code { + border-color: #5faee3; + color: #196090; +} + +.message-notice { + color: #927608; + background: #fbedb8; +} + +.message-notice code { + border-color: #f4d03f; + color: #927608; +} + +.message-warning { + color: #a82315; + background: #fbdedb; +} + +.message-warning code { + border-color: #ed7669; + color: #a82315; +} + +.content h1 { + margin-bottom: 1.5rem; + font-size: 3.75rem; + line-height: 1; + font-weight: 900; + letter-spacing: -0.05em; +} + +.content h2 { + margin-top: 2.5rem; + margin-bottom: 0.75rem; + font-size: 1.875rem; + line-height: 2.25rem; + letter-spacing: -0.025em; + --tw-text-opacity: 1; + color: rgb(56 193 99 / var(--tw-text-opacity)); +} + +.content h3 { + margin-top: 2.5rem; + margin-bottom: 0.75rem; + padding-top: 0.75rem; + padding-bottom: 0.75rem; + font-size: 1.5rem; + line-height: 2rem; + letter-spacing: -0.025em; + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity)); +} + +.content h4 { + margin-top: 2.5rem; + margin-bottom: 0.75rem; + padding-top: 0.75rem; + padding-bottom: 0.75rem; + font-size: 1.25rem; + line-height: 1.75rem; + letter-spacing: -0.025em; + --tw-text-opacity: 1; + color: rgb(39 135 69 / var(--tw-text-opacity)); +} + +.content { + line-height: 2; +} + +.content code.language-plaintext { + --tw-text-opacity: 1; + color: rgb(234 88 12 / var(--tw-text-opacity)); +} + +.content table { + width: 100%; + border-collapse: collapse; + border-width: 1px; + --tw-border-opacity: 1; + border-color: rgb(148 163 184 / var(--tw-border-opacity)); +} + +.content thead { + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); + --tw-bg-opacity: 1; + background-color: rgb(255 198 29 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.content th, +.content td { + border-width: 1px; + --tw-border-opacity: 1; + border-color: rgb(203 213 225 / var(--tw-border-opacity)); + padding: 0.5rem; +} + +.content tr:nth-child(even) { + --tw-bg-opacity: 1; + background-color: rgb(255 221 119 / var(--tw-bg-opacity)); +} + +.header-permalink { + text-decoration: none; + color:transparent; + font-size:.8em; + vertical-align: super; +} + +.header-permalink:hover, +h1:hover .header-permalink, +h2:hover .header-permalink, +h3:hover .header-permalink, +h4:hover .header-permalink, +h5:hover .header-permalink { + text-decoration: none; + color:#777; +} + +nav li a:hover { + text-decoration-line: underline; +} + +.hover-text:hover nav li a:hover { + --tw-text-opacity: 1; + color: rgb(128 128 128 / var(--tw-text-opacity)); + text-decoration-line: none; +} + +nav li.selected a { + --tw-text-opacity: 1; + color: rgb(39 135 69 / var(--tw-text-opacity)); + text-decoration-line: underline; +} + +.hover-text:hover nav li.selected a { + --tw-text-opacity: 1; + color: rgb(128 128 128 / var(--tw-text-opacity)); + text-decoration-line: none; +} + +.content ul li { + margin-left: 1rem; + margin-right: 1rem; + list-style-type: disc; +} + +.content ol { + margin-top: 1rem; + margin-bottom: 1rem; + padding-left: 1rem; +} + +.content ol li { + margin-left: 1rem; + margin-right: 1rem; + list-style-type: decimal; +} + +.content a { + text-decoration-line: underline; +} + +.hover-text:hover .content a { + --tw-text-opacity: 1; + color: rgb(128 128 128 / var(--tw-text-opacity)); + text-decoration-line: none; +} + +img { + margin: auto; + display: inline; +} + +.content p > img { + -o-object-fit: none; + object-fit: none; + -o-object-position: center; + object-position: center; +} + +.algolia-autocomplete { + width: 100% !important +} + +.algolia-autocomplete input { + font-weight: normal; + font-size: 16px; + border:1px solid #e8e8e8; + background-color:#f4f4f4; + padding:.5em 1em; + margin-left:.3em; + border-radius: .3em; + width: 80%; +} + +@media (max-width: 768px) { + .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu { + max-width: calc(100vw - 2rem) !important; + width: calc(100vw - 2rem) !important; + min-width: 0 !important; + margin-left: 16px !important; + right: -3rem !important; + } + + .algolia-autocomplete.algolia-autocomplete-right .ds-dropdown-menu:before { + right: 58px; + } + + .algolia-autocomplete .algolia-docsearch-suggestion--content { + width: 100% !important; + padding-left: 0 !important; + } + + .algolia-autocomplete .algolia-docsearch-suggestion--content:before { + display: none !important; + } + + .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column { + display: none !important; + } +} + +.hover\:bg-csv-dark:hover { + --tw-bg-opacity: 1; + background-color: rgb(39 135 69 / var(--tw-bg-opacity)); +} + +.hover\:text-csv-dark:hover { + --tw-text-opacity: 1; + color: rgb(39 135 69 / var(--tw-text-opacity)); +} + +.hover\:text-slate-800:hover { + --tw-text-opacity: 1; + color: rgb(30 41 59 / var(--tw-text-opacity)); +} + +.active\:scale-95:active { + --tw-scale-x: .95; + --tw-scale-y: .95; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.peer:checked ~ .peer-checked\:block { + display: block; +} + +@media (min-width: 640px) { + .sm\:col-span-3 { + grid-column: span 3 / span 3; + } + + .sm\:h-\[50px\] { + height: 50px; + } + + .sm\:w-2\/3 { + width: 66.666667%; + } + + .sm\:w-\[50px\] { + width: 50px; + } + + .sm\:w-auto { + width: auto; + } + + .sm\:grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); + } + + .sm\:flex-row { + flex-direction: row; + } + + .sm\:justify-center { + justify-content: center; + } + + .sm\:gap-3 { + gap: 0.75rem; + } + + .sm\:gap-32 { + gap: 8rem; + } + + .sm\:px-12 { + padding-left: 3rem; + padding-right: 3rem; + } + + .sm\:px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; + } + + .sm\:px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; + } + + .sm\:py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + } + + .sm\:pb-24 { + padding-bottom: 6rem; + } +} + +@media (min-width: 768px) { + .md\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .md\:flex-row { + flex-direction: row; + } + + .md\:items-center { + align-items: center; + } + + .md\:gap-0 { + gap: 0px; + } + + .md\:p-6 { + padding: 1.5rem; + } + + .md\:px-0 { + padding-left: 0px; + padding-right: 0px; + } + + .md\:text-8xl { + font-size: 6rem; + line-height: 1; + } + + .md\:text-9xl { + font-size: 8rem; + line-height: 1; + } +} + +@media (min-width: 1024px) { + .lg\:relative { + position: relative; + } + + .lg\:top-0 { + top: 0px; + } + + .lg\:col-span-1 { + grid-column: span 1 / span 1; + } + + .lg\:block { + display: block; + } + + .lg\:hidden { + display: none; + } + + .lg\:h-auto { + height: auto; + } + + .lg\:w-\[50vw\] { + width: 50vw; + } + + .lg\:max-w-4xl { + max-width: 56rem; + } + + .lg\:flex-none { + flex: none; + } + + .lg\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .lg\:flex-row { + flex-direction: row; + } + + .lg\:justify-between { + justify-content: space-between; + } + + .lg\:bg-transparent { + background-color: transparent; + } + + .lg\:px-0 { + padding-left: 0px; + padding-right: 0px; + } + + .lg\:px-8 { + padding-left: 2rem; + padding-right: 2rem; + } + + .lg\:pl-8 { + padding-left: 2rem; + } + + .lg\:pr-0 { + padding-right: 0px; + } + + .lg\:text-sm { + font-size: 0.875rem; + line-height: 1.25rem; + } + + .lg\:shadow-none { + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + } +} + +@media (min-width: 1280px) { + .xl\:w-72 { + width: 18rem; + } + + .xl\:px-12 { + padding-left: 3rem; + padding-right: 3rem; + } + + .xl\:px-16 { + padding-left: 4rem; + padding-right: 4rem; + } + + .xl\:pr-16 { + padding-right: 4rem; + } +} diff --git a/docs/tailwind.config.js b/docs/tailwind.config.js new file mode 100644 index 0000000..8e1722d --- /dev/null +++ b/docs/tailwind.config.js @@ -0,0 +1,33 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ["./**/*.{html,js}"], + theme: { + extend: { + fontFamily: { + 'onest': ['"Onest"', 'sans-serif'], + 'mono': ['"IBM Plex Mono"', 'mono', 'source-code-pro', 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'Liberation Mono', 'Courier New', 'monospace'] + }, + colors: { + 'csv': { + light: '#74D492', + base: '#38C163', + dark: '#278745' + }, + 'uri': { + light: '#739AFF', + base: '#376FFF', + dark: '#274EB2' + }, + 'period': { + light: '#FFDD77', + base: '#FFC61D', + dark: '#B28B14' + }, + 'dark': '#2C2C2C', + 'light': '#808080' + } + }, + }, + plugins: [], +} +