From 9d2cc438255d90d266bd09a1431e675df9c1e075 Mon Sep 17 00:00:00 2001 From: Elizabeth Mattijsen Date: Sun, 17 Nov 2024 13:45:21 +0100 Subject: [PATCH] Prepare for release using App::Mi6 --- .github/workflows/linux.yml | 26 + .github/workflows/macos.yml | 26 + .github/workflows/{test.yml => windows.yml} | 15 +- ChangeLog => Changes | 8 +- META6.json | 60 +- README.md | 635 ++++---- dist.ini | 11 + doc/URI.rakudoc | 1292 +++++++++++++++++ lib/IETF/RFC_Grammar.rakumod | 2 + lib/IETF/RFC_Grammar/IPv6.rakumod | 6 +- lib/IETF/RFC_Grammar/URI.rakumod | 3 +- lib/URI.rakumod | 782 +--------- lib/URI/DefaultPort.rakumod | 15 +- lib/URI/Escape.rakumod | 16 +- lib/URI/Path.rakumod | 2 + lib/URI/Query.rakumod | 2 +- run-tests | 66 + t/{01.t => 01.rakutest} | 6 +- t/{authority.t => authority.rakutest} | 5 +- t/{directory.t => directory.rakutest} | 3 +- t/{escape.t => escape.rakutest} | 6 +- t/{issue-43.t => issue-43.rakutest} | 3 +- ...mponents.t => missing-components.rakutest} | 4 +- t/{mutate.t => mutate.rakutest} | 12 +- ...encoded.t => november-urlencoded.rakutest} | 5 +- t/{path.t => path.rakutest} | 6 +- t/{query.t => query.rakutest} | 6 +- t/{rel2abs.t => rel2abs.rakutest} | 3 +- t/{require.t => require.rakutest} | 8 +- ...-examples.t => rfc-3986-examples.rakutest} | 6 +- t/{utf8-c8.t => utf8-c8.rakutest} | 8 +- 31 files changed, 1905 insertions(+), 1143 deletions(-) create mode 100644 .github/workflows/linux.yml create mode 100644 .github/workflows/macos.yml rename .github/workflows/{test.yml => windows.yml} (56%) rename ChangeLog => Changes (90%) create mode 100644 dist.ini create mode 100644 doc/URI.rakudoc create mode 100644 run-tests rename t/{01.t => 01.rakutest} (97%) rename t/{authority.t => authority.rakutest} (95%) rename t/{directory.t => directory.rakutest} (92%) rename t/{escape.t => escape.rakutest} (91%) rename t/{issue-43.t => issue-43.rakutest} (92%) rename t/{missing-components.t => missing-components.rakutest} (93%) rename t/{mutate.t => mutate.rakutest} (96%) rename t/{november-urlencoded.t => november-urlencoded.rakutest} (96%) rename t/{path.t => path.rakutest} (94%) rename t/{query.t => query.rakutest} (99%) rename t/{rel2abs.t => rel2abs.rakutest} (95%) rename t/{require.t => require.rakutest} (86%) rename t/{rfc-3986-examples.t => rfc-3986-examples.rakutest} (97%) rename t/{utf8-c8.t => utf8-c8.rakutest} (92%) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml new file mode 100644 index 0000000..ab53183 --- /dev/null +++ b/.github/workflows/linux.yml @@ -0,0 +1,26 @@ +name: Linux + +on: + push: + branches: + - '*' + tags-ignore: + - '*' + pull_request: + +jobs: + raku: + strategy: + matrix: + os: + - ubuntu-latest + raku-version: + - 'latest' + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - uses: Raku/setup-raku@v1 + with: + raku-version: ${{ matrix.raku-version }} + - name: Run Special Tests + run: raku run-tests -i diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml new file mode 100644 index 0000000..de79738 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,26 @@ +name: MacOS + +on: + push: + branches: + - '*' + tags-ignore: + - '*' + pull_request: + +jobs: + raku: + strategy: + matrix: + os: + - macos-latest + raku-version: + - 'latest' + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - uses: Raku/setup-raku@v1 + with: + raku-version: ${{ matrix.raku-version }} + - name: Run Special Tests + run: raku run-tests -i diff --git a/.github/workflows/test.yml b/.github/workflows/windows.yml similarity index 56% rename from .github/workflows/test.yml rename to .github/workflows/windows.yml index 79216e6..557c8d5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/windows.yml @@ -1,4 +1,4 @@ -name: test +name: Windows on: push: @@ -7,25 +7,20 @@ on: tags-ignore: - '*' pull_request: - workflow_dispatch: jobs: raku: strategy: - fail-fast: false matrix: os: - - ubuntu-latest - - macos-latest - windows-latest raku-version: - - "latest" - - "2023.08" + - 'latest' runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - uses: Raku/setup-raku@v1 with: raku-version: ${{ matrix.raku-version }} - - name: Run tests and Install - run: zef install . --debug + - name: Run Special Tests + run: raku run-tests -i diff --git a/ChangeLog b/Changes similarity index 90% rename from ChangeLog rename to Changes index 845fa21..0b085c7 100644 --- a/ChangeLog +++ b/Changes @@ -1,3 +1,9 @@ +Revision history for URI + +{{$NEXT}} + - Fix multi-thread issue + - First release using App::Mi6 to make releasing easier + 2023-10-17 v0.3.7 * File extensions now follow the Raku conventions @@ -48,7 +54,7 @@ New features for June 2011 upgrade include: 5) Add 'validating' attribute that, when set to true, tells the URI module to fail parsing unless the entire string it is asked to parse is a URI. - Default is to provide Perl 5 URI behavior and just try to parse URI from + Default is to provide Perl URI behavior and just try to parse URI from passed parse string. "is_validating"" attribute can be set as named param to new. diff --git a/META6.json b/META6.json index 131ab73..b9f62fe 100644 --- a/META6.json +++ b/META6.json @@ -1,27 +1,37 @@ { - "raku" : "6.d", - "name" : "URI", - "auth" : "zef:raku-community-modules", - "version" : "0.3.7", - "description" : "A URI implementation using Raku grammars to implement RFC 3986 BNF", - "license" : "Artistic-2.0", - "depends" : [ ], - "provides" : { - "IETF::RFC_Grammar" : "lib/IETF/RFC_Grammar.rakumod", - "IETF::RFC_Grammar::IPv6" : "lib/IETF/RFC_Grammar/IPv6.rakumod", - "IETF::RFC_Grammar::URI" : "lib/IETF/RFC_Grammar/URI.rakumod", - "URI" : "lib/URI.rakumod", - "URI::Authority" : "lib/URI.rakumod", - "URI::Path" : "lib/URI/Path.rakumod", - "URI::Query" : "lib/URI/Query.rakumod", - "URI::Escape" : "lib/URI/Escape.rakumod", - "URI::DefaultPort" : "lib/URI/DefaultPort.rakumod" - }, - "source-url" : "https://github.com/raku-community-modules/URI.git", - "authors" : ["Raku community"], - "meta-version" : 1, - "support" : { - "source" : "https://github.com/raku-community-modules/URI.git", - "bugtracker" : "https://github.com/raku-community-modules/URI/issues" - } + "auth": "zef:raku-community-modules", + "authors": [ + "Raku community" + ], + "build-depends": [ + ], + "depends": [ + ], + "description": "A URI implementation using Raku grammars to implement RFC 3986 BNF", + "license": "Artistic-2.0", + "meta-version": 1, + "name": "URI", + "provides": { + "IETF::RFC_Grammar": "lib/IETF/RFC_Grammar.rakumod", + "IETF::RFC_Grammar::IPv6": "lib/IETF/RFC_Grammar/IPv6.rakumod", + "IETF::RFC_Grammar::URI": "lib/IETF/RFC_Grammar/URI.rakumod", + "URI": "lib/URI.rakumod", + "URI::DefaultPort": "lib/URI/DefaultPort.rakumod", + "URI::Escape": "lib/URI/Escape.rakumod", + "URI::Path": "lib/URI/Path.rakumod", + "URI::Query": "lib/URI/Query.rakumod" + }, + "raku": "6.d", + "resources": [ + ], + "source-url": "https://github.com/raku-community-modules/URI.git", + "support": { + "bugtracker": "https://github.com/raku-community-modules/URI/issues", + "source": "https://github.com/raku-community-modules/URI.git" + }, + "tags": [ + ], + "test-depends": [ + ], + "version": "0.3.7" } diff --git a/README.md b/README.md index 0159dbc..9ee438f 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,66 @@ -# Raku realization of URI - Uniform Resource Identifiers handler [![test](https://github.com/raku-community-modules/URI/actions/workflows/test.yml/badge.svg)](https://github.com/raku-community-modules/URI/actions/workflows/test.yml) - -A URI implementation using Raku grammars to implement RFC 3986 BNF. -Currently only implements parsing. Includes URI::Escape to (un?)escape -characters that aren't otherwise allowed in a URI with % and a hex -character numbering. - - use URI; - my URI $u .= new('http://her.com/foo/bar?tag=woow#bla'); - my $scheme = $u.scheme; - my $authority = $u.authority; - my $host = $u.host; - my $port = $u.port; - my $path = $u.path; - my $query = $u.query; - my $frag = $u.frag; # or $u.fragment; - my $tag = $u.query-form; # should be woow - # etc. - - use URI::Escape; - my $escaped = uri-escape("10% is enough\n"); - my $un-escaped = uri-unescape('10%25%20is%20enough%0A'); - - # Modify the parts - $u.scheme('https'); - $u.authority('example.com:8443'); - $u.path('/bar/foo'); - $u.query('x=1&y=2'); # OR - $u.fragment('cool'); - say "$u"; #> https://example.com:8443/bar/foo?x=1&y=2#cool - - # Authority is an object, but may be undefined - with $u.authority { - say .userinfo; # none set, no output - say .host; #> example.com - say .port; #> 8443 - - # It is mutable too - .userinfo('bob'); - say $u.authority; #> bob@example.com:8443 - } - - # Path is an object, always defined, but immutable - with $u.path { - say .path; #> /bar/foo - .say for .segments; #> bar\nfoo\n - } - - # Query is an object, always defined, mutable - with $u.query { - say .query; #> x=1&y=2 - .query('foo=1&foo=2'); - say .query-form[0]; #> 1 - say .query-form[1]; #> 2 - - .query-form.push: 'bar' => 'ok'; - say .query; #> foo=1&foo=2&bar=ok - - .query(''); - .query-form = 123; - say .query; #> abc=123 - } - - -### POTENTIALLY INCOMPATIBLE CHANGES BETWEEN v0.2.2 and v0.3.0 - -The v0.3.0 introduced the ability to mutate the parts of an existing URI, which in turn may have introduced changes which are incompatible with existing applications: - - * URI.query now returns an object of type URI::Query not a Str - The object will coerce correctly when interpolated into a string but will need an explicit .Str coercion if being assigned to a Str typed variable. - - * URI.path now returns an object of type URI::Path not a Str - The object will coerce correctly when interpolated into a string but will need an explicit .Str coercion if being assigned to a Str typed variable. - - * URI.query-form no longer returns a Hash but an object of URI::Query - The object does the Associative role so for the most part can be treated like a Hash but an explicit .Hash coercion may be required when comparing with another Hash or when merging with another Hash. - Some uses of query-form have been marked as deprecated and should use .query instead. - -The changes have been tested with the majority of modules that depend on URI and only a few required changes. +[![Actions Status](https://github.com/raku-community-modules/URI/actions/workflows/linux.yml/badge.svg)](https://github.com/raku-community-modules/URI/actions) [![Actions Status](https://github.com/raku-community-modules/URI/actions/workflows/macos.yml/badge.svg)](https://github.com/raku-community-modules/URI/actions) [![Actions Status](https://github.com/raku-community-modules/URI/actions/workflows/windows.yml/badge.svg)](https://github.com/raku-community-modules/URI/actions) + +NAME +==== + +URI — Uniform Resource Identifiers (absolute and relative) + +SYNOPSIS +======== + +```raku +use URI; +my $u = URI.new('http://example.com/foo/bar?tag=woow#bla'); + +# Look at the parts +say $u.scheme; #> http +say $u.authority; #> example.com +say $u.host; #> example.com +say $u.port; #> 80 +say $u.path; #> /foo/bar +say $u.query; #> tag=woow +say $u.fragment; #> bla + +# Modify the parts +$u.scheme('https'); +$u.authority('example.com:8443'); +$u.path('/bar/foo'); +$u.query('x=1&y=2'); # OR +$u.fragment('cool'); +say "$u"; #> https://example.com:8443/bar/foo?x=1&y=2#cool + +# Authority is an object, but may be undefined +with $u.authority { + say .userinfo; # none set, no output + say .host; #> example.com + say .port; #> 8443 + + # It is mutable too + .userinfo('bob'); + say $u.authority; #> bob@example.com:8443 +} + +# Path is an object, always defined, but immutable +with $u.path { + say .path; #> /bar/foo + .say for .segments; #> bar\nfoo\n +} + +# Query is an object, always defined, mutable +with $u.query { + say .query; #> x=1&y=2 + .query('foo=1&foo=2'); + say .query-form[0]; #> 1 + say .query-form[1]; #> 2 + + .query-form.push: 'bar' => 'ok'; + say .query; #> foo=1&foo=2&bar=ok + + .query(''); + .query-form = 123; + say .query; #> abc=123 +} +``` DESCRIPTION =========== @@ -87,9 +71,11 @@ As of this writing, The URI class is scheme agnostic. It will verify that URI is This class uses "heavy accessors". From the SYNOPSIS, you may have noted that assignment to accessors is not used. This is because nearly all accessors in this class do some extra work of parsing, validating, and cross-referencing with other fields to guarantee that the URI is correct before making a change. - my $u = URI.new; - $u.path = '/foo/bar'; # ERROR: «Cannot modify an immutable URI::Path» - $u.path('/foo/bar'); # WORKS! +```raku +my $u = URI.new; +$u.path = '/foo/bar'; # ERROR: «Cannot modify an immutable URI::Path» +$u.path('/foo/bar'); # WORKS! +``` This mutator pattern is meant to reflect the internal complexity. @@ -98,11 +84,13 @@ SCHEME, AUTHORITY, AND PATH In RFC 3986 URIs, the scheme, the authority, and the path are related. This should not matter most of the time, but to avoid problems when setting these three, it is safest to set them in this order: - my $u = URI.new; - $u.path(''); - $u.scheme($my-scheme); - $u.authority($my-host); - $u.path($my-path); +```raku +my $u = URI.new; +$u.path(''); +$u.scheme($my-scheme); +$u.authority($my-host); +$u.path($my-path); +``` This is because an empty path is permitted in any case, but the format of the path is limited whether an authority and scheme are set. @@ -110,50 +98,58 @@ With an authority set (i.e., the URI either starts with "//" or with "scheme://" When there's no authority set, but a scheme is used (e.g., it starts with "scheme:", but not "scheme://"), a non-empty path may either start with a "/" or not, but must contain one or more other characters in the first segment of the path. Thus, the following code will fail: - my $u = URI.new('scheme:'); - $u.path('//'); # ERROR: «Could not parse path "//" as part of URI: scheme://» +```raku +my $u = URI.new('scheme:'); +$u.path('//'); # ERROR: «Could not parse path "//" as part of URI: scheme://» +``` When there's no authority and no scheme used, a non-empty path may start with a "/" or not, but must contain one or more other characters in the first segment. -These rules are enforced whenever setting or clearing the scheme, authority, or path. If the resulting URI object would be invalid a `X::URI::Path::Invalid` exception will be thrown. +These rules are enforced whenever setting or clearing the scheme, authority, or path. If the resulting URI object would be invalid an `X::URI::Path::Invalid` exception will be thrown. QUERY ----- -The `query` method of this class returns a `URI::Query` object.This is a special object that is `Positional`, `Associative`, and `Iterable`. That is, it can be bound to an array variable, a hash variable, and iterated using a loop. If stringified, it will return a URI-encoded query. If used as an array, will act like somewhat like an array of `Pair`s. If used as a hash, will provide a map from query keys to query values. +The `query` method of this class returns a `URI::Query` object. This is a special object that is `Positional`, `Associative`, and `Iterable`. That is, it can be bound to an array variable, a hash variable, and iterated using a loop. If stringified, it will return a URI-encoded query. If used as an array, will act like somewhat like an array of `Pair`s. If used as a hash, will provide a map from query keys to query values. The native internal representation is a list of `Pair`s as this is the best match for how queries are defined. Because of this, there's a problem when using a query as a hash: duplicate pairs are valid. To handle this, the `URI::Query` object always returns a list of values, sorted in the order they appear for each key. For example: - my $u = URI.new('?foo=1&bar=2&foo=3'); - say $u.query[0]; #> 1 - say $u.query[1]; #> 3 - say $u.query[0]; #> 2 +```raku +my $u = URI.new('?foo=1&bar=2&foo=3'); +say $u.query[0]; #> 1 +say $u.query[1]; #> 3 +say $u.query[0]; #> 2 +``` Older versions of the URI module handled this differently, using a mixed value representation. In order to gain some backwards compatibility, this is still supported by setting the `hash-format`: - # Continues from previous - $u.query.hash-format = URI::Query::Mixed; - say $u.query[0]; #> 1 - say $u.query[1]; #> 3 +```raku +# Continues from previous +$u.query.hash-format = URI::Query::Mixed; +say $u.query[0]; #> 1 +say $u.query[1]; #> 3 - # The bar value is NOT a list now - say $u.query; #> 2 +# The bar value is NOT a list now +say $u.query; #> 2 - # Return to the default mode - $u.query.hash-format = URI::Query::Lists; +# Return to the default mode +$u.query.hash-format = URI::Query::Lists; +``` However, the list version is safer and may likely work with most existing code that worked with mixed before. Another mode is provided to force single values, which is often how applications treat these out of convenience. In that case, only the last value will be kept: - # Continues from previous - $u.query.hash-format = URI::Query::Singles; +```raku +# Continues from previous +$u.query.hash-format = URI::Query::Singles; - # These are never lists now - say $u.query; #> 3 - say $u.query; #> 2 +# These are never lists now +say $u.query; #> 3 +say $u.query; #> 2 +``` The `URI::Query::Lists` mode is default and recommended mode. @@ -162,15 +158,17 @@ GRAMMAR This class will keep a copy of the result of parsing the URI string for you. If you are interested in precise details of the parse tree, you get them using the `grammar` method: - my $host-in-grammar = - $u.grammar.parse-result; - if $host-in-grammar { - say 'Host looks like registered domain name - approved!'; - } - else { - say 'Sorry we do not take ip address hosts at this time.'; - say 'Please use registered domain name!'; - } +```raku +my $host-in-grammar = + $u.grammar.parse-result; +if $host-in-grammar { + say 'Host looks like registered domain name - approved!'; +} +else { + say 'Sorry we do not take ip address hosts at this time.'; + say 'Please use registered domain name!'; +} +``` The `IETF::RFC_Grammar::URI` grammar sticks close to the BNF defined in RFC 3986. @@ -179,13 +177,15 @@ PARTIAL MATCHING Many times a URI you are interested in is embedded within another string. This class will allow you to parse URIs out of a larger string, so long as the URI is at the start. This is done by setting the `:match-prefix` option during construction or when calling `parse`: - { - # require whole string matches URI and throw exception otherwise .. - my $u_v = URI.new('http://?#?#'); - CATCH { when X::URI::Invalid { ... } } - } +```raku +{ + # require whole string matches URI and throw exception otherwise .. + my $u_v = URI.new('http://?#?#'); + CATCH { when X::URI::Invalid { ... } } +} - my $u_pfx = URI.new('http://example.com } function(var mm){', :match-prefix); +my $u_pfx = URI.new('http://example.com } function(var mm){', :match-prefix); +``` METHODS ======= @@ -193,19 +193,23 @@ METHODS method new ---------- - multi method new(URI:U: Str() $uri, Bool :$match-prefix) returns URI:D - multi method new(URI:U: Str() :$uri, Bool :$match-prefix) returns URI:D +```raku +multi method new(URI:U: Str() $uri, Bool :$match-prefix--> URI:D) +multi method new(URI:U: Str() :$uri, Bool :$match-prefix--> URI:D) +``` These construct a new `URI` object and return it. The given `$uri` value is converted to a string and then parsed using the `parse` method. -If `:match-prefix` is set, then the grammar will be allowed to match a prefix of the given input string rather than requiring a total match. The `:match-prefix` given also becomes the default value for any figure calls to `parse`. +If `:match-prefix` is set, then the grammar will be allowed to match a prefix of the given input string rather than requiring a total match. The `:match-prefix` given also becomes the default value for any future calls to `parse`. Throws a `X::URI::Invalid` exception if the URI cannot be parsed. method parse ------------ - method parse(URI:D: Str() $str, Bool :$match-prefix = $.match-prefix) +```raku +method parse(URI:D: Str() $str, Bool :$match-prefix = $.match-prefix) +``` This method allows an existing URI object to be reused to parse another string. This parses the given string and replaces all internal state of the object with values for the new parse. @@ -216,22 +220,28 @@ Throws a `X::URI::Invalid` exception if the URI cannot be parsed. method grammar -------------- - method grammar(URI:D:) returns IETF::RFC_Grammar:D +```raku +method grammar(URI:D: --> IETF::RFC_Grammar:D) +``` Returns the object used to parse and store the state of the parse. method match-prefix ------------------- - method match-prefix(URI:D:) returns Bool:D +```raku +method match-prefix(URI:D:--> Bool:D) +``` -Returns True if the most recent call to `parse` (or `new`) allowed a prefix match or False if a total match was required. This is the default value of any future call to `parse`. +Returns `True` if the most recent call to `parse` (or `new`) allowed a prefix match or `False` if a total match was required. This is the default value of any future call to `parse`. method scheme ------------- - multi method scheme(URI:D:) returns URI::Scheme:D - multi method scheme(URI:D: Str() $scheme) returns URI::Scheme:D +```raku +multi method scheme(URI:D:--> URI::Scheme:D) +multi method scheme(URI:D: Str() $scheme--> URI::Scheme:D) +``` Returns the scheme part of the URI. This is a string that must match the `URI::Scheme` subset type. @@ -242,9 +252,11 @@ This will throw an `X::URI::Path::Invalid` exception if adding or removing the s method authority ---------------- - multi method authority(URI:D:) returns URI::Authority - multi method authority(URI:D: Nil) returns URI::Authority:U - multi method authority(URI:D: Str() $new) returns URI::Authority:D +```raku +multi method authority(URI:D:--> URI::Authority) +multi method authority(URI:D: Nil--> URI::Authority:U) +multi method authority(URI:D: Str() $new--> URI::Authority:D) +``` Returns the `URI::Authority` for the current URI object. This may be an undefined type object if no authority has been set or found during parse. @@ -259,8 +271,10 @@ The authority is made up of three components: userinfo, host, and port. Addition method userinfo --------------- - multi method userinfo(URI:D:) returns URI::Userinfo:D - multi method userinfo(URI:D: Str() $new) returns URI::Userinfo:D +```raku +multi method userinfo(URI:D:--> URI::Userinfo:D) +multi method userinfo(URI:D: Str() $new--> URI::Userinfo:D) +``` The userinfo is an optional component of the URI authority. This method returns the current userinfo or an empty string. @@ -269,8 +283,10 @@ Setting this method will cause a `URI::Authority` to be constructed and `authori method host ----------- - multi method host(URI:D:) returns URI::Host:D - multi method host(URI:D: Str() $new) returns URI::Host:D +```raku +multi method host(URI:D:--> URI::Host:D) +multi method host(URI:D: Str() $new--> URI::Host:D) +``` The host is a component of the URI authority. This method returns the current host or an empty string. @@ -279,21 +295,27 @@ Setting this method will cause a `URI::Authority` to be constructed and `authori method default-port ------------------- - method default-port(URI:D:) returns URI::Port +```raku +method default-port(URI:D:--> URI::Port) +``` This method applies the `scheme-port` method of `URI::DefaultPort` to the scheme set on this object. Basically, a shortcut for: - my $u = URI.new("..."); - my $port = URI::DefaultPort.scheme-port($u.scheme); +```raku +my $u = URI.new("..."); +my $port = URI::DefaultPort.scheme-port($u.scheme); +``` It returns the usual port for the named scheme. method _port ------------ - multi method _port(URI:D:) returns URI::Port - multi method _port(URI:D: Nil) returns URI::Port:U - multi method _port(URI:D: Int() $new) returns URI::Port:D +```raku +multi method _port(URI:D:--> URI::Port) +multi method _port(URI:D: Nil--> URI::Port:U) +multi method _port(URI:D: Int() $new--> URI::Port:D) +``` When an authority is set on the URI, this gets or sets the authority's port. This differs from `port`, which returns either the port set or the `default-port`. This method returns just the port. @@ -304,9 +326,11 @@ Setting this method will cause a `URI::Authority` to be constructed and `authori method port ----------- - multi method port(URI:D:) returns URI::Port - multi method port(URI:D: Nil) returns URI::Port - multi method port(URI:D: Int() $new) returns URI::Port +```raku +multi method port(URI:D:--> URI::Port) +multi method port(URI:D: Nil--> URI::Port) +multi method port(URI:D: Int() $new--> URI::Port) +``` When retrieving a value from the object, this method returns either the port set on the authority or the default port for the current URI scheme. It may return an undefined value (i.e., an `Int` type object) if there is no port set and no known default port for the current scheme or no scheme set. @@ -317,8 +341,10 @@ Setting this method will cause a `URI::Authority` to be constructed and `authori method path ----------- - multi method path(URI:D:) returns URI::Path:D - multi method path(URI:D: Str() $path) returns URI::Path:D +```raku +multi method path(URI:D:--> URI::Path:D) +multi method path(URI:D: Str() $path--> URI::Path:D) +``` Path is the main required element of a URI, but may be an empty string. This method returns the current setting for the path as a `URI::Path` object. It also allows setting a new path, which will construct a new `URI::Path` object. @@ -327,14 +353,18 @@ This method will throw a `X::URI::Path::Invalid` exception if the path is not va method segments --------------- - multi method segments(URI:D:) returns List:D - multi method segments(URI:D: @segments where *.elems > 0) returns List:D - multi method segments(URI:D: $first-segment, *@remaining-segments) returns List:D +```raku +multi method segments(URI:D:--> List:D) +multi method segments(URI:D: @segments where *.elems > 0--> List:D) +multi method segments(URI:D: $first-segment, *@remaining-segments--> List:D) +``` Returns the path segments (i.e., the parts between slashes). This is a shortcut for: - my $u = URI.new("..."); - my @s = $u.path.segments; +```raku +my $u = URI.new("..."); +my @s = $u.path.segments; +``` The number of segments is equal to the number of slashes in the original path plus one. The segments are constructed so that joining the segments by a slash (/) will give you the original path. @@ -345,9 +375,11 @@ Be sure when setting segments to include an initial empty string if you want the method query ------------ - multi method query(URI:D:) returns URI::Query:D - multi method query(URI:D: Str() $new) returns URI::Query:D - multi method query(URI:D: *@new) returns URI::Query:D +```raku +multi method query(URI:D:--> URI::Query:D) +multi method query(URI:D: Str() $new--> URI::Query:D) +multi method query(URI:D: *@new--> URI::Query:D) +``` Accesses or updates the query associated with the URI. This is returns as a `URI::Query` object. This will always be defined, but may be empty. @@ -358,45 +390,37 @@ When the list form is used, any named parameters passed will be ignored (they wi method path-query ----------------- - method path-query(URI:D:) returns Str:D +```raku +method path-query(URI:D:--> Str:D) +``` Returns the path and query as a string joined by a question mark ("?"). It will return just the path as a string if the query is empty. method fragment --------------- - multi method fragment(URI:D:) returns URI::Fragment:D - multi method fragment(URI:D: Str() $new) returns URI::Fragment:D +```raku +multi method fragment(URI:D:--> URI::Fragment:D) +multi method fragment(URI:D: Str() $new--> URI::Fragment:D) +``` Returns the URI fragment, which is always defined, but may be an empty string. If passed a value, it will set the fragment. method gist ----------- - multi method gist(URI:D:) returns Str:D +```raku +multi method gist(URI:D:--> Str:D) +``` Reconstructs the URI from the components and returns it as a string. -method is-relative ------------------ - - method is-relative returns Bool - -A URI is considered relative, if it doesn't include a scheme or a host and and its path generally refers to an HTML document on the same machine as any current document; - -method rel2abs -------------- - - method rel2abs(URI:D: URI:D $base) returns URI:D - -If the URI object is relative, this method constructs a new URI by cloning `$base` with a new cloned from $base, but with path constructed by calling `$base.path.rel2abs(self.path)` - -If the URI object is not relative, it is returned unaltered. - method Str ---------- - multi method Str(URI:D:) returns Str:D +```raku +multi method Str(URI:D:--> Str:D) +``` Reconstructs the URI from the components and returns it as a string. @@ -406,7 +430,9 @@ SUBROUTINES sub split-query --------------- - sub split-query(Str() $query, :$hash-format = URI::Query::None) +```raku +sub split-query(Str() $query, :$hash-format = URI::Query::None) +``` This routine will slice and dice a query string, which is useful when parsing URIs and may also be useful when parsing POST entities that are encoded as `application/x-www-form-urlencoded`. @@ -416,8 +442,10 @@ With just the required string, this routine will parse that string and return a For example: - my @qf = URI::split-query("foo=1&bar%20=2&foo=3"); - dd @qf; #> Array @qf = [:foo("1"), "bar " => ("2"), :foo("3")] +```raku +my @qf = URI::split-query("foo=1&bar%20=2&foo=3"); +dd @qf; #> Array @qf = [:foo("1"), "bar " => ("2"), :foo("3")] +``` Notice that this will perform a `uri-escape` operation of keys and values in the process so the values you receive have had the URI encoded characters decoded. @@ -429,10 +457,12 @@ The options for `:hash-format` include: Every key is mapped to a list of one or more values. From the example input, a structure like the following is returned: - my %qf = URI::split-query("foo=1&bar%20=2&foo=3", - hash-format => URI::Query::Lists, - ); - dd %qf; #> Hash %qf = {"bar " => (["2"]), :foo(["1", "3"])} +```raku +my %qf = URI::split-query("foo=1&bar%20=2&foo=3", + hash-format => URI::Query::Lists, +); +dd %qf; #> Hash %qf = {"bar " => (["2"]), :foo(["1", "3"])} +``` This is also the default if you just pass `:hash-format` as a boolean option. @@ -440,19 +470,23 @@ This is also the default if you just pass `:hash-format` as a boolean option. Every key is mapped to either a list of two or more values or directly to a single value, like the following: - my %qf = URI::split-query("foo=1&bar%20=2&foo=3", - hash-format => URI::Query::Mixed, - ); - dd %qf; #> Hash %qf = {"bar " => ("2"), :foo(["1", "3"])} +```raku +my %qf = URI::split-query("foo=1&bar%20=2&foo=3", + hash-format => URI::Query::Mixed, +); +dd %qf; #> Hash %qf = {"bar " => ("2"), :foo(["1", "3"])} +``` ### URI::Query::Singles Every key is mapped to a single value, which will be the last value encountered in the input, like this: - my %qf = URI::split-query("foo=1&bar%20=2&foo=3", - hash-format => URI::Query::Mixed, - ); - dd %qf; #> Hash %qf = {"bar " => ("2"), :foo("3")} +```raku +my %qf = URI::split-query("foo=1&bar%20=2&foo=3", + hash-format => URI::Query::Mixed, +); +dd %qf; #> Hash %qf = {"bar " => ("2"), :foo("3")} +``` HELPER SUBSETS ============== @@ -499,36 +533,48 @@ It is recommended that you do not construct a `URI::Authority` object directly, ### method userinfo - method userinfo(URI::Authority:D:) is rw returns URI::Userinfo:D +```raku +method userinfo(URI::Authority:D: --> URI::Userinfo:D) is rw +``` This is a simple setter/getter for the userinfo on the authority. It must be defined, but may be the empty string. If not empty, it must be valid for the userinfo component of a URI. ### method host - method host(URI::Authority:D:) is rw returns URI::Host:D +```raku +method host(URI::Authority:D: --> URI::Host:D) is rw +``` This is a simple setter/getter for the host part of the URI authority. It must be defined, but may be the empty string. If not empty, must be a valid host value, which may be an IP address or registered name. ### method port - method port(URI::Authority:D:) is rw returns URI::Port +```raku +method port(URI::Authority:D: --> URI::Port) is rw +``` This is a simple setter/getter for the port part of the URI authority. It may be set to an undefined value if no explicit port is set in the authority. If defined, it must an unsigned integer. ### method gist - multi method gist(URI::Authority:D:) returns Str:D +```raku +multi method gist(URI::Authority:D:--> Str:D) +``` -Returns the string representation of the URI authority. For example, +Returns the string representation of the URI authority. For example: - my $u = URI.new("http://steve@example.com:8008"); +```raku +my $u = URI.new("http://steve@example.com:8008"); - # say calls .gist - say $u.authority; #> "steve@example.com:8080"; +# say calls .gist +say $u.authority; #> "steve@example.com:8080"; +``` ### method Str - multi method gist(URI::Authority:D:) returns Str:D +```raku +multi method gist(URI::Authority:D:--> Str:D) +``` Stringifies identical to `gist`. @@ -541,33 +587,33 @@ It is recommended that you do not construct a `URI::Path` object directly, but r ### method path - method path(URI::Path:D:) returns Str:D +```raku +method path(URI::Path:D:--> Str:D) +``` Returns the string representation of the path. ### method segments - method segments(URI::Path:D:) returns List:D +```raku +method segments(URI::Path:D:--> List:D) +``` Returns a list representation of the path segments. In a URI, the path segments are the strings between slashes ("/"). -### method rel2abs - - method rel2abs(URI::Path:D: URI::Path:D $base) returns URI::Path:D - -Compute a new path by appending the path object's path to base. - -If the path object already is absolute (has a leading '/'), it is returned unaltered. - ### method gist - method gist(URI::Path:D:) returns Str:D +```raku +method gist(URI::Path:D:--> Str:D) +``` Returns the `path`. ### method Str - method Str(URI::Path:D:) returns Str:D +```raku +method Str(URI::Path:D:--> Str:D) +``` Returns the `path`. @@ -582,8 +628,10 @@ The performance of the associative methods is not guaranteed and is probably goi ### method new - multi method new(Str() $query, URI::Query::HashFormat :$hash-format = URI::Query::Lists) returns URI::Query:D - multi method new(:$query, URI::Query::HashFormat :$hash-format = URI::Query::Lists) returns URI::Query:D +```raku +multi method new(Str() $query, URI::Query::HashFormat :$hash-format = URI::Query::Lists--> URI::Query:D) +multi method new(:$query, URI::Query::HashFormat :$hash-format = URI::Query::Lists--> URI::Query:D) +``` Constructs a new `URI::Query` from the given string, which may be empty. @@ -617,14 +665,18 @@ This value should not be used, but will be treated the same as `URI::Query::List ### method hash-format - method hash-format(URI::Query:D) is rw returns URI::Query::HashFormat +```raku +method hash-format(URI::Query:D --> URI::Query::HashFormat) is rw +``` This is a simple setter/getter for setting the way in which associative lookups are performed. See `enum URI::Query::HashFormat` for a description of each mode. ### method query - method query(Query:D:) returns URI::Query::ValidQuery:D - method query(Query:D: Str() $new) returns URI::Query::ValidQuery:D +```raku +method query(Query:D:--> URI::Query::ValidQuery:D) +method query(Query:D: Str() $new--> URI::Query::ValidQuery:D) +``` This method returns the string representation of the URI query component. When passed a string, it will replace the query with the new value and will use `split-query` to parse that query into an array of `Pair`s. @@ -632,21 +684,25 @@ The primary representation of the queryo object is the value returned by `query- ### method query-form - method query-form(Query:D:) returns Array:D - method query-form(Query:D: *@new, *%new) returns Array:D +```raku +method query-form(Query:D:--> Array:D) +method query-form(Query:D: *@new, *%new--> Array:D) +``` This method returns the array of `Pair`s that store the internal representation of the URI query component. When passed an array of `Pair`s, it will replace the current value with that array. A quick note about the way pairs are passed as parameters to a method, you most likely want to avoid passing values as named parameters. If values are passed using unquoted strings, they will be treated as named parameters, which is most likely what you want: - my $q = URI::Query.new; +```raku +my $q = URI::Query.new; - # Prefer this - $q.query-form('foo' => 1, 'bar' => 2, 'foo' => 3); +# Prefer this +$q.query-form('foo' => 1, 'bar' => 2, 'foo' => 3); - # Avoid these - $q.query-form(foo => 1, bar => 2, foo => 3); - $q.query-form(:foo(1), :bar(2), :foo(3)); +# Avoid these +$q.query-form(foo => 1, bar => 2, foo => 3); +$q.query-form(:foo(1), :bar(2), :foo(3)); +``` The latter two will result in the first "foo" pair being lost. Named parameters assume unique names and the latter "foo" key will effectively override the former. @@ -654,133 +710,177 @@ That said, the method will allow hashes to be passed in, if that is your prefere ### method of - method of() +```raku +method of() +``` Always returns `Pair`. ### method iterator - method iterator(Query:D:) returns Iterator:D +```raku +method iterator(Query:D:--> Iterator:D) +``` Returns the iterator on the internal array of `Pair`s. ### method postcircumflex:<[ ]> - method postcircumflex:<[ ]> returns Pair +```raku +method postcircumflex:<[ ]> returns Pair +``` This permits positional access to each `Pair` stored internally. You may use this to get a `Pair`, set a `Pair`, test for existence, or delete. ### method postcircumflex:<{ }> - method postcircumflex:<{ }> +```raku +method AT-KEY(key) +``` This permits associative access to the values stored internally by key. What is returned here when fetching values depends on the setting in `hash-format`, a list of one or more values or `Nil`, by default. You can use this for getting, setting, existence testing, or deletion. ### method keys - method keys(Query:D:) returns Seq:D +```raku +method keys(Query:D:--> Seq:D) +``` This method returns all the keys of the query in order. ### method values - method values(Query:D:) returns Seq:D +```raku +method values(Query:D:--> Seq:D) +``` This method returns all the values of the query in order. ### method kv - method kv(Query:D:) returns Seq:D +```raku +method kv(Query:D:--> Seq:D) +``` This method returns a sequence alternating the keys and values of the query in order. ### method pairs - method kv(Query:D:) returns Seq:D +```raku +method kv(Query:D:--> Seq:D) +``` This method returns a copy of the internal representation of the query string array. ### method pop - method pop(Query:D:) returns Pair +```raku +method pop(Query:D:--> Pair) +``` -This method removes the last Pair from the array of pairs and returns it. +This method removes the last `Pair` from the array of pairs and returns it. ### method push - method push(Query:D: *@new) +```raku +method push(Query:D: *@new) +``` -This method adds the given pairs to the end of the array of pairs in the query using push semantics. +This method adds the given pairs to the end of the array of pairs in the query using `push` semantics. ### method append - method append(Query:D: *@new) +```raku +method append(Query:D: *@new) +``` -This method adds the given pairs to the end of the array of pairs in the query using append semantics. +This method adds the given pairs to the end of the array of pairs in the query using `append` semantics. ### method shift - method shift(Query:D:) returns Pair +```raku +method shift(Query:D:--> Pair) +``` -This method removes the first Pair from the array of pairs and returns it. +This method removes the first `Pair` from the array of pairs and returns it. ### method unshift - method unshift(Query:D: *@new) +```raku +method unshift(Query:D: *@new) +``` -This method adds the given pairs to the front of the array of pairs in the query using unshift semantics. +This method adds the given pairs to the front of the array of pairs in the query using `unshift` semantics. ### method prepend - method prepend(Query:D: *@new) +```raku +method prepend(Query:D: *@new) +``` -This method adds the given pairs to the front of the array of pairs in the query using prepend semantics. +This method adds the given pairs to the front of the array of pairs in the query using `prepend` semantics. ### method splice - method splice(Query:D: $start, $elems?, *@replacement) +```raku +method splice(Query:D: $start, $elems?, *@replacement) +``` This method removes a `$elems` number of pairs from the array of pairs in the query starting at index `$start`. It then inserts the pairs in `@replacement` into that part of the array (if any are given). ### method elems - method elems(Query:D:) returns Int:D +```raku +method elems(Query:D:--> Int:D) +``` Returns the number of pairs stored in the query. ### method end - method end(Query:D:) returns Int:D +```raku +method end(Query:D:--> Int:D) +``` Returns the index of the last pair stored in the query. ### method Bool - method Bool(Query:D:) returns Bool:D +```raku +method Bool(Query:D:--> Bool:D) +``` Returns `True` if the at least one pair is stored in the query or `False` otherwise. ### method Int - method Int(Query:D:) returns Int:D +```raku +method Int(Query:D:--> Int:D) +``` Returns `elems`. ### method Numeric - method Numeric(Query:D:) returns Int:D +```raku +method Numeric(Query:D:--> Int:D) +``` Returns `elems`. ### method gist - method gist(Query:D:) returns Str:D +```raku +method gist(Query:D:--> Str:D) +``` Returns the `query`. ### method Str - method Str(Query:D:) returns Str:D +```raku +method Str(Query:D:--> Str:D) +``` Returns the `query`. @@ -803,6 +903,19 @@ The `source` field will name the invalid URI. Strictly speaking, the URI might b In cases where the segments have been modified in an invalid way, the first invalid segment will be set in `bad-segment`. +POTENTIALLY INCOMPATIBLE CHANGES BETWEEN v0.2.2 and v0.3.0 +========================================================== + +The v0.3.0 introduced the ability to mutate the parts of an existing URI, which in turn may have introduced changes which are incompatible with existing applications: + + * URI.query now returns an object of type URI::Query not a Str The object will coerce correctly when interpolated into a string but will need an explicit .Str coercion if being assigned to a Str typed variable. + + * URI.path now returns an object of type URI::Path not a Str The object will coerce correctly when interpolated into a string but will need an explicit .Str coercion if being assigned to a Str typed variable. + + * URI.query-form no longer returns a Hash but an object of URI::Query The object does the Associative role so for the most part can be treated like a Hash but an explicit .Hash coercion may be required when comparing with another Hash or when merging with another Hash. Some uses of query-form have been marked as deprecated and should use .query instead. + +The changes have been tested with the majority of modules that depend on URI and only a few required changes. + AUTHORS ======= @@ -848,10 +961,14 @@ Contributors to this module include: * Sterling Hanenkamp (zostay) +Source can be located at: https://github.com/raku-community-modules/URI . Comments and Pull Requests are welcome. + COPYRIGHT & LICENSE =================== -Copyright © 2017 Ronald Schmidt. - © 2015 - Raku Community Authors +Copyright 2017 Ronald Schmidt. + +Copyright 2015 - 2024 Raku Community Authors + +This software is licensed under the Artistic 2.0 License. -This software is licensed under the [Artistic 2.0 License](LICENSE). diff --git a/dist.ini b/dist.ini new file mode 100644 index 0000000..dbaf877 --- /dev/null +++ b/dist.ini @@ -0,0 +1,11 @@ +name = URI + +[ReadmeFromPod] +filename = doc/URI.rakudoc + +[UploadToZef] + +[Badges] +provider = github-actions/linux.yml +provider = github-actions/macos.yml +provider = github-actions/windows.yml diff --git a/doc/URI.rakudoc b/doc/URI.rakudoc new file mode 100644 index 0000000..1d4a742 --- /dev/null +++ b/doc/URI.rakudoc @@ -0,0 +1,1292 @@ +=begin pod + +=head1 NAME + +URI — Uniform Resource Identifiers (absolute and relative) + +=head1 SYNOPSIS + +=begin code :lang + +use URI; +my $u = URI.new('http://example.com/foo/bar?tag=woow#bla'); + +# Look at the parts +say $u.scheme; #> http +say $u.authority; #> example.com +say $u.host; #> example.com +say $u.port; #> 80 +say $u.path; #> /foo/bar +say $u.query; #> tag=woow +say $u.fragment; #> bla + +# Modify the parts +$u.scheme('https'); +$u.authority('example.com:8443'); +$u.path('/bar/foo'); +$u.query('x=1&y=2'); # OR +$u.fragment('cool'); +say "$u"; #> https://example.com:8443/bar/foo?x=1&y=2#cool + +# Authority is an object, but may be undefined +with $u.authority { + say .userinfo; # none set, no output + say .host; #> example.com + say .port; #> 8443 + + # It is mutable too + .userinfo('bob'); + say $u.authority; #> bob@example.com:8443 +} + +# Path is an object, always defined, but immutable +with $u.path { + say .path; #> /bar/foo + .say for .segments; #> bar\nfoo\n +} + +# Query is an object, always defined, mutable +with $u.query { + say .query; #> x=1&y=2 + .query('foo=1&foo=2'); + say .query-form[0]; #> 1 + say .query-form[1]; #> 2 + + .query-form.push: 'bar' => 'ok'; + say .query; #> foo=1&foo=2&bar=ok + + .query(''); + .query-form = 123; + say .query; #> abc=123 +} + +=end code + +=head1 DESCRIPTION + +A URI object represents a parsed string that abides by the grammar +defined in RFC 3986 for Universal Resource Identifiers. The object +then works to enforce the rules defined in the RFC for any modifications +made to it. + +As of this writing, The URI class is scheme agnostic. It will verify +that URI is correct, in general, but not correct for a given scheme. +For example, C would be considered a valid URI even though +that is not a valid URI according to the rules specific to the C +scheme. + +This class uses "heavy accessors". From the SYNOPSIS, you may have +noted that assignment to accessors is not used. This is because nearly +all accessors in this class do some extra work of parsing, validating, +and cross-referencing with other fields to guarantee that the URI is +correct before making a change. + +=begin code :lang + +my $u = URI.new; +$u.path = '/foo/bar'; # ERROR: «Cannot modify an immutable URI::Path» +$u.path('/foo/bar'); # WORKS! + +=end code + +This mutator pattern is meant to reflect the internal complexity. + +=head2 SCHEME, AUTHORITY, AND PATH + +In RFC 3986 URIs, the scheme, the authority, and the path are related. +This should not matter most of the time, but to avoid problems when +setting these three, it is safest to set them in this order: + +=begin code :lang + +my $u = URI.new; +$u.path(''); +$u.scheme($my-scheme); +$u.authority($my-host); +$u.path($my-path); + +=end code + +This is because an empty path is permitted in any case, but the format +of the path is limited whether an authority and scheme are set. + +With an authority set (i.e., the URI either starts with "//" or with +"scheme://"), a non-empty path must start with a "/" and may start with +empty path segments, e.g. "scheme://foo//" is valid. + +When there's no authority set, but a scheme is used (e.g., it starts +with "scheme:", but not "scheme://"), a non-empty path may either start +with a "/" or not, but must contain one or more other characters in the +first segment of the path. Thus, the following code will fail: + +=begin code :lang + +my $u = URI.new('scheme:'); +$u.path('//'); # ERROR: «Could not parse path "//" as part of URI: scheme://» + +=end code + +When there's no authority and no scheme used, a non-empty path may +start with a "/" or not, but must contain one or more other characters +in the first segment. + +These rules are enforced whenever setting or clearing the scheme, +authority, or path. If the resulting URI object would be invalid an +C exception will be thrown. + +=head2 QUERY + +The C method of this class returns a C object. +This is a special object that is C, C, and +C. That is, it can be bound to an array variable, a hash +variable, and iterated using a loop. If stringified, it will return +a URI-encoded query. If used as an array, will act like somewhat like +an array of Cs. If used as a hash, will provide a map from +query keys to query values. + +The native internal representation is a list of Cs as this is +the best match for how queries are defined. Because of this, there's +a problem when using a query as a hash: duplicate pairs are valid. +To handle this, the C object always returns a list of +values, sorted in the order they appear for each key. + +For example: + +=begin code :lang + +my $u = URI.new('?foo=1&bar=2&foo=3'); +say $u.query[0]; #> 1 +say $u.query[1]; #> 3 +say $u.query[0]; #> 2 + +=end code + +Older versions of the URI module handled this differently, using a mixed +value representation. In order to gain some backwards compatibility, +this is still supported by setting the C: + +=begin code :lang + +# Continues from previous +$u.query.hash-format = URI::Query::Mixed; +say $u.query[0]; #> 1 +say $u.query[1]; #> 3 + +# The bar value is NOT a list now +say $u.query; #> 2 + +# Return to the default mode +$u.query.hash-format = URI::Query::Lists; + +=end code + +However, the list version is safer and may likely work with most existing +code that worked with mixed before. + +Another mode is provided to force single values, which is often how +applications treat these out of convenience. In that case, only the last +value will be kept: + +=begin code :lang + +# Continues from previous +$u.query.hash-format = URI::Query::Singles; + +# These are never lists now +say $u.query; #> 3 +say $u.query; #> 2 + +=end code + +The C mode is default and recommended mode. + +=head2 GRAMMAR + +This class will keep a copy of the result of parsing the URI string +for you. If you are interested in precise details of the parse tree, +you get them using the C method: + +=begin code :lang + +my $host-in-grammar = + $u.grammar.parse-result; +if $host-in-grammar { + say 'Host looks like registered domain name - approved!'; +} +else { + say 'Sorry we do not take ip address hosts at this time.'; + say 'Please use registered domain name!'; +} + +=end code + +The C grammar sticks close to the BNF defined +in RFC 3986. + +=head2 PARTIAL MATCHING + +Many times a URI you are interested in is embedded within another +string. This class will allow you to parse URIs out of a larger string, +so long as the URI is at the start. This is done by setting the +C<:match-prefix> option during construction or when calling C: + +=begin code :lang + +{ + # require whole string matches URI and throw exception otherwise .. + my $u_v = URI.new('http://?#?#'); + CATCH { when X::URI::Invalid { ... } } +} + +my $u_pfx = URI.new('http://example.com } function(var mm){', :match-prefix); + +=end code + +=head1 METHODS + +=head2 method new + +=begin code :lang + +multi method new(URI:U: Str() $uri, Bool :$match-prefix--> URI:D) +multi method new(URI:U: Str() :$uri, Bool :$match-prefix--> URI:D) + +=end code + +These construct a new C object and return it. The given C<$uri> +value is converted to a string and then parsed using the C method. + +If C<:match-prefix> is set, then the grammar will be allowed to match +a prefix of the given input string rather than requiring a total match. +The C<:match-prefix> given also becomes the default value for any future +calls to C. + +Throws a C exception if the URI cannot be parsed. + +=head2 method parse + +=begin code :lang + +method parse(URI:D: Str() $str, Bool :$match-prefix = $.match-prefix) + +=end code + +This method allows an existing URI object to be reused to parse another +string. This parses the given string and replaces all internal state of +the object with values for the new parse. + +The given C<:match-prefix> flag becomes the new default when set. + +Throws a C exception if the URI cannot be parsed. + +=head2 method grammar + +=begin code :lang + +method grammar(URI:D: --> IETF::RFC_Grammar:D) + +=end code + +Returns the object used to parse and store the state of the parse. + +=head2 method match-prefix + +=begin code :lang + +method match-prefix(URI:D:--> Bool:D) + +=end code + +Returns C if the most recent call to C (or C) +allowed a prefix match or C if a total match was required. +This is the default value of any future call to C. + +=head2 method scheme + +=begin code :lang + +multi method scheme(URI:D:--> URI::Scheme:D) +multi method scheme(URI:D: Str() $scheme--> URI::Scheme:D) + +=end code + +Returns the scheme part of the URI. This is a string that must match +the C subset type. + +The second form allows the scheme to be replaced with a new scheme. +It is legal for the scheme to be set to an empty string, which has +the effect of making the URI scheme-less. + +This will throw an C exception if adding or +removing the scheme will make the URI invalid. See SCHEME, AUTHORITY, +AND PATH section for additional details. + +=head2 method authority + +=begin code :lang + +multi method authority(URI:D:--> URI::Authority) +multi method authority(URI:D: Nil--> URI::Authority:U) +multi method authority(URI:D: Str() $new--> URI::Authority:D) + +=end code + +Returns the C for the current URI object. This may +be an undefined type object if no authority has been set or found +during parse. + +When passed a string, the string will have the authority parsed out +of it and a new authority object will be used to store the parsed +information. An empty authority is valid. + +When passed C, the authority will be cleared. + +When passing an argument, this will throw an C +exception if setting or clearing the authority on the URI will make +the URI invalid. See SCHEME, AUTHORITY, AND PATH section for details. + +The authority is made up of three components: userinfo, host, and +port. Additional methods are provided for accessing each of those +parts separately. + +=head2 method userinfo + +=begin code :lang + +multi method userinfo(URI:D:--> URI::Userinfo:D) +multi method userinfo(URI:D: Str() $new--> URI::Userinfo:D) + +=end code + +The userinfo is an optional component of the URI authority. This +method returns the current userinfo or an empty string. + +Setting this method will cause a C to be constructed +and C to be set, if it is not already defined. This may +result in a C exception being thrown if adding +an authority will make the path invalid. + +=head2 method host + +=begin code :lang + +multi method host(URI:D:--> URI::Host:D) +multi method host(URI:D: Str() $new--> URI::Host:D) + +=end code + +The host is a component of the URI authority. This method returns +the current host or an empty string. + +Setting this method will cause a C to be constructed +and C to be set, if it is not already defined. This may +result in a C exception being thrown if adding +an authority will make the path invalid. + +=head2 method default-port + +=begin code :lang + +method default-port(URI:D:--> URI::Port) + +=end code + +This method applies the C method of C +to the scheme set on this object. Basically, a shortcut for: + +=begin code :lang + +my $u = URI.new("..."); +my $port = URI::DefaultPort.scheme-port($u.scheme); + +=end code + +It returns the usual port for the named scheme. + +=head2 method _port + +=begin code :lang + +multi method _port(URI:D:--> URI::Port) +multi method _port(URI:D: Nil--> URI::Port:U) +multi method _port(URI:D: Int() $new--> URI::Port:D) + +=end code + +When an authority is set on the URI, this gets or sets the authority's +port. This differs from C, which returns either the port set or +the C. This method returns just the port. + +If no authority is set or no port is set, this returns an undefined +value (i.e., an C type object). + +Setting this method will cause a C to be constructed +and C to be set, if it is not already defined. This may +result in a C exception being thrown if adding +an authority will make the path invalid. + +=head2 method port + +=begin code :lang + +multi method port(URI:D:--> URI::Port) +multi method port(URI:D: Nil--> URI::Port) +multi method port(URI:D: Int() $new--> URI::Port) + +=end code + +When retrieving a value from the object, this method returns either +the port set on the authority or the default port for the current URI +scheme. It may return an undefined value (i.e., an C type object) +if there is no port set and no known default port for the current scheme +or no scheme set. + +When setting a value on the object, this method sets the port on the +authority. + +Setting this method will cause a C to be constructed +and C to be set, if it is not already defined. This may +result in a C exception being thrown if adding +an authority will make the path invalid. + +=head2 method path + +=begin code :lang + +multi method path(URI:D:--> URI::Path:D) +multi method path(URI:D: Str() $path--> URI::Path:D) + +=end code + +Path is the main required element of a URI, but may be an empty string. +This method returns the current setting for the path as a C +object. It also allows setting a new path, which will construct a new +C object. + +This method will throw a C exception if the path +is not valid for the current scheme and authority settings. + +=head2 method segments + +=begin code :lang + +multi method segments(URI:D:--> List:D) +multi method segments(URI:D: @segments where *.elems > 0--> List:D) +multi method segments(URI:D: $first-segment, *@remaining-segments--> List:D) + +=end code + +Returns the path segments (i.e., the parts between slashes). This is a +shortcut for: + +=begin code :lang + +my $u = URI.new("..."); +my @s = $u.path.segments; + +=end code + +The number of segments is equal to the number of slashes in the original +path plus one. The segments are constructed so that joining the segments +by a slash (/) will give you the original path. + +You may pass a list in to replace the segments with a new value. This +will throw a C exception if any of the segments +contain a slash or if the set path will cause the URI to become invalid. + +Be sure when setting segments to include an initial empty string if you +want the path to start with a slash. + +=head2 method query + +=begin code :lang + +multi method query(URI:D:--> URI::Query:D) +multi method query(URI:D: Str() $new--> URI::Query:D) +multi method query(URI:D: *@new--> URI::Query:D) + +=end code + +Accesses or updates the query associated with the URI. This is returns +as a C object. This will always be defined, but may be empty. + +When passed a string, the string will be parsed using C +and the query object will be updated. When passed a list, the list +of Cs given will be used to setup a new query that way. + +When the list form is used, any named parameters passed will be ignored +(they will not be used to fill the query) and a warning will be issued. + +=head2 method path-query + +=begin code :lang + +method path-query(URI:D:--> Str:D) + +=end code + +Returns the path and query as a string joined by a question mark ("?"). +It will return just the path as a string if the query is empty. + +=head2 method fragment + +=begin code :lang + +multi method fragment(URI:D:--> URI::Fragment:D) +multi method fragment(URI:D: Str() $new--> URI::Fragment:D) + +=end code + +Returns the URI fragment, which is always defined, but may be an empty +string. If passed a value, it will set the fragment. + +=head2 method gist + +=begin code :lang + +multi method gist(URI:D:--> Str:D) + +=end code + +Reconstructs the URI from the components and returns it as a string. + +=head2 method Str + +=begin code :lang + +multi method Str(URI:D:--> Str:D) + +=end code + +Reconstructs the URI from the components and returns it as a string. + +=head1 SUBROUTINES + +=head2 sub split-query + +=begin code :lang + +sub split-query(Str() $query, :$hash-format = URI::Query::None) + +=end code + +This routine will slice and dice a query string, which is useful when +parsing URIs and may also be useful when parsing POST entities that +are encoded as C. + +This routine is exported with the C<:split-query> tag or can be used +with the full namespace, C. + +With just the required string, this routine will parse that string and +return a list of Cs mapping each query form key to its respective +value. The order is preserved. This is used by C during +construction. + +For example: + +=begin code :lang + +my @qf = URI::split-query("foo=1&bar%20=2&foo=3"); +dd @qf; #> Array @qf = [:foo("1"), "bar " => ("2"), :foo("3")] + +=end code + +Notice that this will perform a C operation of keys and +values in the process so the values you receive have had the URI +encoded characters decoded. + +You can retrieve the query string as a hash instead by passing the +C<:hash-format> option. This works exactly as it does for +C. + +The options for C<:hash-format> include: + +=head3 URI::Query::Lists + +Every key is mapped to a list of one or more values. From the example +input, a structure like the following is returned: + +=begin code :lang + +my %qf = URI::split-query("foo=1&bar%20=2&foo=3", + hash-format => URI::Query::Lists, +); +dd %qf; #> Hash %qf = {"bar " => (["2"]), :foo(["1", "3"])} + +=end code + +This is also the default if you just pass C<:hash-format> as a +boolean option. + +=head3 URI::Query::Mixed + +Every key is mapped to either a list of two or more values or +directly to a single value, like the following: + +=begin code :lang + +my %qf = URI::split-query("foo=1&bar%20=2&foo=3", + hash-format => URI::Query::Mixed, +); +dd %qf; #> Hash %qf = {"bar " => ("2"), :foo(["1", "3"])} + +=end code + +=head3 URI::Query::Singles + +Every key is mapped to a single value, which will be the last +value encountered in the input, like this: + +=begin code :lang + +my %qf = URI::split-query("foo=1&bar%20=2&foo=3", + hash-format => URI::Query::Mixed, +); +dd %qf; #> Hash %qf = {"bar " => ("2"), :foo("3")} + +=end code + +=head1 HELPER SUBSETS + +=head2 URI::Scheme + +This is a subset of C that only accepts valid schemes. + +=head2 URI::Userinfo + +This is a subset of C that only accepts valid userinfo. + +=head2 URI::Host + +This is a subset of C that only accepts valid hosts. + +=head2 URI::Port + +This is a subset of C. + +=head2 URI::Query::ValidQuery + +This is a subset of C that only accepts valid query strings. + +=head2 URI::Fragment + +This is a subset of C that only accepts valid URI fragments. + +=head1 HELPER CLASSES + +=head2 URI::Authority + +The C method of a URI constructs or returns this object. + +It is recommended that you do not construct a C +object directly, but let the methods of C handle construction. + +=head3 method userinfo + +=begin code :lang + +method userinfo(URI::Authority:D: --> URI::Userinfo:D) is rw + +=end code + +This is a simple setter/getter for the userinfo on the authority. +It must be defined, but may be the empty string. If not empty, it +must be valid for the userinfo component of a URI. + +=head3 method host + +=begin code :lang + +method host(URI::Authority:D: --> URI::Host:D) is rw + +=end code + +This is a simple setter/getter for the host part of the URI authority. +It must be defined, but may be the empty string. If not empty, must +be a valid host value, which may be an IP address or registered name. + +=head3 method port + +=begin code :lang + +method port(URI::Authority:D: --> URI::Port) is rw + +=end code + +This is a simple setter/getter for the port part of the URI authority. +It may be set to an undefined value if no explicit port is set in the +authority. If defined, it must an unsigned integer. + +=head3 method gist + +=begin code :lang + +multi method gist(URI::Authority:D:--> Str:D) + +=end code + +Returns the string representation of the URI authority. For example: + +=begin code :lang + +my $u = URI.new("http://steve@example.com:8008"); + +# say calls .gist +say $u.authority; #> "steve@example.com:8080"; + +=end code + +=head3 method Str + +=begin code :lang + +multi method gist(URI::Authority:D:--> Str:D) + +=end code + +Stringifies identical to C. + +=head2 URI::Path + +This class is used to represent URI path components. + +It is recommended that you do not construct a C object +directly, but rely on the C setter in C instead. These +objects are immutable. Please use methods on C to make changes. + +=head3 method path + +=begin code :lang + +method path(URI::Path:D:--> Str:D) + +=end code + +Returns the string representation of the path. + +=head3 method segments + +=begin code :lang + +method segments(URI::Path:D:--> List:D) + +=end code + +Returns a list representation of the path segments. In a URI, the +path segments are the strings between slashes ("/"). + +=head3 method gist + +=begin code :lang + +method gist(URI::Path:D:--> Str:D) + +=end code + +Returns the C. + +=head3 method Str + +=begin code :lang + +method Str(URI::Path:D:--> Str:D) + +=end code + +Returns the C. + +=head2 URI::Query + +This class is used to represent a URI query component. This class may +be safely constructed and used independently of the URI object. + +It behaves as both a positional and associative object and is iterable. +Internally, it is stored as an C of Cs. You must not treat +this object purely as you would an C or purely as you would a +C as some methods will not work the way you expect. + +The performance of the associative methods is not guaranteed and is +probably going to be relatively slow. This implementation values +simplicity and accuracy of representation to CPU performance. If you +need something that is better for CPU performance, you should +investigate the use of another library, such as C +or sub-class to provide a higher performance implementation of +C's associative methods. + +=head3 method new + +=begin code :lang + +multi method new(Str() $query, URI::Query::HashFormat :$hash-format = URI::Query::Lists--> URI::Query:D) +multi method new(:$query, URI::Query::HashFormat :$hash-format = URI::Query::Lists--> URI::Query:D) + +=end code + +Constructs a new C from the given string, which may be empty. + +Unlike C, which permits boolean values for the +C option, C requires a C +value and will reject a boolean (because the boolean does not make sense +in this case). + +=head3 enum HashFormat + +This enumeration provides the values that may be set on C +on a C. Possible values are as follows. + +=head4 URI::Query::List + +This is the default and recommended value. When set in C, +each key in the hash representation will point to a list of one or more +values. + +This is recommended as it is the most accurate representation to what +is actually possible in a query string. The values within each key will +be sorted in the same order as they appear in the query. + +=head4 URI::Query::Mixed + +When set in C, each key in the hash representation may +be mapped to either a list of two or more values, or to the value +itself if there is only one. + +This is not recommended because it means that setting a key to an +iterable value will be treated as multiple key-value pairs when it +comes time to render the query as a string. This could have unintended +consequences. + +=head4 URI::Query::Singles + +When set in C, each key in the hash representation will +be mapped directly to a single value. If there are multiple values +that have been set in the query, then only the last will be visible +through the hash representation. + +This is not recommended because it may hide certain values, but may +be useful for simple applications that treat the query string as +having unique values. Just note that the trade-off is that your +application may be confused when multiple values are present. + +=head4 URI::Query::None + +This value should not be used, but will be treated the same as +C if used here. It is provided for use with +C only. + +=head3 method hash-format + +=begin code :lang + +method hash-format(URI::Query:D --> URI::Query::HashFormat) is rw + +=end code + +This is a simple setter/getter for setting the way in which associative +lookups are performed. See C for a +description of each mode. + +=head3 method query + +=begin code :lang + +method query(Query:D:--> URI::Query::ValidQuery:D) +method query(Query:D: Str() $new--> URI::Query::ValidQuery:D) + +=end code + +This method returns the string representation of the URI query +component. When passed a string, it will replace the query with +the new value and will use C to parse that query +into an array of Cs. + +The primary representation of the queryo object is the value returned +by C. The C is cached to keep the value stored in +it when this method is called to set it or when C is +constructed from a string. Any modification to the internal array of +pairs, though, will clear this cache. It will be generated the next +time the C method is called and that string will be cached. + +=head3 method query-form + +=begin code :lang + +method query-form(Query:D:--> Array:D) +method query-form(Query:D: *@new, *%new--> Array:D) + +=end code + +This method returns the array of Cs that store the internal +representation of the URI query component. When passed an array of +Cs, it will replace the current value with that array. + +A quick note about the way pairs are passed as parameters to a method, +you most likely want to avoid passing values as named parameters. +If values are passed using unquoted strings, they will be treated +as named parameters, which is most likely what you want: + +=begin code :lang + +my $q = URI::Query.new; + +# Prefer this +$q.query-form('foo' => 1, 'bar' => 2, 'foo' => 3); + +# Avoid these +$q.query-form(foo => 1, bar => 2, foo => 3); +$q.query-form(:foo(1), :bar(2), :foo(3)); + +=end code + +The latter two will result in the first "foo" pair being lost. +Named parameters assume unique names and the latter "foo" key +will effectively override the former. + +That said, the method will allow hashes to be passed in, if +that is your preference. + +=head3 method of + +=begin code :lang + +method of() + +=end code + +Always returns C. + +=head3 method iterator + +=begin code :lang + +method iterator(Query:D:--> Iterator:D) + +=end code + +Returns the iterator on the internal array of Cs. + +=head3 method postcircumflex:<[ ]> + +=begin code :lang + +method postcircumflex:<[ ]> returns Pair + +=end code + +This permits positional access to each C stored internally. +You may use this to get a C, set a C, test for existence, +or delete. + +=head3 method postcircumflex:<{ }> + +=begin code :lang + +method AT-KEY(key) + +=end code + +This permits associative access to the values stored internally +by key. What is returned here when fetching values depends on the +setting in C, a list of one or more values or C, +by default. You can use this for getting, setting, existence +testing, or deletion. + +=head3 method keys + +=begin code :lang + +method keys(Query:D:--> Seq:D) + +=end code + +This method returns all the keys of the query in order. + +=head3 method values + +=begin code :lang + +method values(Query:D:--> Seq:D) + +=end code + +This method returns all the values of the query in order. + +=head3 method kv + +=begin code :lang + +method kv(Query:D:--> Seq:D) + +=end code + +This method returns a sequence alternating the keys and values +of the query in order. + +=head3 method pairs + +=begin code :lang + +method kv(Query:D:--> Seq:D) + +=end code + +This method returns a copy of the internal representation of +the query string array. + +=head3 method pop + +=begin code :lang + +method pop(Query:D:--> Pair) + +=end code + +This method removes the last C from the array of pairs +and returns it. + +=head3 method push + +=begin code :lang + +method push(Query:D: *@new) + +=end code + +This method adds the given pairs to the end of the array of +pairs in the query using C semantics. + +=head3 method append + +=begin code :lang + +method append(Query:D: *@new) + +=end code + +This method adds the given pairs to the end of the array of +pairs in the query using C semantics. + +=head3 method shift + +=begin code :lang + +method shift(Query:D:--> Pair) + +=end code + +This method removes the first C from the array of pairs +and returns it. + +=head3 method unshift + +=begin code :lang + +method unshift(Query:D: *@new) + +=end code + +This method adds the given pairs to the front of the array of +pairs in the query using C semantics. + +=head3 method prepend + +=begin code :lang + +method prepend(Query:D: *@new) + +=end code + +This method adds the given pairs to the front of the array of +pairs in the query using C semantics. + +=head3 method splice + +=begin code :lang + +method splice(Query:D: $start, $elems?, *@replacement) + +=end code + +This method removes a C<$elems> number of pairs from the array +of pairs in the query starting at index C<$start>. It then inserts +the pairs in C<@replacement> into that part of the array (if any +are given). + +=head3 method elems + +=begin code :lang + +method elems(Query:D:--> Int:D) + +=end code + +Returns the number of pairs stored in the query. + +=head3 method end + +=begin code :lang + +method end(Query:D:--> Int:D) + +=end code + +Returns the index of the last pair stored in the query. + +=head3 method Bool + +=begin code :lang + +method Bool(Query:D:--> Bool:D) + +=end code + +Returns C if the at least one pair is stored in the query +or C otherwise. + +=head3 method Int + +=begin code :lang + +method Int(Query:D:--> Int:D) + +=end code + +Returns C. + +=head3 method Numeric + +=begin code :lang + +method Numeric(Query:D:--> Int:D) + +=end code + +Returns C. + +=head3 method gist + +=begin code :lang + +method gist(Query:D:--> Str:D) + +=end code + +Returns the C. + +=head3 method Str + +=begin code :lang + +method Str(Query:D:--> Str:D) + +=end code + +Returns the C. + +=head1 EXCEPTIONS + +=head2 X::URI::Invalid + +This exception is thrown in many places where the URI is being parsed +or manipulated. If the string being parsed is not a valid URI or if +certain manipulations of the URI object would cause it to become an +invalid URI, this exception may be used. + +It provides a C accessor, which returns the string that was +determined to be invalid. + +=head2 X::URI::Path::Invalid + +In some cases where an attempt is made to set C to an invalid +value, this exception is thrown. + +The C field will name the invalid URI. Strictly speaking, the +URI might be valid, but will not parse the same way as given. To make +it clear that this is the case, the C field names the invalid +path part. + +In cases where the segments have been modified in an invalid way, the +first invalid segment will be set in C. + +=head1 POTENTIALLY INCOMPATIBLE CHANGES BETWEEN v0.2.2 and v0.3.0 + +The v0.3.0 introduced the ability to mutate the parts of an existing +URI, which in turn may have introduced changes which are incompatible +with existing applications: + +=item URI.query now returns an object of type URI::Query not a Str +The object will coerce correctly when interpolated into a string but +will need an explicit .Str coercion if being assigned to a Str typed +variable. + +=item URI.path now returns an object of type URI::Path not a Str +The object will coerce correctly when interpolated into a string but +will need an explicit .Str coercion if being assigned to a Str typed +variable. + +=item URI.query-form no longer returns a Hash but an object of URI::Query +The object does the Associative role so for the most part can be treated +like a Hash but an explicit .Hash coercion may be required when comparing +with another Hash or when merging with another Hash. Some uses of +query-form have been marked as deprecated and should use .query instead. + +The changes have been tested with the majority of modules that depend on +URI and only a few required changes. + +=head1 AUTHORS + +Contributors to this module include: + +=item Ronald Schmidt (ronaldxs) + +=item Moritz Lentz (moritz) + +=item Nick Logan (ugexe) + +=item Tobias Leich (FROGGS) + +=item Jonathan Stowe (jonathanstowe) + +=item Justin DeVuyst (jdv) + +=item Solomon Foster (colomon) + +=item Roman Baumer (rba) + +=item Zoffix Znet (zoffixznet) + +=item Ahmad M. Zawawi (azawawi) + +=item Gabor Szabo (szabgab) + +=item Samantha McVey (samcv) + +=item Pawel Pabian (bbkr) + +=item Rob Hoelz (hoelzro) + +=item radiak + +=item Paul Cochrane (paultcochrane) + +=item Steve Mynott (stmuk) + +=item timo + +=item David Warring (dwarring) + +=item Sterling Hanenkamp (zostay) + +Source can be located at: https://github.com/raku-community-modules/URI . +Comments and Pull Requests are welcome. + +=head1 COPYRIGHT & LICENSE + +Copyright 2017 Ronald Schmidt. + +Copyright 2015 - 2024 Raku Community Authors + +This software is licensed under the Artistic 2.0 License. + +=end pod + +# vim: expandtab shiftwidth=4 diff --git a/lib/IETF/RFC_Grammar.rakumod b/lib/IETF/RFC_Grammar.rakumod index fa6b26c..469176f 100644 --- a/lib/IETF/RFC_Grammar.rakumod +++ b/lib/IETF/RFC_Grammar.rakumod @@ -54,3 +54,5 @@ method new(Str $rfc, $grammar?) { } method parse_result { $!parse-result } + +# vim: expandtab shiftwidth=4 diff --git a/lib/IETF/RFC_Grammar/IPv6.rakumod b/lib/IETF/RFC_Grammar/IPv6.rakumod index 556ebb8..05ce178 100644 --- a/lib/IETF/RFC_Grammar/IPv6.rakumod +++ b/lib/IETF/RFC_Grammar/IPv6.rakumod @@ -1,4 +1,4 @@ -# Taken/Copied with relatively minor translation to Perl6 +# Taken/Copied with relatively minor translation to Raku # from RFC 3986 (http://www.ietf.org/rfc/rfc3986.txt) unit grammar IETF::RFC_Grammar::IPv6:ver<0.02>; @@ -15,7 +15,7 @@ token IPv6address { [ [ <.sep-h16> ] ** 0..6 <.h16> ]? '::' }; - # token avoiding backtracking happiness +# token avoiding backtracking happiness token sep-h16 { [ <.h16> ':' ] } token ls32 { [<.h16> ':' <.h16>] | <.IPv4address> }; @@ -33,4 +33,4 @@ token dec-octet { <.digit> # 0 - 9 } - +# vim: expandtab shiftwidth=4 diff --git a/lib/IETF/RFC_Grammar/URI.rakumod b/lib/IETF/RFC_Grammar/URI.rakumod index 9a53b24..f633a65 100644 --- a/lib/IETF/RFC_Grammar/URI.rakumod +++ b/lib/IETF/RFC_Grammar/URI.rakumod @@ -1,4 +1,4 @@ -# Taken/Copied with relatively minor translation to Perl6 +# Taken/Copied with relatively minor translation to Raku # from RFC 3986 (http://www.ietf.org/rfc/rfc3986.txt) # Rule names moved from snake case with _ to kebab case with - @@ -91,3 +91,4 @@ token sub-delims { <[;!$&'()*+,=]> }; token uri-alphanum { <+uri-alpha +:N +:S> }; token uri-alpha { }; +# vim: expandtab shiftwidth=4 diff --git a/lib/URI.rakumod b/lib/URI.rakumod index fc06ef7..32a4ba7 100644 --- a/lib/URI.rakumod +++ b/lib/URI.rakumod @@ -1,8 +1,7 @@ -use v6.d; use URI::Path; use URI::Query; -unit class URI:auth:ver; +unit class URI; use IETF::RFC_Grammar; use IETF::RFC_Grammar::URI; @@ -331,7 +330,7 @@ multi method segments(URI:D: $first-segment, *@remaining-segments --> List:D ) { } # The absolute and relative methods are artifacts carried over from an old -# version of the then-p6 module. The perl 5 module does not provide such +# version of the module. The perl odule does not provide such # functionality. The Ruby equivalent just checks for the presence or # absence of a scheme. The URI rfc does identify absolute URIs and # absolute URI paths and these methods somewhat confused the two. Their @@ -432,779 +431,4 @@ multi method query-form() { $!query } multi method query-form(|c) { $!query.query-form(|c) } - -=begin pod - -=head1 NAME - -URI — Uniform Resource Identifiers (absolute and relative) - -=head1 SYNOPSIS - - use URI; - my $u = URI.new('http://example.com/foo/bar?tag=woow#bla'); - - # Look at the parts - say $u.scheme; #> http - say $u.authority; #> example.com - say $u.host; #> example.com - say $u.port; #> 80 - say $u.path; #> /foo/bar - say $u.query; #> tag=woow - say $u.fragment; #> bla - - # Modify the parts - $u.scheme('https'); - $u.authority('example.com:8443'); - $u.path('/bar/foo'); - $u.query('x=1&y=2'); # OR - $u.fragment('cool'); - say "$u"; #> https://example.com:8443/bar/foo?x=1&y=2#cool - - # Authority is an object, but may be undefined - with $u.authority { - say .userinfo; # none set, no output - say .host; #> example.com - say .port; #> 8443 - - # It is mutable too - .userinfo('bob'); - say $u.authority; #> bob@example.com:8443 - } - - # Path is an object, always defined, but immutable - with $u.path { - say .path; #> /bar/foo - .say for .segments; #> bar\nfoo\n - } - - # Query is an object, always defined, mutable - with $u.query { - say .query; #> x=1&y=2 - .query('foo=1&foo=2'); - say .query-form[0]; #> 1 - say .query-form[1]; #> 2 - - .query-form.push: 'bar' => 'ok'; - say .query; #> foo=1&foo=2&bar=ok - - .query(''); - .query-form = 123; - say .query; #> abc=123 - } - -=head1 DESCRIPTION - -A URI object represents a parsed string that abides by the grammar defined in RFC 3986 for Universal Resource Identifiers. The object then works to enforce the rules defined in the RFC for any modifications made to it. - -As of this writing, The URI class is scheme agnostic. It will verify that URI is correct, in general, but not correct for a given scheme. For example, C would be considered a valid URI even though that is not a valid URI according to the rules specific to the C scheme. - -This class uses "heavy accessors". From the SYNOPSIS, you may have noted that assignment to accessors is not used. This is because nearly all accessors in this class do some extra work of parsing, validating, and cross-referencing with other fields to guarantee that the URI is correct before making a change. - - my $u = URI.new; - $u.path = '/foo/bar'; # ERROR: «Cannot modify an immutable URI::Path» - $u.path('/foo/bar'); # WORKS! - -This mutator pattern is meant to reflect the internal complexity. - -=head2 SCHEME, AUTHORITY, AND PATH - -In RFC 3986 URIs, the scheme, the authority, and the path are related. This should not matter most of the time, but to avoid problems when setting these three, it is safest to set them in this order: - - my $u = URI.new; - $u.path(''); - $u.scheme($my-scheme); - $u.authority($my-host); - $u.path($my-path); - -This is because an empty path is permitted in any case, but the format of the path is limited whether an authority and scheme are set. - -With an authority set (i.e., the URI either starts with "//" or with "scheme://"), a non-empty path must start with a "/" and may start with empty path segments, e.g. "scheme://foo//" is valid. - -When there's no authority set, but a scheme is used (e.g., it starts with "scheme:", but not "scheme://"), a non-empty path may either start with a "/" or not, but must contain one or more other characters in the first segment of the path. Thus, the following code will fail: - - my $u = URI.new('scheme:'); - $u.path('//'); # ERROR: «Could not parse path "//" as part of URI: scheme://» - -When there's no authority and no scheme used, a non-empty path may start with a "/" or not, but must contain one or more other characters in the first segment. - -These rules are enforced whenever setting or clearing the scheme, authority, or path. If the resulting URI object would be invalid a C exception will be thrown. - -=head2 QUERY - -The C method of this class returns a C object.This is a special object that is C, C, and C. That is, it can be bound to an array variable, a hash variable, and iterated using a loop. If stringified, it will return a URI-encoded query. If used as an array, will act like somewhat like an array of Cs. If used as a hash, will provide a map from query keys to query values. - -The native internal representation is a list of Cs as this is the best match for how queries are defined. Because of this, there's a problem when using a query as a hash: duplicate pairs are valid. To handle this, the C object always returns a list of values, sorted in the order they appear for each key. - -For example: - - my $u = URI.new('?foo=1&bar=2&foo=3'); - say $u.query[0]; #> 1 - say $u.query[1]; #> 3 - say $u.query[0]; #> 2 - -Older versions of the URI module handled this differently, using a mixed value representation. In order to gain some backwards compatibility, this is still supported by setting the C: - - # Continues from previous - $u.query.hash-format = URI::Query::Mixed; - say $u.query[0]; #> 1 - say $u.query[1]; #> 3 - - # The bar value is NOT a list now - say $u.query; #> 2 - - # Return to the default mode - $u.query.hash-format = URI::Query::Lists; - -However, the list version is safer and may likely work with most existing code that worked with mixed before. - -Another mode is provided to force single values, which is often how applications treat these out of convenience. In that case, only the last value will be kept: - - # Continues from previous - $u.query.hash-format = URI::Query::Singles; - - # These are never lists now - say $u.query; #> 3 - say $u.query; #> 2 - -The C mode is default and recommended mode. - -=head2 GRAMMAR - -This class will keep a copy of the result of parsing the URI string for you. If you are interested in precise details of the parse tree, you get them using the C method: - - my $host-in-grammar = - $u.grammar.parse-result; - if $host-in-grammar { - say 'Host looks like registered domain name - approved!'; - } - else { - say 'Sorry we do not take ip address hosts at this time.'; - say 'Please use registered domain name!'; - } - -The C grammar sticks close to the BNF defined in RFC 3986. - -=head2 PARTIAL MATCHING - -Many times a URI you are interested in is embedded within another string. This class will allow you to parse URIs out of a larger string, so long as the URI is at the start. This is done by setting the C<:match-prefix> option during construction or when calling C: - - { - # require whole string matches URI and throw exception otherwise .. - my $u_v = URI.new('http://?#?#'); - CATCH { when X::URI::Invalid { ... } } - } - - my $u_pfx = URI.new('http://example.com } function(var mm){', :match-prefix); - -=head1 METHODS - -=head2 method new - - multi method new(URI:U: Str() $uri, Bool :$match-prefix) returns URI:D - multi method new(URI:U: Str() :$uri, Bool :$match-prefix) returns URI:D - -These construct a new C object and return it. The given C<$uri> value is converted to a string and then parsed using the C method. - -If C<:match-prefix> is set, then the grammar will be allowed to match a prefix of the given input string rather than requiring a total match. The C<:match-prefix> given also becomes the default value for any figure calls to C. - -Throws a C exception if the URI cannot be parsed. - -=head2 method parse - - method parse(URI:D: Str() $str, Bool :$match-prefix = $.match-prefix) - -This method allows an existing URI object to be reused to parse another string. This parses the given string and replaces all internal state of the object with values for the new parse. - -The given C<:match-prefix> flag becomes the new default when set. - -Throws a C exception if the URI cannot be parsed. - -=head2 method grammar - - method grammar(URI:D:) returns IETF::RFC_Grammar:D - -Returns the object used to parse and store the state of the parse. - -=head2 method match-prefix - - method match-prefix(URI:D:) returns Bool:D - -Returns True if the most recent call to C (or C) allowed a prefix match or False if a total match was required. This is the default value of any future call to C. - -=head2 method scheme - - multi method scheme(URI:D:) returns URI::Scheme:D - multi method scheme(URI:D: Str() $scheme) returns URI::Scheme:D - -Returns the scheme part of the URI. This is a string that must match the C subset type. - -The second form allows the scheme to be replaced with a new scheme. It is legal for the scheme to be set to an empty string, which has the effect of making the URI scheme-less. - -This will throw an C exception if adding or removing the scheme will make the URI invalid. See SCHEME, AUTHORITY, AND PATH section for additional details. - -=head2 method authority - - multi method authority(URI:D:) returns URI::Authority - multi method authority(URI:D: Nil) returns URI::Authority:U - multi method authority(URI:D: Str() $new) returns URI::Authority:D - -Returns the C for the current URI object. This may be an undefined type object if no authority has been set or found during parse. - -When passed a string, the string will have the authority parsed out of it and a new authority object will be used to store the parsed information. An empty authority is valid. - -When passed C, the authority will be cleared. - -When passing an argument, this will throw an C exception if setting or clearing the authority on the URI will make the URI invalid. See SCHEME, AUTHORITY, AND PATH section for details. - -The authority is made up of three components: userinfo, host, and port. Additional methods are provided for accessing each of those parts separately. - -=head2 method userinfo - - multi method userinfo(URI:D:) returns URI::Userinfo:D - multi method userinfo(URI:D: Str() $new) returns URI::Userinfo:D - -The userinfo is an optional component of the URI authority. This method returns the current userinfo or an empty string. - -Setting this method will cause a C to be constructed and C to be set, if it is not already defined. This may result in a C exception being thrown if adding an authority will make the path invalid. - -=head2 method host - - multi method host(URI:D:) returns URI::Host:D - multi method host(URI:D: Str() $new) returns URI::Host:D - -The host is a component of the URI authority. This method returns the current host or an empty string. - -Setting this method will cause a C to be constructed and C to be set, if it is not already defined. This may result in a C exception being thrown if adding an authority will make the path invalid. - -=head2 method default-port - - method default-port(URI:D:) returns URI::Port - -This method applies the C method of C to the scheme set on this object. Basically, a shortcut for: - - my $u = URI.new("..."); - my $port = URI::DefaultPort.scheme-port($u.scheme); - -It returns the usual port for the named scheme. - -=head2 method _port - - multi method _port(URI:D:) returns URI::Port - multi method _port(URI:D: Nil) returns URI::Port:U - multi method _port(URI:D: Int() $new) returns URI::Port:D - -When an authority is set on the URI, this gets or sets the authority's port. This differs from C, which returns either the port set or the C. This method returns just the port. - -If no authority is set or no port is set, this returns an undefined value (i.e., an C type object). - -Setting this method will cause a C to be constructed and C to be set, if it is not already defined. This may result in a C exception being thrown if adding an authority will make the path invalid. - -=head2 method port - - multi method port(URI:D:) returns URI::Port - multi method port(URI:D: Nil) returns URI::Port - multi method port(URI:D: Int() $new) returns URI::Port - -When retrieving a value from the object, this method returns either the port set on the authority or the default port for the current URI scheme. It may return an undefined value (i.e., an C type object) if there is no port set and no known default port for the current scheme or no scheme set. - -When setting a value on the object, this method sets the port on the authority. - -Setting this method will cause a C to be constructed and C to be set, if it is not already defined. This may result in a C exception being thrown if adding an authority will make the path invalid. - -=head2 method path - - multi method path(URI:D:) returns URI::Path:D - multi method path(URI:D: Str() $path) returns URI::Path:D - -Path is the main required element of a URI, but may be an empty string. This method returns the current setting for the path as a C object. It also allows setting a new path, which will construct a new C object. - -This method will throw a C exception if the path is not valid for the current scheme and authority settings. - -=head2 method segments - - multi method segments(URI:D:) returns List:D - multi method segments(URI:D: @segments where *.elems > 0) returns List:D - multi method segments(URI:D: $first-segment, *@remaining-segments) returns List:D - -Returns the path segments (i.e., the parts between slashes). This is a shortcut for: - - my $u = URI.new("..."); - my @s = $u.path.segments; - -The number of segments is equal to the number of slashes in the original path plus one. The segments are constructed so that joining the segments by a slash (/) will give you the original path. - -You may pass a list in to replace the segments with a new value. This will throw a C exception if any of the segments contain a slash or if the set path will cause the URI to become invalid. - -Be sure when setting segments to include an initial empty string if you want the path to start with a slash. - -=head2 method query - - multi method query(URI:D:) returns URI::Query:D - multi method query(URI:D: Str() $new) returns URI::Query:D - multi method query(URI:D: *@new) returns URI::Query:D - -Accesses or updates the query associated with the URI. This is returns as a C object. This will always be defined, but may be empty. - -When passed a string, the string will be parsed using C and the query object will be updated. When passed a list, the list of Cs given will be used to setup a new query that way. - -When the list form is used, any named parameters passed will be ignored (they will not be used to fill the query) and a warning will be issued. - -=head2 method path-query - - method path-query(URI:D:) returns Str:D - -Returns the path and query as a string joined by a question mark ("?"). It will return just the path as a string if the query is empty. - -=head2 method fragment - - multi method fragment(URI:D:) returns URI::Fragment:D - multi method fragment(URI:D: Str() $new) returns URI::Fragment:D - -Returns the URI fragment, which is always defined, but may be an empty string. If passed a value, it will set the fragment. - -=head2 method gist - - multi method gist(URI:D:) returns Str:D - -Reconstructs the URI from the components and returns it as a string. - -=head2 method Str - - multi method Str(URI:D:) returns Str:D - -Reconstructs the URI from the components and returns it as a string. - -=head1 SUBROUTINES - -=head2 sub split-query - - sub split-query(Str() $query, :$hash-format = URI::Query::None) - -This routine will slice and dice a query string, which is useful when parsing URIs and may also be useful when parsing POST entities that are encoded as C. - -This routine is exported with the C<:split-query> tag or can be used with the full namespace, C. - -With just the required string, this routine will parse that string and return a list of Cs mapping each query form key to its respective value. The order is preserved. This is used by C during construction. - -For example: - - my @qf = URI::split-query("foo=1&bar%20=2&foo=3"); - dd @qf; #> Array @qf = [:foo("1"), "bar " => ("2"), :foo("3")] - -Notice that this will perform a C operation of keys and values in the process so the values you receive have had the URI encoded characters decoded. - -You can retrieve the query string as a hash instead by passing the C<:hash-format> option. This works exactly as it does for C. - -The options for C<:hash-format> include: - -=head3 URI::Query::Lists - -Every key is mapped to a list of one or more values. From the example input, a structure like the following is returned: - - my %qf = URI::split-query("foo=1&bar%20=2&foo=3", - hash-format => URI::Query::Lists, - ); - dd %qf; #> Hash %qf = {"bar " => (["2"]), :foo(["1", "3"])} - -This is also the default if you just pass C<:hash-format> as a boolean option. - -=head3 URI::Query::Mixed - -Every key is mapped to either a list of two or more values or directly to a single value, like the following: - - my %qf = URI::split-query("foo=1&bar%20=2&foo=3", - hash-format => URI::Query::Mixed, - ); - dd %qf; #> Hash %qf = {"bar " => ("2"), :foo(["1", "3"])} - -=head3 URI::Query::Singles - -Every key is mapped to a single value, which will be the last value encountered in the input, like this: - - my %qf = URI::split-query("foo=1&bar%20=2&foo=3", - hash-format => URI::Query::Mixed, - ); - dd %qf; #> Hash %qf = {"bar " => ("2"), :foo("3")} - -=head1 HELPER SUBSETS - -=head2 URI::Scheme - -This is a subset of C that only accepts valid schemes. - -=head2 URI::Userinfo - -This is a subset of C that only accepts valid userinfo. - -=head2 URI::Host - -This is a subset of C that only accepts valid hosts. - -=head2 URI::Port - -This is a subset of C. - -=head2 URI::Query::ValidQuery - -This is a subset of C that only accepts valid query strings. - -=head2 URI::Fragment - -This is a subset of C that only accepts valid URI fragments. - -=head1 HELPER CLASSES - -=head2 URI::Authority - -The C method of a URI constructs or returns this object. - -It is recommended that you do not costruct a C object directly, but let the methods of C handle construction. - -=head3 method userinfo - - method userinfo(URI::Authority:D:) is rw returns URI::Userinfo:D - -This is a simple setter/getter for the userinfo on the authority. It must be defined, but may be the empty string. If not empty, it must be valid for the userinfo component of a URI. - -=head3 method host - - method host(URI::Authority:D:) is rw returns URI::Host:D - -This is a simple setter/getter for the host part of the URI authority. It must be defined, but may be the empty string. If not empty, must be a valid host value, which may be an IP address or registered name. - -=head3 method port - - method port(URI::Authority:D:) is rw returns URI::Port - -This is a simple setter/getter for the port part of the URI authority. It may be set to an undefined value if no explicit port is set in the authority. If defined, it must an unsigned integer. - -=head3 method gist - - multi method gist(URI::Authority:D:) returns Str:D - -Returns the string representation of the URI authority. For example, - - my $u = URI.new("http://steve@example.com:8008"); - - # say calls .gist - say $u.authority; #> "steve@example.com:8080"; - -=head3 method Str - - multi method gist(URI::Authority:D:) returns Str:D - -Stringifies identical to C. - -=head2 URI::Path - -This class is used to represent URI path components. - -It is recommended that you do not construct a C object directly, but rely on the C setter in C instead. These objects are immutable. Please use methods on C to make changes. - -=head3 method path - - method path(URI::Path:D:) returns Str:D - -Returns the string representation of the path. - -=head3 method segments - - method segments(URI::Path:D:) returns List:D - -Returns a list representation of the path segments. In a URI, the path segments are the strings between slashes ("/"). - -=head3 method gist - - method gist(URI::Path:D:) returns Str:D - -Returns the C. - -=head3 method Str - - method Str(URI::Path:D:) returns Str:D - -Returns the C. - -=head2 URI::Query - -This class is used to represent a URI query component. This class may be safely constructed and used independently of the URI object. - -It behaves as both a positional and associative object and is iterable. Internally, it is stored as an C of Cs. You must not treat this object purely as you would an C or purely as you would a C as some methods will not work the way you expect. - -The performance of the associative methods is not guaranteed and is probably going to be relatively slow. This implementation values simplicity and accuracy of representation to CPU performance. If you need something that is better for CPU performance, you should investigate the use of another library, such as C or sub-class to provide a higher performance implementation of C's associative methods. - -=head3 method new - - multi method new(Str() $query, URI::Query::HashFormat :$hash-format = URI::Query::Lists) returns URI::Query:D - multi method new(:$query, URI::Query::HashFormat :$hash-format = URI::Query::Lists) returns URI::Query:D - -Constructs a new C from the given string, which may be empty. - -Unlike C, which permits boolean values for the C option, C requires a C value and will reject a boolean (because the boolean does not make sense in this case). - -=head3 enum HashFormat - -This enumeration provides the values that may be set on C on a C. Possible values are as follows. - -=head4 URI::Query::List - -This is the default and recommended value. When set in C, each key in the hash representation will point to a list of one or more values. - -This is recommended as it is the most accurate representation to what is actually possible in a query string. The values within each key will be sorted in the same order as they appear in the query. - -=head4 URI::Query::Mixed - -When set in C, each key in the hash representation may be mapped to either a list of two or more values, or to the value itself if there is only one. - -This is not recommended because it means that setting a key to an iterable value will be treated as multiple key-value pairs when it comes time to render the query as a string. This could have unintended consequences. - -=head4 URI::Query::Singles - -When set in C, each key in the hash representation will be mapped directly to a single value. If there are multiple values that have been set in the query, then only the last will be visible through the hash representation. - -This is not recommended because it may hide certain values, but may be useful for simple applications that treat the query string as having unique values. Just note that the trade-off is that your application may be confused when multiple values are present. - -=head4 URI::Query::None - -This value should not be used, but will be treated the same as C if used here. It is provided for use with C only. - -=head3 method hash-format - - method hash-format(URI::Query:D) is rw returns URI::Query::HashFormat - -This is a simple setter/getter for setting the way in which associative lookups are performed. See C for a description of each mode. - -=head3 method query - - method query(Query:D:) returns URI::Query::ValidQuery:D - method query(Query:D: Str() $new) returns URI::Query::ValidQuery:D - -This method returns the string representation of the URI query component. When passed a string, it will replace the query with the new value and will use C to parse that query into an array of Cs. - -The primary representation of the queryo object is the value returned by C. The C is cached to keep the value stored in it when this method is called to set it or when C is constructed from a string. Any modification to the internal array of pairs, though, will clear this cache. It will be generated the next time the C method is called and that string will be cached. - -=head3 method query-form - - method query-form(Query:D:) returns Array:D - method query-form(Query:D: *@new, *%new) returns Array:D - -This method returns the array of Cs that store the internal representation of the URI query component. When passed an array of Cs, it will replace the current value with that array. - -A quick note about the way pairs are passed as parameters to a method, you most likely want to avoid passing values as named parameters. If values are passed using unquoted strings, they will be treated as named parameters, which is most likely what you want: - - my $q = URI::Query.new; - - # Prefer this - $q.query-form('foo' => 1, 'bar' => 2, 'foo' => 3); - - # Avoid these - $q.query-form(foo => 1, bar => 2, foo => 3); - $q.query-form(:foo(1), :bar(2), :foo(3)); - -The latter two will result in the first "foo" pair being lost. Named parameters assume unique names and the latter "foo" key will effectively override the former. - -That said, the method will allow hashes to be passed in, if that is your preference. - -=head3 method of - - method of() - -Always returns C. - -=head3 method iterator - - method iterator(Query:D:) returns Iterator:D - -Returns the iterator on the internal array of Cs. - -=head3 method postcircumflex:<[ ]> - - method postcircumflex:<[ ]> returns Pair - -This permits positional access to each C stored internally. You may use this to get a C, set a C, test for existence, or delete. - -=head3 method postcircumflex:<{ }> - - method postcircumflex:<{ }> - -This permits associative access to the values stored internally by key. What is returned here when fetching values depends on the setting in C, a list of one or more values or C, by default. You can use this for getting, setting, existence testing, or deletion. - -=head3 method keys - - method keys(Query:D:) returns Seq:D - -This method returns all the keys of the query in order. - -=head3 method values - - method values(Query:D:) returns Seq:D - -This method returns all the values of the query in order. - -=head3 method kv - - method kv(Query:D:) returns Seq:D - -This method returns a sequence alternating the keys and values of the query in order. - -=head3 method pairs - - method kv(Query:D:) returns Seq:D - -This method returns a copy of the internal representation of the query string array. - -=head3 method pop - - method pop(Query:D:) returns Pair - -This method removes the last Pair from the array of pairs and returns it. - -=head3 method push - - method push(Query:D: *@new) - -This method adds the given pairs to the end of the array of pairs in the query using push semantics. - -=head3 method append - - method append(Query:D: *@new) - -This method adds the given pairs to the end of the array of pairs in the query using append semantics. - -=head3 method shift - - method shift(Query:D:) returns Pair - -This method removes the first Pair from the array of pairs and returns it. - -=head3 method unshift - - method unshift(Query:D: *@new) - -This method adds the given pairs to the front of the array of pairs in the query using unshift semantics. - -=head3 method prepend - - method prepend(Query:D: *@new) - -This method adds the given pairs to the front of the array of pairs in the query using prepend semantics. - -=head3 method splice - - method splice(Query:D: $start, $elems?, *@replacement) - -This method removes a C<$elems> number of pairs from the array of pairs in the query starting at index C<$start>. It then inserts the pairs in C<@replacement> into that part of the array (if any are given). - -=head3 method elems - - method elems(Query:D:) returns Int:D - -Returns the number of pairs stored in the query. - -=head3 method end - - method end(Query:D:) returns Int:D - -Returns the index of the last pair stored in the query. - -=head3 method Bool - - method Bool(Query:D:) returns Bool:D - -Returns C if the at least one pair is stored in the query or C otherwise. - -=head3 method Int - - method Int(Query:D:) returns Int:D - -Returns C. - -=head3 method Numeric - - method Numeric(Query:D:) returns Int:D - -Returns C. - -=head3 method gist - - method gist(Query:D:) returns Str:D - -Returns the C. - -=head3 method Str - - method Str(Query:D:) returns Str:D - -Returns the C. - -=head1 EXCEPTIONS - -=head2 X::URI::Invalid - -This exception is thrown in many places where the URI is being parsed or manipulated. If the string being parsed is not a valid URI or if certain manipulations of the URI object would cause it to become an invalid URI, this exception may be used. - -It provides a C accessor, which returns the string that was determined to be invalid. - -=head2 X::URI::Path::Invalid - -In some cases where an attempt is made to set C to an invalid value, this exception is thrown. - -The C field will name the invalid URI. Strictly speaking, the URI might be valid, but will not parse the same way as given. To make it clear that this is the case, the C field names the invalid path part. - -In cases where the segments have been modified in an invalid way, the first invalid segment will be set in C. - -=head1 AUTHORS - -Contributors to this module include: - -=item Ronald Schmidt (ronaldxs) - -=item Moritz Lentz (moritz) - -=item Nick Logan (ugexe) - -=item Tobias Leich (FROGGS) - -=item Jonathan Stowe (jonathanstowe) - -=item Justin DeVuyst (jdv) - -=item Solomon Foster (colomon) - -=item Roman Baumer (rba) - -=item Zoffix Znet (zoffixznet) - -=item Ahmad M. Zawawi (azawawi) - -=item Gabor Szabo (szabgab) - -=item Samantha McVey (samcv) - -=item Pawel Pabian (bbkr) - -=item Rob Hoelz (hoelzro) - -=item radiak - -=item Paul Cochrane (paultcochrane) - -=item Steve Mynott (stmuk) - -=item timo - -=item David Warring (dwarring) - -=item Sterling Hanenkamp (zostay) - -=head1 COPYRIGHT & LICENSE - -Copyright © 2017 Ronald Schmidt. - © 2015 - Raku Community Authors - -This software is licensed under the Artistic 2.0 License. - -=end pod - - +# vim: expandtab shiftwidth=4 diff --git a/lib/URI/DefaultPort.rakumod b/lib/URI/DefaultPort.rakumod index 550aaa8..034f2e6 100644 --- a/lib/URI/DefaultPort.rakumod +++ b/lib/URI/DefaultPort.rakumod @@ -35,17 +35,4 @@ method scheme-port(Str $scheme) { %default_port{$scheme}; } -=begin pod - -=head1 NAME - -URI::DefaultPort - Stores the default ports for various services - -=head1 SYNOPSIS - - use URI::DefaultPort; - - say scheme-port( "https" ); # 443 - -=end pod - +# vim: expandtab shiftwidth=4 diff --git a/lib/URI/Escape.rakumod b/lib/URI/Escape.rakumod index 40c2f60..013a0c7 100644 --- a/lib/URI/Escape.rakumod +++ b/lib/URI/Escape.rakumod @@ -67,18 +67,4 @@ sub uri_unescape(*@to_unesc, Bool :$no_utf8 = False) is export { uri-unescape(@to_unesc, no-utf8 => $no_utf8) } -=begin pod - -=head1 NAME - -URI::Escape - Escape and unescape unsafe characters - -=head1 SYNOPSIS - - use URI::Escape; - - my $escaped = uri-escape("10% is enough\n"); - my $un_escaped = uri-unescape('10%25%20is%20enough%0A'); - -=end pod - +# vim: expandtab shiftwidth=4 diff --git a/lib/URI/Path.rakumod b/lib/URI/Path.rakumod index 0b52503..1a9de49 100644 --- a/lib/URI/Path.rakumod +++ b/lib/URI/Path.rakumod @@ -57,3 +57,5 @@ method rel2abs(URI::Path:D: Str:D() $base) { self.new: :$path; } } + +# vim: expandtab shiftwidth=4 diff --git a/lib/URI/Query.rakumod b/lib/URI/Query.rakumod index 5d71d46..dc82ae7 100644 --- a/lib/URI/Query.rakumod +++ b/lib/URI/Query.rakumod @@ -1,4 +1,3 @@ -use v6.d; unit class URI::Query does Positional does Associative @@ -207,3 +206,4 @@ multi method query-form(URI::Query:D: *@new, *%new) { multi method gist(URI::Query:D: --> Str ) { $.query } multi method Str(URI::Query:D: --> Str ) { $.gist } +# vim: expandtab shiftwidth=4 diff --git a/run-tests b/run-tests new file mode 100644 index 0000000..565dcf5 --- /dev/null +++ b/run-tests @@ -0,0 +1,66 @@ +unit sub MAIN(:a($author), :i($install)); + +say run(, :out).out.slurp.chomp; +say "Running on $*DISTRO.gist().\n"; + +say "Testing { + "dist.ini".IO.lines.head.substr(7) +}{ + " including author tests" if $author +}"; + +my @failed; +my $done = 0; + +sub process($proc, $filename) { + if $proc { + $proc.out.slurp; + } + else { + @failed.push($filename); + if $proc.out.slurp -> $stdout { + my @lines = $stdout.lines; + with @lines.first( + *.starts-with(" from gen/moar/stage2"),:k) + -> $index { + say @lines[^$index].join("\n"); + } + else { + say $stdout; + } + } + else { + say "No output received, exit-code $proc.exitcode() ($proc.signal()):\n$proc.os-error()"; + } + } +} + +sub install() { + my $zef := $*DISTRO.is-win ?? 'zef.bat' !! 'zef'; + my $proc := run $zef, "install", ".", "--verbose", "--/test", :out,:err,:merge; + process($proc, "*installation*"); +} + +sub test-dir($dir) { + for $dir.IO.dir(:test(*.ends-with: '.t' | '.rakutest')).map(*.Str).sort { + say "=== $_"; + my $proc := run "raku", "--ll-exception", "-I.", $_, :out,:err,:merge; + process($proc, $_); + $done++; + } +} + +test-dir("t"); +test-dir($_) for dir("t", :test({ !.starts-with(".") && "t/$_".IO.d})).map(*.Str).sort; +test-dir("xt") if $author && "xt".IO.e; +install if $install; + +if @failed { + say "\nFAILED: {+@failed} of $done:"; + say " $_" for @failed; + exit +@failed; +} + +say "\nALL {"$done " if $done > 1}OK"; + +# vim: expandtab shiftwidth=4 diff --git a/t/01.t b/t/01.rakutest similarity index 97% rename from t/01.t rename to t/01.rakutest index e69c2ae..315c738 100644 --- a/t/01.t +++ b/t/01.rakutest @@ -1,4 +1,3 @@ -use v6.d; use Test; plan 50; @@ -93,10 +92,10 @@ is($u.frag, 'bar', 'test query and frag capture'); $u.parse('http://example.com:80/about?foo=cod&foo=trout'); is($u.query[1], 'trout', 'query param foo - el 2 without frag'); -$u.parse('about/perl6uri?foo=cod&foo=trout#bar'); +$u.parse('about/rakuuri?foo=cod&foo=trout#bar'); is($u.query[1], 'trout', 'query param foo - el 2 relative path'); -$u.parse('about/perl6uri?foo=cod&foo=trout'); +$u.parse('about/rakuuri?foo=cod&foo=trout'); is($u.query[1], 'trout', 'query param foo - el 2 relative path without frag'); throws-like {URI.new('http:://?#?#')}, X::URI::Invalid, @@ -116,3 +115,4 @@ my Str $user-info = URI.new( is( uri-unescape($user-info), 'user,n:deprecatedpwd', 'extracted userinfo correctly'); +# vim: expandtab shiftwidth=4 diff --git a/t/authority.t b/t/authority.rakutest similarity index 95% rename from t/authority.t rename to t/authority.rakutest index 05aa2e1..ae30f0c 100644 --- a/t/authority.t +++ b/t/authority.rakutest @@ -1,7 +1,8 @@ -use v6.d; use Test; use URI; +plan 21; + my $u = URI.new('foo://me@localhost:4321'); isa-ok $u.authority, URI::Authority; @@ -58,4 +59,4 @@ is "$u", 'foo://'; $u.authority(Nil); is "$u", 'foo:'; -done-testing; +# vim: expandtab shiftwidth=4 diff --git a/t/directory.t b/t/directory.rakutest similarity index 92% rename from t/directory.t rename to t/directory.rakutest index 05287d6..cb4dc58 100644 --- a/t/directory.t +++ b/t/directory.rakutest @@ -1,4 +1,3 @@ -use v6.d; use Test; use URI; @@ -16,3 +15,5 @@ is dir("path/doc.html"), "path/"; is dir("https://mysite.com"), "https://mysite.com/"; is dir("https://mysite.com/"), "https://mysite.com/"; is dir("https://mysite.com/path/doc.html"), "https://mysite.com/path/"; + +# vim: expandtab shiftwidth=4 diff --git a/t/escape.t b/t/escape.rakutest similarity index 91% rename from t/escape.t rename to t/escape.rakutest index ea6b2ae..ab0d07d 100644 --- a/t/escape.t +++ b/t/escape.rakutest @@ -1,9 +1,8 @@ -use v6.d; - -# Copied, with minor translation to Perl6, from the escape.t file +# Copied, with minor translation to Raku, from the escape.t file # in the CPAN URI distribution use Test; + plan 11; use URI::Escape; @@ -27,3 +26,4 @@ is uri-unescape("%40A%42", "CDE", "F%47++H"), ['@AB', 'CDE', 'FG H'], 'unescape list'; ok not uri-escape(Str).defined, 'undef returns undef'; +# vim: expandtab shiftwidth=4 diff --git a/t/issue-43.t b/t/issue-43.rakutest similarity index 92% rename from t/issue-43.t rename to t/issue-43.rakutest index 7a2f4a7..34f151f 100644 --- a/t/issue-43.t +++ b/t/issue-43.rakutest @@ -1,5 +1,3 @@ -use v6.d; - use Test; use URI; @@ -15,3 +13,4 @@ lives-ok { $uri = URI.new($test-uri) }, "create the URI object"; is $uri.Str, $test-uri, "and the URI stringifies to the same as the source"; +# vim: expandtab shiftwidth=4 diff --git a/t/missing-components.t b/t/missing-components.rakutest similarity index 93% rename from t/missing-components.t rename to t/missing-components.rakutest index 6199bb7..b8cbc1e 100644 --- a/t/missing-components.t +++ b/t/missing-components.rakutest @@ -1,5 +1,5 @@ -use v6.d; use Test; + use URI; use IETF::RFC_Grammar::URI; @@ -13,3 +13,5 @@ ok '#foo' ~~ IETF::RFC_Grammar::URI.new().URI-reference, "Relative URIs work"; ok '' ~~ IETF::RFC_Grammar::URI.new().path-abempty, "path-abempty work"; nok 'foo:bar_baz' ~~ IETF::RFC_Grammar::URI.new().absolute-URI, "absolute-URI covered" + +# vim: expandtab shiftwidth=4 diff --git a/t/mutate.t b/t/mutate.rakutest similarity index 96% rename from t/mutate.t rename to t/mutate.rakutest index 8e121a6..a27515b 100644 --- a/t/mutate.t +++ b/t/mutate.rakutest @@ -1,8 +1,10 @@ -use v6.d; use Test; + use URI; use IETF::RFC_Grammar::URI; +plan 35; + cmp-ok 'http', '~~', URI::Scheme, 'URI::Scheme matches http'; cmp-ok '', '~~', URI::Scheme, 'URI::Scheme matches ""'; cmp-ok '-asdf', '!~~', URI::Scheme, 'URI::Scheme refused to match -asdf'; @@ -22,10 +24,10 @@ is "$u", 'https://example.com:567/about/us?foo#bar', 'setting _port works too'; $u._port(Nil); is "$u", 'https://example.com/about/us?foo#bar', 'clearing with _port works too'; -$u.authority("larry@perl6.org:1234"); -is "$u", 'https://larry@perl6.org:1234/about/us?foo#bar', 'setting authority works'; +$u.authority("larry@raku.org:1234"); +is "$u", 'https://larry@raku.org:1234/about/us?foo#bar', 'setting authority works'; is $u.userinfo, 'larry', 'setting authority set userinfo'; -is $u.host, 'perl6.org', 'setting authority set host'; +is $u.host, 'raku.org', 'setting authority set host'; is $u.port, 1234, 'setting authority set port'; $u.authority("example.com"); @@ -148,4 +150,4 @@ is "$u", 'https://example.com/careers/are/good?bar#wubba', 'setting fragment wor $u.fragment("hello"); is "$u", 'https://example.com/careers/are/good?bar#hello', 'setting fragment works'; -done-testing; +# vim: expandtab shiftwidth=4 diff --git a/t/november-urlencoded.t b/t/november-urlencoded.rakutest similarity index 96% rename from t/november-urlencoded.t rename to t/november-urlencoded.rakutest index fa06afc..4349dbd 100644 --- a/t/november-urlencoded.t +++ b/t/november-urlencoded.rakutest @@ -1,9 +1,8 @@ -use v6.d; - # lifted more or less completely from the Novemver project # just now uses URI::Escape rather than November::CGI use Test; + use URI::Escape; my @t = @@ -31,4 +30,4 @@ for @t { } -# vim: ft=perl6 +# vim: expandtab shiftwidth=4 diff --git a/t/path.t b/t/path.rakutest similarity index 94% rename from t/path.t rename to t/path.rakutest index 32a13d0..d966e33 100644 --- a/t/path.t +++ b/t/path.rakutest @@ -1,7 +1,9 @@ -use v6.d; use Test; + use URI; +plan 14; + my $q = URI.new('foo:/a/b/c'); isa-ok $q.path, URI::Path; ok $q.path.defined; @@ -41,4 +43,4 @@ throws-like { $q.path('//a/b/c'); }, X::URI::Invalid; -done-testing; +# vim: expandtab shiftwidth=4 diff --git a/t/query.t b/t/query.rakutest similarity index 99% rename from t/query.t rename to t/query.rakutest index 7da847b..fce0454 100644 --- a/t/query.t +++ b/t/query.rakutest @@ -1,7 +1,9 @@ -use v6.d; use Test; + use URI :split-query; +plan 24; + constant $qs = 'foo=1&bar=2&foo=3&baz'; subtest { @@ -258,4 +260,4 @@ subtest { is "$q", "foo=1&bar=2&foo=3&baz="; }, 'query-form'; -done-testing; +# vim: expandtab shiftwidth=4 diff --git a/t/rel2abs.t b/t/rel2abs.rakutest similarity index 95% rename from t/rel2abs.t rename to t/rel2abs.rakutest index f8b110b..18edf3b 100644 --- a/t/rel2abs.t +++ b/t/rel2abs.rakutest @@ -1,4 +1,3 @@ -use v6.d; use Test; use URI; @@ -21,3 +20,5 @@ is rel2abs("/ccc", "foo:/aaa/bbb"), "foo:/ccc"; is rel2abs("/ccc", "foo://aaa/bbb"), "foo://aaa/ccc"; is rel2abs("foo:ccc", "/aaa/bbb"), "foo:ccc"; is rel2abs("foo:ccc", "foo:/aaa/bbb"), "foo:ccc"; + +# vim: expandtab shiftwidth=4 diff --git a/t/require.t b/t/require.rakutest similarity index 86% rename from t/require.t rename to t/require.rakutest index c265728..2853f4c 100644 --- a/t/require.t +++ b/t/require.rakutest @@ -1,8 +1,5 @@ -#!/usr/bin/env raku - -use v6.d; - use Test; + plan 2; use lib $*PROGRAM.parent.child('lib').Str; @@ -14,5 +11,4 @@ my $port; lives-ok { $port = ::("TestURIRequire").test('http://example.com'); }, "can use URI in a module that is itself required rather than used"; is $port, 80, "and got the right thing back"; - -done-testing; +# vim: expandtab shiftwidth=4 diff --git a/t/rfc-3986-examples.t b/t/rfc-3986-examples.rakutest similarity index 97% rename from t/rfc-3986-examples.t rename to t/rfc-3986-examples.rakutest index c72bd3a..700c952 100644 --- a/t/rfc-3986-examples.t +++ b/t/rfc-3986-examples.rakutest @@ -1,8 +1,9 @@ -use v6.d; use Test; -plan 25; use URI; + +plan 25; + my $u = URI.new('ftp://ftp.is.co.za/rfc/rfc1808.txt', :validating<1>); is($u.scheme, 'ftp', 'ftp scheme'); is($u.host, 'ftp.is.co.za', 'ftp host'); @@ -46,3 +47,4 @@ $u.parse('urn:oasis:names:specification:docbook:dtd:xml:4.1.2'); is($u.scheme, 'urn', 'urn scheme'); is($u.path, 'oasis:names:specification:docbook:dtd:xml:4.1.2', 'urn path'); +# vim: expandtab shiftwidth=4 diff --git a/t/utf8-c8.t b/t/utf8-c8.rakutest similarity index 92% rename from t/utf8-c8.t rename to t/utf8-c8.rakutest index 22fdb46..0fcf859 100644 --- a/t/utf8-c8.t +++ b/t/utf8-c8.rakutest @@ -1,7 +1,11 @@ -use v6.d; use Test; -plan 1; + use URI::Escape; + +plan 1; + my $uri = "file:///space/pub/music/mp3/Musopen%20DVD/Brahms%20-%20Symphony%20No%201%20in%20C%20Major/Symphony%20No.%201%20in%20C%20Minor,%20Op.%2068%20-%20IV.%20Adagio%20-%20Piu%CC%80%20andante%20-%20Allegro%20non%20troppo,%20ma%20con%20brio.mp3"; my $expected = "t/expected.txt".IO.slurp(:enc('utf8-c8')).chomp; is uri-unescape($uri, :enc('utf8-c8')), $expected, "uri-unescape works with encoding utf8-c8"; + +# vim: expandtab shiftwidth=4