iri 0.2 → 0.5.1.2
raw patch · 41 files changed
Files
- Setup.hs +0/−2
- iri.cabal +143/−94
- library/Iri/CodePointPredicates/Core.hs +13/−11
- library/Iri/CodePointPredicates/Rfc3986.hs +123/−121
- library/Iri/CodePointPredicates/Rfc3987.hs +207/−153
- library/Iri/Data.hs +15/−15
- library/Iri/Data/Functions.hs +5/−7
- library/Iri/Data/Instances/Eq.hs +4/−4
- library/Iri/Data/Instances/Generic.hs +40/−0
- library/Iri/Data/Instances/Hashable.hs +42/−0
- library/Iri/Data/Instances/Lift.hs +40/−34
- library/Iri/Data/Instances/Ord.hs +41/−0
- library/Iri/Data/Instances/Show.hs +7/−8
- library/Iri/Data/Types.hs +57/−73
- library/Iri/MonadPlus.hs +27/−14
- library/Iri/Optics.hs +10/−100
- library/Iri/Optics/Basics.hs +34/−0
- library/Iri/Optics/Defs.hs +74/−0
- library/Iri/Parsing/Attoparsec/ByteString.hs +103/−99
- library/Iri/Parsing/Attoparsec/Text.hs +102/−75
- library/Iri/Parsing/ByteString.hs +43/−17
- library/Iri/Parsing/Text.hs +56/−19
- library/Iri/PercentEncoding.hs +7/−6
- library/Iri/Prelude.hs +71/−53
- library/Iri/QuasiQuoter.hs +33/−38
- library/Iri/Rendering/ByteString.hs +4/−6
- library/Iri/Rendering/Poke.hs +0/−14
- library/Iri/Rendering/Ptr/Poke.hs +12/−0
- library/Iri/Rendering/Ptr/Poking.hs +64/−54
- library/Iri/Rendering/Text.hs +2/−4
- library/Iri/Rendering/Text/Internal.hs +49/−11
- library/Iri/Rendering/TextBuilder.hs +2/−4
- library/Iri/Rendering/TextBuilder/Internal.hs +113/−84
- library/Iri/Utf8CodePoint.hs +17/−21
- library/Iri/Vector.hs +3/−5
- punycode/Data/Text/Punycode.hs +4/−0
- punycode/Data/Text/Punycode/Decode.hs +106/−0
- punycode/Data/Text/Punycode/Encode.hs +96/−0
- punycode/Data/Text/Punycode/Shared.hs +39/−0
- test/Main.hs +124/−0
- tests/Main.hs +0/−93
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
iri.cabal view
@@ -1,120 +1,169 @@-name:- iri-version:- 0.2-category:- IRI, URI, URL, Data, Network-synopsis:- RFC-based International Resource Identifier library+cabal-version: 3.0+name: iri+version: 0.5.1.2+category: URI, URL, IRI, Network+synopsis: RFC-compliant universal resource identifier library (URL, URI, IRI) description:- This library provides a universal data model for representation of both URI (URL) and IRI.+ This library provides a universal data model for representation of URI, URL and IRI. It comes packed with parsing, rendering and serialisation functionality. It is implemented in conformance with the RFC-3986 and RFC-3987 specifications and is intended as a standard tool for all tasks related to resource identifiers.- .- The data is represented in Unicode,- and the API automates all the encoding fuss.-homepage:- https://github.com/nikita-volkov/iri -bug-reports:- https://github.com/nikita-volkov/iri/issues -author:- Nikita Volkov <nikita.y.volkov@mail.ru>-maintainer:- Nikita Volkov <nikita.y.volkov@mail.ru>-copyright:- (c) 2017, Nikita Volkov-license:- MIT-license-file:- LICENSE-build-type:- Simple-cabal-version:- >=1.10 +homepage: https://github.com/nikita-volkov/iri+bug-reports: https://github.com/nikita-volkov/iri/issues+author: Nikita Volkov <nikita.y.volkov@mail.ru>+maintainer: Nikita Volkov <nikita.y.volkov@mail.ru>+copyright: (c) 2017 Nikita Volkov+license: MIT+license-file: LICENSE+ source-repository head- type:- git- location:- git://github.com/nikita-volkov/iri.git+ type: git+ location: https://github.com/nikita-volkov/iri -library- hs-source-dirs:- library+common base+ default-language: Haskell2010 default-extensions:- Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, PatternSynonyms, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples- default-language:- Haskell2010+ BangPatterns+ BlockArguments+ ConstraintKinds+ DataKinds+ DefaultSignatures+ DeriveDataTypeable+ DeriveFoldable+ DeriveFunctor+ DeriveGeneric+ DeriveTraversable+ DerivingStrategies+ EmptyDataDecls+ FlexibleContexts+ FlexibleInstances+ FunctionalDependencies+ GADTs+ GeneralizedNewtypeDeriving+ LambdaCase+ LiberalTypeSynonyms+ MagicHash+ MultiParamTypeClasses+ MultiWayIf+ NoImplicitPrelude+ NoMonomorphismRestriction+ NumericUnderscores+ OverloadedStrings+ ParallelListComp+ PatternGuards+ QuasiQuotes+ RankNTypes+ RecordWildCards+ ScopedTypeVariables+ StandaloneDeriving+ StrictData+ TemplateHaskell+ TupleSections+ TypeApplications+ TypeFamilies+ TypeOperators+ UnboxedTuples+ ViewPatterns++common executable+ import: base+ ghc-options:+ -O2+ -threaded+ -with-rtsopts=-N+ -rtsopts+ -funbox-strict-fields++common test+ import: base+ ghc-options:+ -threaded+ -with-rtsopts=-N++library+ import: base+ hs-source-dirs: library exposed-modules:- Iri.Rendering.Text- Iri.Rendering.TextBuilder- Iri.Rendering.Ptr.Poking- Iri.Rendering.ByteString- Iri.Parsing.ByteString+ Iri.Data+ Iri.Optics Iri.Parsing.Attoparsec.ByteString Iri.Parsing.Attoparsec.Text+ Iri.Parsing.ByteString Iri.Parsing.Text Iri.QuasiQuoter- Iri.Data- Iri.Optics+ Iri.Rendering.ByteString+ Iri.Rendering.Ptr.Poking+ Iri.Rendering.Text+ Iri.Rendering.TextBuilder+ other-modules:- Iri.Rendering.Text.Internal- Iri.Rendering.TextBuilder.Internal- Iri.Data.Types+ Iri.CodePointPredicates.Core+ Iri.CodePointPredicates.Rfc3986+ Iri.CodePointPredicates.Rfc3987 Iri.Data.Functions+ Iri.Data.Instances.Eq+ Iri.Data.Instances.Generic+ Iri.Data.Instances.Hashable Iri.Data.Instances.Lift+ Iri.Data.Instances.Ord Iri.Data.Instances.Show- Iri.Data.Instances.Eq+ Iri.Data.Types+ Iri.MonadPlus+ Iri.Optics.Basics+ Iri.Optics.Defs+ Iri.PercentEncoding Iri.Prelude+ Iri.Rendering.Ptr.Poke+ Iri.Rendering.Text.Internal+ Iri.Rendering.TextBuilder.Internal Iri.Utf8CodePoint Iri.Vector- Iri.MonadPlus- Iri.CodePointPredicates.Core- Iri.CodePointPredicates.Rfc3986- Iri.CodePointPredicates.Rfc3987- Iri.Rendering.Poke- Iri.PercentEncoding+ build-depends:- th-lift-instances >=0.1.11 && <0.2,- th-lift >=0.7.7 && <0.8,- template-haskell >=2.9 && <3,- unordered-containers >=0.2 && <0.3,+ attoparsec >=0.13 && <0.15,+ base >=4.15 && <5,+ bytestring >=0.10 && <0.13,+ contravariant >=1.4 && <2,+ hashable >=1.2 && <2,+ ip >=1.7.8 && <2,+ iri:punycode,+ profunctors >=5.2 && <6, ptr >=0.16.1 && <0.17,- attoparsec >=0.13 && <0.14,- punycode >=2 && <3,- ip >=1.1 && <2,- text ==1.*,- text-builder >=0.5.1 && <0.6,- bytestring >=0.10 && <0.11,- vector >=0.10 && <0.13,+ template-haskell >=2.17 && <3,+ text >=2 && <3,+ text-builder ^>=1,+ th-lift >=0.8 && <0.9,+ th-lift-instances >=0.1.20 && <0.2,+ unordered-containers >=0.2 && <0.3,+ vector >=0.13 && <0.14, vector-builder >=0.3.4 && <0.4,- profunctors >=5.2 && <6,- contravariant >=1.4 && <2,- semigroups >=0.18 && <0.20,- bug ==1.*,- base-prelude >=1 && <2,- base >=4.7 && <5+ vector-instances >=3.4 && <4,+ wide-word >=0.1.6 && <2, -test-suite tests- type:- exitcode-stdio-1.0- hs-source-dirs:- tests- main-is:- Main.hs- default-extensions:- Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, PatternSynonyms, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples- default-language:- Haskell2010+library punycode+ hs-source-dirs: punycode+ default-language: Haskell2010 build-depends:- -- + base >=4.13 && <5,+ bytestring >=0.10 && <0.13,+ cereal >=0.5.8.3 && <0.6,+ mtl >=2 && <3,+ text >=2 && <3,++ exposed-modules: Data.Text.Punycode+ other-modules:+ Data.Text.Punycode.Decode+ Data.Text.Punycode.Encode+ Data.Text.Punycode.Shared++test-suite test+ import: test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Main.hs+ build-depends: iri,- -- testing:- tasty >=0.12 && <0.13,- tasty-quickcheck >=0.9 && <0.10,- tasty-hunit >=0.9 && <0.10,- quickcheck-instances >=0.3.11 && <0.4,- QuickCheck >=2.8.1 && <3,- --- rerebase >=1.1 && <2+ rerebase <2,+ tasty >=0.12 && <2,+ tasty-hunit >=0.9 && <0.11,
library/Iri/CodePointPredicates/Core.hs view
@@ -1,9 +1,7 @@-module Iri.CodePointPredicates.Core-where--import Iri.Prelude hiding ((|||), (&&&), inRange, Predicate)-import qualified Data.Vector as A+module Iri.CodePointPredicates.Core where +import qualified Data.Vector.Unboxed as A+import Iri.Prelude hiding (Predicate, inRange, (&&&), (|||)) type Predicate = Int -> Bool@@ -19,11 +17,13 @@ elem i (fmap ord string) infixr 2 |||+ (|||) :: Predicate -> Predicate -> Predicate (|||) left right i = left i || right i infixr 3 &&&+ (&&&) :: Predicate -> Predicate -> Predicate (&&&) left right i = left i && right i@@ -36,7 +36,7 @@ inCharRange min max = inRange (ord min) (ord max) -{-| 7-bit -}+-- | 7-bit septimal :: Predicate septimal i = i < 0x80@@ -47,11 +47,13 @@ control :: Predicate control i =- i <= 0x1F ||- i == 0x7F+ i+ <= 0x1F+ || i+ == 0x7F asciiAlphanumeric :: Predicate asciiAlphanumeric =- inCharRange 'a' 'z' |||- inCharRange 'A' 'Z' |||- inCharRange '0' '9'+ inCharRange 'a' 'z'+ ||| inCharRange 'A' 'Z'+ ||| inCharRange '0' '9'
library/Iri/CodePointPredicates/Rfc3986.hs view
@@ -1,105 +1,100 @@-{-|-References:-- <https://www.ietf.org/rfc/rfc3986 RFC3986: Uniform Resource Identifier (URI): Generic Syntax>--@-URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]--hier-part = "//" authority path-abempty- / path-absolute- / path-rootless- / path-empty--URI-reference = URI / relative-ref--absolute-URI = scheme ":" hier-part [ "?" query ]--relative-ref = relative-part [ "?" query ] [ "#" fragment ]--relative-part = "//" authority path-abempty- / path-absolute- / path-noscheme- / path-empty--scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )--authority = [ userinfo "@" ] host [ ":" port ]-userinfo = *( unreserved / pct-encoded / sub-delims / ":" )-host = IP-literal / IPv4address / reg-name-port = *DIGIT--IP-literal = "[" ( IPv6address / IPvFuture ) "]"--IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )--IPv6address = 6( h16 ":" ) ls32- / "::" 5( h16 ":" ) ls32- / [ h16 ] "::" 4( h16 ":" ) ls32- / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32- / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32- / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32- / [ *4( h16 ":" ) h16 ] "::" ls32- / [ *5( h16 ":" ) h16 ] "::" h16- / [ *6( h16 ":" ) h16 ] "::"--h16 = 1*4HEXDIG-ls32 = ( h16 ":" h16 ) / IPv4address-IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet-dec-octet = DIGIT ; 0-9- / %x31-39 DIGIT ; 10-99- / "1" 2DIGIT ; 100-199- / "2" %x30-34 DIGIT ; 200-249- / "25" %x30-35 ; 250-255--reg-name = *( unreserved / pct-encoded / sub-delims )--path = path-abempty ; begins with "/" or is empty- / path-absolute ; begins with "/" but not "//"- / path-noscheme ; begins with a non-colon segment- / path-rootless ; begins with a segment- / path-empty ; zero characters--path-abempty = *( "/" segment )-path-absolute = "/" [ segment-nz *( "/" segment ) ]-path-noscheme = segment-nz-nc *( "/" segment )-path-rootless = segment-nz *( "/" segment )-path-empty = 0<pchar>--segment = *pchar-segment-nz = 1*pchar-segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )- ; non-zero-length segment without any colon ":"--pchar = unreserved / pct-encoded / sub-delims / ":" / "@"--query = *( pchar / "/" / "?" )--fragment = *( pchar / "/" / "?" )--pct-encoded = "%" HEXDIG HEXDIG--unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"-reserved = gen-delims / sub-delims-gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"-sub-delims = "!" / "$" / "&" / "'" / "(" / ")"- / "*" / "+" / "," / ";" / "="-@---}-module Iri.CodePointPredicates.Rfc3986-where+-- |+-- References:+-- - <https://www.ietf.org/rfc/rfc3986 RFC3986: Uniform Resource Identifier (URI): Generic Syntax>+--+-- @+-- URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]+--+-- hier-part = "//" authority path-abempty+-- / path-absolute+-- / path-rootless+-- / path-empty+--+-- URI-reference = URI / relative-ref+--+-- absolute-URI = scheme ":" hier-part [ "?" query ]+--+-- relative-ref = relative-part [ "?" query ] [ "#" fragment ]+--+-- relative-part = "//" authority path-abempty+-- / path-absolute+-- / path-noscheme+-- / path-empty+--+-- scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )+--+-- authority = [ userinfo "@" ] host [ ":" port ]+-- userinfo = *( unreserved / pct-encoded / sub-delims / ":" )+-- host = IP-literal / IPv4address / reg-name+-- port = *DIGIT+--+-- IP-literal = "[" ( IPv6address / IPvFuture ) "]"+--+-- IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )+--+-- IPv6address = 6( h16 ":" ) ls32+-- / "::" 5( h16 ":" ) ls32+-- / [ h16 ] "::" 4( h16 ":" ) ls32+-- / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32+-- / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32+-- / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32+-- / [ *4( h16 ":" ) h16 ] "::" ls32+-- / [ *5( h16 ":" ) h16 ] "::" h16+-- / [ *6( h16 ":" ) h16 ] "::"+--+-- h16 = 1*4HEXDIG+-- ls32 = ( h16 ":" h16 ) / IPv4address+-- IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet+-- dec-octet = DIGIT ; 0-9+-- / %x31-39 DIGIT ; 10-99+-- / "1" 2DIGIT ; 100-199+-- / "2" %x30-34 DIGIT ; 200-249+-- / "25" %x30-35 ; 250-255+--+-- reg-name = *( unreserved / pct-encoded / sub-delims )+--+-- path = path-abempty ; begins with "/" or is empty+-- / path-absolute ; begins with "/" but not "//"+-- / path-noscheme ; begins with a non-colon segment+-- / path-rootless ; begins with a segment+-- / path-empty ; zero characters+--+-- path-abempty = *( "/" segment )+-- path-absolute = "/" [ segment-nz *( "/" segment ) ]+-- path-noscheme = segment-nz-nc *( "/" segment )+-- path-rootless = segment-nz *( "/" segment )+-- path-empty = 0<pchar>+--+-- segment = *pchar+-- segment-nz = 1*pchar+-- segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )+-- ; non-zero-length segment without any colon ":"+--+-- pchar = unreserved / pct-encoded / sub-delims / ":" / "@"+--+-- query = *( pchar / "/" / "?" )+--+-- fragment = *( pchar / "/" / "?" )+--+-- pct-encoded = "%" HEXDIG HEXDIG+--+-- unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"+-- reserved = gen-delims / sub-delims+-- gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"+-- sub-delims = "!" / "$" / "&" / "'" / "(" / ")"+-- / "*" / "+" / "," / ";" / "="+-- @+module Iri.CodePointPredicates.Rfc3986 where -import Iri.Prelude hiding ((|||), (&&&), inRange, Predicate) import Iri.CodePointPredicates.Core-import qualified Data.Vector as A--{-|-Reference:+import Iri.Prelude hiding (Predicate, inRange, (&&&), (|||)) -@-unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"-@--}+-- |+-- Reference:+--+-- @+-- unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"+-- @ unreserved :: Predicate unreserved = asciiAlphanumeric ||| oneOfChars "-._~"@@ -111,14 +106,16 @@ {-# NOINLINE scheme #-} scheme :: Predicate scheme =- cached $- asciiAlphanumeric ||| oneOfChars "+.-"+ cached+ $ asciiAlphanumeric+ ||| oneOfChars "+.-" {-# NOINLINE domainLabel #-} domainLabel :: Predicate domainLabel =- cached $- asciiAlphanumeric ||| oneOfChars "-_~"+ cached+ $ asciiAlphanumeric+ ||| oneOfChars "-_~" {- pchar = unreserved / pct-encoded / sub-delims / ":" / "@"@@ -126,24 +123,26 @@ {-# NOINLINE unencodedPathSegment #-} unencodedPathSegment :: Predicate unencodedPathSegment =- cached $- unreserved ||| subDelims ||| oneOfChars ":@"--{-|-Reference:--@-query = *( pchar / "/" / "?" )-@+ cached+ $ unreserved+ ||| subDelims+ ||| oneOfChars ":@" -Notice that we've added the "|" char, because some real life URIs seem to contain it.-Also we've excluded the '+' char, because it gets decoded as a space char.--}+-- |+-- Reference:+--+-- @+-- query = *( pchar / "/" / "?" )+-- @+--+-- Notice that we've added the "|" char, because some real life URIs seem to contain it.+-- Also we've excluded the '+' char, because it gets decoded as a space char. {-# NOINLINE unencodedQuery #-} unencodedQuery :: Predicate unencodedQuery =- cached $- (unencodedPathSegment ||| oneOfChars "/?|") &&& (/= 43)+ cached+ $ (unencodedPathSegment ||| oneOfChars "/?|[]\"'")+ &&& (/= 43) unencodedFragment :: Predicate unencodedFragment =@@ -152,11 +151,14 @@ {-# NOINLINE unencodedQueryComponent #-} unencodedQueryComponent :: Predicate unencodedQueryComponent =- cached $- unencodedQuery &&& not . oneOfChars "=&;"+ cached+ $ unencodedQuery+ &&& not+ . oneOfChars "=&;" {-# NOINLINE unencodedUserInfoComponent #-} unencodedUserInfoComponent :: Predicate unencodedUserInfoComponent =- cached $- unreserved ||| subDelims+ cached+ $ unreserved+ ||| subDelims
library/Iri/CodePointPredicates/Rfc3987.hs view
@@ -1,125 +1,121 @@-{-|-References:-- <https://www.ietf.org/rfc/rfc3987 RFC3987>--@-The following rules are different from those in [RFC3986]:--IRI = scheme ":" ihier-part [ "?" iquery ]- [ "#" ifragment ]--ihier-part = "//" iauthority ipath-abempty- / ipath-absolute- / ipath-rootless- / ipath-empty--IRI-reference = IRI / irelative-ref--absolute-IRI = scheme ":" ihier-part [ "?" iquery ]--irelative-ref = irelative-part [ "?" iquery ] [ "#" ifragment ]--irelative-part = "//" iauthority ipath-abempty- / ipath-absolute-- / ipath-noscheme- / ipath-empty--iauthority = [ iuserinfo "@" ] ihost [ ":" port ]-iuserinfo = *( iunreserved / pct-encoded / sub-delims / ":" )-ihost = IP-literal / IPv4address / ireg-name--ireg-name = *( iunreserved / pct-encoded / sub-delims )--ipath = ipath-abempty ; begins with "/" or is empty- / ipath-absolute ; begins with "/" but not "//"- / ipath-noscheme ; begins with a non-colon segment- / ipath-rootless ; begins with a segment- / ipath-empty ; zero characters--ipath-abempty = *( "/" isegment )-ipath-absolute = "/" [ isegment-nz *( "/" isegment ) ]-ipath-noscheme = isegment-nz-nc *( "/" isegment )-ipath-rootless = isegment-nz *( "/" isegment )-ipath-empty = 0<ipchar>--isegment = *ipchar-isegment-nz = 1*ipchar-isegment-nz-nc = 1*( iunreserved / pct-encoded / sub-delims- / "@" )- ; non-zero-length segment without any colon ":"--ipchar = iunreserved / pct-encoded / sub-delims / ":"- / "@"--iquery = *( ipchar / iprivate / "/" / "?" )--ifragment = *( ipchar / "/" / "?" )--iunreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" / ucschar--ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF- / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD- / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD- / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD- / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD- / %xD0000-DFFFD / %xE1000-EFFFD--iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD--Some productions are ambiguous. The "first-match-wins" (a.k.a.-"greedy") algorithm applies. For details, see [RFC3986].--The following rules are the same as those in [RFC3986]:--scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )--port = *DIGIT--IP-literal = "[" ( IPv6address / IPvFuture ) "]"--IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )--IPv6address = 6( h16 ":" ) ls32- / "::" 5( h16 ":" ) ls32- / [ h16 ] "::" 4( h16 ":" ) ls32- / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32- / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32- / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32- / [ *4( h16 ":" ) h16 ] "::" ls32- / [ *5( h16 ":" ) h16 ] "::" h16- / [ *6( h16 ":" ) h16 ] "::"--h16 = 1*4HEXDIG-ls32 = ( h16 ":" h16 ) / IPv4address--IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet--dec-octet = DIGIT ; 0-9- / %x31-39 DIGIT ; 10-99- / "1" 2DIGIT ; 100-199- / "2" %x30-34 DIGIT ; 200-249- / "25" %x30-35 ; 250-255--pct-encoded = "%" HEXDIG HEXDIG--unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"-reserved = gen-delims / sub-delims-gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"-sub-delims = "!" / "$" / "&" / "'" / "(" / ")"- / "*" / "+" / "," / ";" / "="--This syntax does not support IPv6 scoped addressing zone identifiers.-@---}-module Iri.CodePointPredicates.Rfc3987-where+-- |+-- References:+-- - <https://www.ietf.org/rfc/rfc3987 RFC3987>+--+-- @+-- The following rules are different from those in [RFC3986]:+--+-- IRI = scheme ":" ihier-part [ "?" iquery ]+-- [ "#" ifragment ]+--+-- ihier-part = "//" iauthority ipath-abempty+-- / ipath-absolute+-- / ipath-rootless+-- / ipath-empty+--+-- IRI-reference = IRI / irelative-ref+--+-- absolute-IRI = scheme ":" ihier-part [ "?" iquery ]+--+-- irelative-ref = irelative-part [ "?" iquery ] [ "#" ifragment ]+--+-- irelative-part = "//" iauthority ipath-abempty+-- / ipath-absolute+--+-- / ipath-noscheme+-- / ipath-empty+--+-- iauthority = [ iuserinfo "@" ] ihost [ ":" port ]+-- iuserinfo = *( iunreserved / pct-encoded / sub-delims / ":" )+-- ihost = IP-literal / IPv4address / ireg-name+--+-- ireg-name = *( iunreserved / pct-encoded / sub-delims )+--+-- ipath = ipath-abempty ; begins with "/" or is empty+-- / ipath-absolute ; begins with "/" but not "//"+-- / ipath-noscheme ; begins with a non-colon segment+-- / ipath-rootless ; begins with a segment+-- / ipath-empty ; zero characters+--+-- ipath-abempty = *( "/" isegment )+-- ipath-absolute = "/" [ isegment-nz *( "/" isegment ) ]+-- ipath-noscheme = isegment-nz-nc *( "/" isegment )+-- ipath-rootless = isegment-nz *( "/" isegment )+-- ipath-empty = 0<ipchar>+--+-- isegment = *ipchar+-- isegment-nz = 1*ipchar+-- isegment-nz-nc = 1*( iunreserved / pct-encoded / sub-delims+-- / "@" )+-- ; non-zero-length segment without any colon ":"+--+-- ipchar = iunreserved / pct-encoded / sub-delims / ":"+-- / "@"+--+-- iquery = *( ipchar / iprivate / "/" / "?" )+--+-- ifragment = *( ipchar / "/" / "?" )+--+-- iunreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" / ucschar+--+-- ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF+-- / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD+-- / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD+-- / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD+-- / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD+-- / %xD0000-DFFFD / %xE1000-EFFFD+--+-- iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD+--+-- Some productions are ambiguous. The "first-match-wins" (a.k.a.+-- "greedy") algorithm applies. For details, see [RFC3986].+--+-- The following rules are the same as those in [RFC3986]:+--+-- scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )+--+-- port = *DIGIT+--+-- IP-literal = "[" ( IPv6address / IPvFuture ) "]"+--+-- IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )+--+-- IPv6address = 6( h16 ":" ) ls32+-- / "::" 5( h16 ":" ) ls32+-- / [ h16 ] "::" 4( h16 ":" ) ls32+-- / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32+-- / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32+-- / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32+-- / [ *4( h16 ":" ) h16 ] "::" ls32+-- / [ *5( h16 ":" ) h16 ] "::" h16+-- / [ *6( h16 ":" ) h16 ] "::"+--+-- h16 = 1*4HEXDIG+-- ls32 = ( h16 ":" h16 ) / IPv4address+--+-- IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet+--+-- dec-octet = DIGIT ; 0-9+-- / %x31-39 DIGIT ; 10-99+-- / "1" 2DIGIT ; 100-199+-- / "2" %x30-34 DIGIT ; 200-249+-- / "25" %x30-35 ; 250-255+--+-- pct-encoded = "%" HEXDIG HEXDIG+--+-- unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"+-- reserved = gen-delims / sub-delims+-- gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"+-- sub-delims = "!" / "$" / "&" / "'" / "(" / ")"+-- / "*" / "+" / "," / ";" / "="+--+-- This syntax does not support IPv6 scoped addressing zone identifiers.+-- @+module Iri.CodePointPredicates.Rfc3987 where -import Iri.Prelude hiding ((|||), (&&&), inRange, Predicate) import Iri.CodePointPredicates.Core import qualified Iri.CodePointPredicates.Rfc3986 as A-+import Iri.Prelude hiding (Predicate, inRange, (&&&), (|||)) scheme :: Predicate scheme =@@ -144,24 +140,22 @@ unencodedPathSegment = unreserved ||| A.subDelims ||| oneOfChars ":@" -{-|-Reference:--@-iquery = *( ipchar / iprivate / "/" / "?" )-@--Notice that we've added the "|" char, because some real life URIs seem to contain it.-Also we've excluded the '+' char, because it gets decoded as a space char.--}+-- |+-- Reference:+--+-- @+-- iquery = *( ipchar / iprivate / "/" / "?" )+-- @+--+-- Notice that we've added the "|" char, because some real life URIs seem to contain it.+-- Also we've excluded the '+' char, because it gets decoded as a space char. unencodedQuery :: Predicate unencodedQuery = (unencodedPathSegment ||| private ||| oneOfChars "/?|") &&& (/= 43) -{-|-Notice that we've added the "|" char, because some real life URIs seem to contain it.-Also we've excluded the '+' char, because it gets decoded as a space char.--}+-- |+-- Notice that we've added the "|" char, because some real life URIs seem to contain it.+-- Also we've excluded the '+' char, because it gets decoded as a space char. unencodedFragment :: Predicate unencodedFragment = (unencodedPathSegment ||| oneOfChars "/?|") &&& (/= 43)@@ -183,29 +177,89 @@ -} ucs :: Predicate ucs x =- x >= 0xA0 && x <= 0xD7FF ||- x >= 0xF900 && x <= 0xFDCF ||- x >= 0xFDF0 && x <= 0xFFEF ||- x >= 0x10000 && x <= 0x1FFFD ||- x >= 0x20000 && x <= 0x2FFFD ||- x >= 0x30000 && x <= 0x3FFFD ||- x >= 0x40000 && x <= 0x4FFFD ||- x >= 0x50000 && x <= 0x5FFFD ||- x >= 0x60000 && x <= 0x6FFFD ||- x >= 0x70000 && x <= 0x7FFFD ||- x >= 0x80000 && x <= 0x8FFFD ||- x >= 0x90000 && x <= 0x9FFFD ||- x >= 0xA0000 && x <= 0xAFFFD ||- x >= 0xB0000 && x <= 0xBFFFD ||- x >= 0xC0000 && x <= 0xCFFFD ||- x >= 0xD0000 && x <= 0xDFFFD ||- x >= 0xE1000 && x <= 0xEFFFD+ x+ >= 0xA0+ && x+ <= 0xD7FF+ || x+ >= 0xF900+ && x+ <= 0xFDCF+ || x+ >= 0xFDF0+ && x+ <= 0xFFEF+ || x+ >= 0x10000+ && x+ <= 0x1FFFD+ || x+ >= 0x20000+ && x+ <= 0x2FFFD+ || x+ >= 0x30000+ && x+ <= 0x3FFFD+ || x+ >= 0x40000+ && x+ <= 0x4FFFD+ || x+ >= 0x50000+ && x+ <= 0x5FFFD+ || x+ >= 0x60000+ && x+ <= 0x6FFFD+ || x+ >= 0x70000+ && x+ <= 0x7FFFD+ || x+ >= 0x80000+ && x+ <= 0x8FFFD+ || x+ >= 0x90000+ && x+ <= 0x9FFFD+ || x+ >= 0xA0000+ && x+ <= 0xAFFFD+ || x+ >= 0xB0000+ && x+ <= 0xBFFFD+ || x+ >= 0xC0000+ && x+ <= 0xCFFFD+ || x+ >= 0xD0000+ && x+ <= 0xDFFFD+ || x+ >= 0xE1000+ && x+ <= 0xEFFFD {- iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD -} private :: Predicate private x =- x >= 0xE000 && x <= 0xF8FF ||- x >= 0xF0000 && x <= 0xFFFFD ||- x >= 0x100000 && x <= 0x10FFFD+ x+ >= 0xE000+ && x+ <= 0xF8FF+ || x+ >= 0xF0000+ && x+ <= 0xFFFFD+ || x+ >= 0x100000+ && x+ <= 0x10FFFD
library/Iri/Data.hs view
@@ -1,19 +1,19 @@-{-|-References:--* <https://tools.ietf.org/rfc/rfc3986.txt URI RFC>-* <https://tools.ietf.org/rfc/rfc3987.txt IRI RFC>--}+-- |+-- References:+--+-- * <https://tools.ietf.org/rfc/rfc3986.txt URI RFC>+-- * <https://tools.ietf.org/rfc/rfc3987.txt IRI RFC> module Iri.Data-(- module Iri.Data.Types,- module Iri.Data.Functions,-)+ ( module Iri.Data.Types,+ module Iri.Data.Functions,+ ) where -import Iri.Data.Types import Iri.Data.Functions-import Iri.Data.Instances.Lift-import Iri.Data.Instances.Eq-import Iri.Data.Instances.Show-+import Iri.Data.Instances.Eq ()+import Iri.Data.Instances.Generic ()+import Iri.Data.Instances.Hashable ()+import Iri.Data.Instances.Lift ()+import Iri.Data.Instances.Ord ()+import Iri.Data.Instances.Show ()+import Iri.Data.Types
library/Iri/Data/Functions.hs view
@@ -1,11 +1,9 @@-module Iri.Data.Functions-where+module Iri.Data.Functions where -import Iri.Prelude import Iri.Data.Types-+import Iri.Prelude -{-| Try to specialize a general IRI to HTTP -}+-- | Try to specialize a general IRI to HTTP httpIriFromIri :: Iri -> Either Text HttpIri httpIriFromIri (Iri (Scheme scheme) hierarchy query fragment) = do@@ -16,10 +14,10 @@ case hierarchy of AuthorisedHierarchy (Authority userInfo host port) path -> case userInfo of MissingUserInfo -> Right (HttpIri security host port path query fragment)- PresentUserInfo (User user) _ -> Left ("User Info is present")+ PresentUserInfo (User _) _ -> Left ("User Info is present") _ -> Left ("Not an authorised hierarchy") -{-| Generalize an HTTP IRI to IRI -}+-- | Generalize an HTTP IRI to IRI iriFromHttpIri :: HttpIri -> Iri iriFromHttpIri (HttpIri (Security secure) host port path query fragment) = Iri scheme hierarchy query fragment
library/Iri/Data/Instances/Eq.hs view
@@ -1,9 +1,9 @@-module Iri.Data.Instances.Eq-where+{-# OPTIONS_GHC -Wno-orphans #-} -import Iri.Prelude-import Iri.Data.Types+module Iri.Data.Instances.Eq where +import Iri.Data.Types+import Iri.Prelude deriving instance Eq Iri
+ library/Iri/Data/Instances/Generic.hs view
@@ -0,0 +1,40 @@+{-# OPTIONS_GHC -Wno-orphans #-}++module Iri.Data.Instances.Generic where++import Iri.Data.Types+import Iri.Prelude++deriving instance Generic Iri++deriving instance Generic Scheme++deriving instance Generic Hierarchy++deriving instance Generic Authority++deriving instance Generic UserInfo++deriving instance Generic User++deriving instance Generic Password++deriving instance Generic Host++deriving instance Generic RegName++deriving instance Generic DomainLabel++deriving instance Generic Port++deriving instance Generic Path++deriving instance Generic PathSegment++deriving instance Generic Query++deriving instance Generic Fragment++deriving instance Generic HttpIri++deriving instance Generic Security
+ library/Iri/Data/Instances/Hashable.hs view
@@ -0,0 +1,42 @@+{-# OPTIONS_GHC -Wno-orphans #-}++module Iri.Data.Instances.Hashable where++import Iri.Data.Instances.Eq ()+import Iri.Data.Instances.Generic ()+import Iri.Data.Types+import Iri.Prelude++instance Hashable Iri++instance Hashable Scheme++instance Hashable Hierarchy++instance Hashable Authority++instance Hashable UserInfo++instance Hashable User++instance Hashable Password++instance Hashable Host++instance Hashable RegName++instance Hashable DomainLabel++instance Hashable Port++instance Hashable Path++instance Hashable PathSegment++instance Hashable Query++instance Hashable Fragment++instance Hashable HttpIri++instance Hashable Security
library/Iri/Data/Instances/Lift.hs view
@@ -1,41 +1,47 @@-module Iri.Data.Instances.Lift-where+{-# OPTIONS_GHC -Wno-orphans #-} -import Iri.Prelude+module Iri.Data.Instances.Lift where++import qualified Data.WideWord.Word128 import Iri.Data.Types+import Iri.Prelude import Language.Haskell.TH.Lift---deriveLift ''Iri--deriveLift ''Scheme--deriveLift ''Hierarchy--deriveLift ''Authority--deriveLift ''UserInfo--deriveLift ''User--deriveLift ''Password--deriveLift ''Host--deriveLift ''RegName--deriveLift ''DomainLabel--deriveLift ''Port--deriveLift ''Path--deriveLift ''PathSegment+import Language.Haskell.TH.Syntax+import qualified Net.IPv6 -deriveLift ''Query+fmap concat+ $ traverse deriveLift+ $ [ ''RegName,+ ''DomainLabel+ ] -deriveLift ''Fragment+instance Lift Host where+ lift = \case+ NamedHost regName ->+ AppE (ConE 'NamedHost) <$> lift regName+ IpV4Host ipV4 ->+ AppE (ConE 'IpV4Host) <$> $(makeLift ''IPv4) ipV4+ IpV6Host (Net.IPv6.IPv6 word128) -> do+ word128Exp <- $(makeLift ''Data.WideWord.Word128.Word128) word128+ pure (AppE (ConE 'IpV6Host) (AppE (ConE 'Net.IPv6.IPv6) word128Exp)) -deriveLift ''HttpIri+ liftTyped =+ Code . fmap TExp . lift -deriveLift ''Security+fmap concat+ $ traverse deriveLift+ $ [ ''Authority,+ ''Fragment,+ ''Hierarchy,+ ''HttpIri,+ ''Iri,+ ''Password,+ ''Path,+ ''PathSegment,+ ''Port,+ ''Query,+ ''Scheme,+ ''Security,+ ''User,+ ''UserInfo+ ]
+ library/Iri/Data/Instances/Ord.hs view
@@ -0,0 +1,41 @@+{-# OPTIONS_GHC -Wno-orphans #-}++module Iri.Data.Instances.Ord where++import Iri.Data.Instances.Eq ()+import Iri.Data.Types+import Iri.Prelude++deriving instance Ord Iri++deriving instance Ord Scheme++deriving instance Ord Hierarchy++deriving instance Ord Authority++deriving instance Ord UserInfo++deriving instance Ord User++deriving instance Ord Password++deriving instance Ord Host++deriving instance Ord RegName++deriving instance Ord DomainLabel++deriving instance Ord Port++deriving instance Ord Path++deriving instance Ord PathSegment++deriving instance Ord Query++deriving instance Ord Fragment++deriving instance Ord HttpIri++deriving instance Ord Security
library/Iri/Data/Instances/Show.hs view
@@ -1,19 +1,19 @@-module Iri.Data.Instances.Show-where+{-# OPTIONS_GHC -Wno-orphans #-} -import Iri.Prelude+module Iri.Data.Instances.Show where++import qualified Data.Text as B import Iri.Data.Types+import Iri.Prelude import qualified Iri.Rendering.Text.Internal as A-import qualified Data.Text as B - instance Show Iri where show =- B.unpack . A.iri+ mappend "\"" . flip mappend "\"" . B.unpack . A.iri instance Show HttpIri where show =- B.unpack . A.httpIri+ mappend "\"" . flip mappend "\"" . B.unpack . A.httpIri deriving instance Show Scheme @@ -44,4 +44,3 @@ deriving instance Show Fragment deriving instance Show Security-
library/Iri/Data/Types.hs view
@@ -1,99 +1,83 @@-{-|-References:--* <https://tools.ietf.org/rfc/rfc3986.txt URI RFC>-* <https://tools.ietf.org/rfc/rfc3987.txt IRI RFC>--} {-# OPTIONS_GHC -funbox-strict-fields #-}-module Iri.Data.Types-where -import Iri.Prelude---{-|-Thorough structure of IRI.--}-data Iri =- Iri !Scheme !Hierarchy !Query !Fragment+-- |+-- References:+--+-- * <https://tools.ietf.org/rfc/rfc3986.txt URI RFC>+-- * <https://tools.ietf.org/rfc/rfc3987.txt IRI RFC>+module Iri.Data.Types where -newtype Scheme =- Scheme ByteString+import Iri.Prelude -data Hierarchy =- AuthorisedHierarchy !Authority !Path |- AbsoluteHierarchy !Path |- RelativeHierarchy !Path+-- |+-- Thorough structure of IRI or URI.+data Iri = Iri !Scheme !Hierarchy !Query !Fragment -data Authority =- Authority !UserInfo !Host !Port+newtype Scheme = Scheme ByteString -data UserInfo =- PresentUserInfo !User !Password |- MissingUserInfo+data Hierarchy+ = AuthorisedHierarchy !Authority !Path+ | AbsoluteHierarchy !Path+ | RelativeHierarchy !Path -newtype User =- User Text+data Authority = Authority !UserInfo !Host !Port -data Password =- PresentPassword !Text |- MissingPassword+data UserInfo+ = PresentUserInfo !User !Password+ | MissingUserInfo -data Host =- NamedHost !RegName |- IpV4Host !IPv4 |- IpV6Host !IPv6+newtype User = User ByteString -newtype RegName =- RegName (Vector DomainLabel)+data Password+ = PresentPassword !ByteString+ | MissingPassword -newtype DomainLabel =- DomainLabel Text+data Host+ = NamedHost !RegName+ | IpV4Host !IPv4+ | IpV6Host !IPv6 -data Port =- PresentPort !Word16 |- MissingPort+newtype RegName = RegName (Vector DomainLabel) -newtype Path =- Path (Vector PathSegment)+data DomainLabel = DomainLabel Text -newtype PathSegment =- PathSegment Text+data Port+ = PresentPort !Word16+ | MissingPort -{-|-Since the exact structure of the query string is not standardised and-methods used to parse the query string may differ between websites,-we simply represent it as a decoded Unicode string.+newtype Path = Path (Vector PathSegment) -See <https://en.wikipedia.org/wiki/Query_string>.--}-newtype Query =- Query Text+newtype PathSegment = PathSegment ByteString -newtype Fragment =- Fragment Text+-- |+-- Since the exact structure of the query string is not standardised and+-- methods used to parse the query string may differ between websites,+-- we simply represent it as percent-decoded bytes.+--+-- See <https://en.wikipedia.org/wiki/Query_string>.+newtype Query = Query ByteString +newtype Fragment = Fragment ByteString -- * Special cases-------------------------- --- ** HTTP special case ------------------------- -{-|-HTTP being by far the most common use-case for resource identifiers,-it's been isolated into a dedicated data-type,-which is optimised for that particular case.+-- ** HTTP special case -Compared to the general IRI definition it:+------------------------- -* only supports the HTTP and HTTPS schemes-* misses the Username and Password components-* requires the Host component-* requires the Path component to be absolute--}-data HttpIri =- HttpIri !Security !Host !Port !Path !Query !Fragment+-- |+-- HTTP being by far the most common use-case for resource identifiers,+-- it's been isolated into a dedicated data-type,+-- which is optimised for that particular case.+--+-- Compared to the general URI definition it:+--+-- * only supports the HTTP and HTTPS schemes+-- * misses the Username and Password components+-- * requires the Host component+-- * requires the Path component to be absolute+data HttpIri = HttpIri !Security !Host !Port !Path !Query !Fragment -newtype Security =- Security Bool+newtype Security = Security Bool
library/Iri/MonadPlus.hs view
@@ -1,30 +1,43 @@-module Iri.MonadPlus-where--import Iri.Prelude hiding (null, length)+module Iri.MonadPlus where +import Iri.Prelude hiding (foldl)+import qualified Ptr.ByteString as ByteString+import qualified Ptr.Poking as Poking {-# INLINE foldl #-}-foldl :: MonadPlus m => (a -> b -> a) -> a -> m b -> m a+foldl :: (MonadPlus m) => (a -> b -> a) -> a -> m b -> m a foldl step start elementParser = loop start where loop state = mplus- (do- element <- elementParser- loop $! step state element)+ ( do+ element <- elementParser+ loop $! step state element+ ) (return state) {-# INLINE foldlM #-}-foldlM :: MonadPlus m => (a -> b -> m a) -> a -> m b -> m a+foldlM :: (MonadPlus m) => (a -> b -> m a) -> a -> m b -> m a foldlM step start elementParser = loop start where loop state = join- (mplus- (do- element <- elementParser- return (step state element >>= loop))- (return (return state)))+ ( mplus+ ( do+ element <- elementParser+ return (step state element >>= loop)+ )+ (return (return state))+ )++{-# INLINE foldByteString #-}+foldByteString :: (MonadPlus m) => m ByteString -> m ByteString+foldByteString =+ fmap ByteString.poking+ . foldl (\poking -> mappend poking . Poking.bytes) mempty++{-# INLINE fold #-}+fold :: (MonadPlus m, Monoid a) => m a -> m a+fold = foldl mappend mempty
library/Iri/Optics.hs view
@@ -1,103 +1,13 @@-{-|-These are the beginnings of a Lens API.-It is compatible with the general Van Laarhoven lens libraries, such as \"lens\".--Many more definitions can be implemented, so do PRs if you miss any!--}+-- |+-- These are the beginnings of a Lens API.+-- It is compatible with the general Van Laarhoven lens libraries, such as \"lens\".+--+-- Many more definitions can be implemented, so do PRs if you miss any! module Iri.Optics-(- -- * Definitions- Lens,- Lens',- Prism,- Prism',- -- * Prisms- iriHttpIriPrism,- uriByteStringIriPrism,- uriByteStringHttpIriPrism,- iriTextIriPrism,- iriTextHttpIriPrism,- -- * Lenses- iriSchemeLens,- iriHierarchyLens,- iriQueryLens,- iriFragmentLens,-)+ ( module Basics,+ module Defs,+ ) where -import Iri.Prelude-import Iri.Data-import qualified Iri.Rendering.ByteString as A-import qualified Iri.Parsing.ByteString as B-import qualified Iri.Rendering.Text as C-import qualified Iri.Parsing.Text as D---type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t--type Lens' s a = Lens s s a a--type Prism s t a b = forall p f. (Choice p, Applicative f) => p a (f b) -> p s (f t)--type Prism' s a = Prism s s a a--{-# INLINE prism #-}-prism :: (b -> t) -> (s -> Either t a) -> Prism s t a b-prism bt seta =- dimap seta (either pure (fmap bt)) . right'--{-# INLINE lens #-}-lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b-lens sa sbt afb s =- sbt s <$> afb (sa s)---- * Prisms----------------------------iriHttpIriPrism :: Prism' Iri HttpIri-iriHttpIriPrism =- prism iriFromHttpIri (\ iri -> either (const (Left iri)) Right (httpIriFromIri iri))--uriByteStringIriPrism :: Prism' ByteString Iri-uriByteStringIriPrism =- prism A.uri (\ bytes -> either (const (Left bytes)) Right (B.uri bytes))--uriByteStringHttpIriPrism :: Prism' ByteString HttpIri-uriByteStringHttpIriPrism =- uriByteStringIriPrism . iriHttpIriPrism--iriTextIriPrism :: Prism' Text Iri-iriTextIriPrism =- prism C.iri (\ text -> either (const (Left text)) Right (D.iri text))--iriTextHttpIriPrism :: Prism' Text HttpIri-iriTextHttpIriPrism =- iriTextIriPrism . iriHttpIriPrism----- * Lenses----------------------------iriSchemeLens :: Lens' Iri ByteString-iriSchemeLens =- lens- (\ (Iri (Scheme x) _ _ _) -> x)- (\ (Iri _ hierarchy query fragment) x -> Iri (Scheme x) hierarchy query fragment)--iriHierarchyLens :: Lens' Iri Hierarchy-iriHierarchyLens =- lens- (\ (Iri _ x _ _) -> x)- (\ (Iri scheme _ query fragment) x -> Iri scheme x query fragment)--iriQueryLens :: Lens' Iri Text-iriQueryLens =- lens- (\ (Iri _ _ (Query x) _) -> x)- (\ (Iri scheme hierarchy _ fragment) x -> Iri scheme hierarchy (Query x) fragment)--iriFragmentLens :: Lens' Iri Text-iriFragmentLens =- lens- (\ (Iri _ _ _ (Fragment x)) -> x)- (\ (Iri scheme hierarchy query _) x -> Iri scheme hierarchy query (Fragment x))+import Iri.Optics.Basics as Basics (Lens, Lens', Prism, Prism', Traversal, Traversal')+import Iri.Optics.Defs as Defs
+ library/Iri/Optics/Basics.hs view
@@ -0,0 +1,34 @@+module Iri.Optics.Basics where++import Iri.Prelude++type Lens s t a b = forall f. (Functor f) => (a -> f b) -> s -> f t++type Lens' s a = Lens s s a a++type Prism s t a b = forall p f. (Choice p, Applicative f) => p a (f b) -> p s (f t)++type Prism' s a = Prism s s a a++type Traversal s t a b = forall f. (Applicative f) => (a -> f b) -> s -> f t++type Traversal' s a = Traversal s s a a++type Iso s t a b = forall p f. (Profunctor p, Functor f) => p a (f b) -> p s (f t)++type Iso' s a = Iso s s a a++{-# INLINE prism #-}+prism :: (b -> t) -> (s -> Either t a) -> Prism s t a b+prism bt seta =+ dimap seta (either pure (fmap bt)) . right'++{-# INLINE prism' #-}+prism' :: (a -> s) -> (s -> Maybe a) -> Prism' s a+prism' as sma =+ prism as (\s -> maybe (Left s) Right (sma s))++{-# INLINE lens #-}+lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b+lens sa sbt afb s =+ sbt s <$> afb (sa s)
+ library/Iri/Optics/Defs.hs view
@@ -0,0 +1,74 @@+module Iri.Optics.Defs where++import qualified Data.Text.Encoding as Text+import Iri.Data+import Iri.Optics.Basics+import qualified Iri.Parsing.ByteString as B+import qualified Iri.Parsing.Text as D+import Iri.Prelude+import qualified Iri.Rendering.ByteString as A+import qualified Iri.Rendering.Text as C++-- * Definitions by source++-------------------------++-- ** Text++-------------------------++textIriIri :: Prism' Text Iri+textIriIri =+ prism C.iri (\text -> either (const (Left text)) Right (D.iri text))++textIriHttpIri :: Prism' Text HttpIri+textIriHttpIri =+ textIriIri . iriHttpIri++-- ** ByteString++-------------------------++byteStringIri :: Prism' ByteString Iri+byteStringIri =+ prism A.uri (\bytes -> either (const (Left bytes)) Right (B.uri bytes))++byteStringHttpIri :: Prism' ByteString HttpIri+byteStringHttpIri =+ byteStringIri . iriHttpIri++byteStringTextInUtf8 :: Prism' ByteString Text+byteStringTextInUtf8 = prism' Text.encodeUtf8 (either (const Nothing) Just . Text.decodeUtf8')++-- ** IRI++-------------------------++iriHttpIri :: Prism' Iri HttpIri+iriHttpIri = prism' iriFromHttpIri (either (const Nothing) Just . httpIriFromIri)++iriScheme :: Lens' Iri Scheme+iriScheme = lens (\(Iri x _ _ _) -> x) (\(Iri _ hierarchy query fragment) x -> Iri x hierarchy query fragment)++iriHierarchy :: Lens' Iri Hierarchy+iriHierarchy = lens (\(Iri _ x _ _) -> x) (\(Iri scheme _ query fragment) x -> Iri scheme x query fragment)++iriQuery :: Lens' Iri Query+iriQuery = lens (\(Iri _ _ x _) -> x) (\(Iri scheme hierarchy _ fragment) x -> Iri scheme hierarchy x fragment)++iriFragment :: Lens' Iri Fragment+iriFragment = lens (\(Iri _ _ _ x) -> x) (\(Iri scheme hierarchy query _) x -> Iri scheme hierarchy query x)++-- ** Scheme++-------------------------++schemeByteString :: Lens' Scheme ByteString+schemeByteString = lens (\(Scheme x) -> x) (const Scheme)++-- ** Fragment++-------------------------++fragmentByteString :: Lens' Fragment ByteString+fragmentByteString = lens (\(Fragment x) -> x) (const Fragment)
library/Iri/Parsing/Attoparsec/ByteString.hs view
@@ -1,33 +1,28 @@+{-# OPTIONS_GHC -Wno-unused-top-binds #-}+ module Iri.Parsing.Attoparsec.ByteString-(- uri,- httpUri,-)+ ( uri,+ httpUri,+ regName,+ ) where -import Iri.Prelude hiding (foldl, hash)-import Iri.Data import Data.Attoparsec.ByteString hiding (try) import qualified Data.Attoparsec.ByteString.Char8 as F import qualified Data.ByteString as K-import qualified Data.Text as T-import qualified Data.Text.Punycode as A import qualified Data.Text.Encoding as B import qualified Data.Text.Encoding.Error as L-import qualified Data.HashMap.Strict as O+import qualified Data.Text.Punycode as A import qualified Data.Vector as S-import qualified VectorBuilder.Builder as P-import qualified VectorBuilder.Vector as Q-import qualified VectorBuilder.MonadPlus as E-import qualified Iri.PercentEncoding as I import qualified Iri.CodePointPredicates.Rfc3986 as C+import Iri.Data import qualified Iri.MonadPlus as R-import qualified Ptr.Poking as G-import qualified Ptr.ByteString as H-import qualified Text.Builder as J+import qualified Iri.PercentEncoding as I+import Iri.Prelude hiding (foldl, hash) import qualified Net.IPv4 as M import qualified Net.IPv6 as N-+import qualified TextBuilder as J+import qualified VectorBuilder.MonadPlus as E {-# INLINE percent #-} percent :: Parser Word8@@ -84,34 +79,32 @@ labeled label parser = parser <?> label -{-|-Parser of a well-formed URI conforming to the RFC3986 standard into IRI.-Performs URL- and Punycode-decoding.--}-{-# INLINABLE uri #-}+-- |+-- Parser of a well-formed URI conforming to the RFC3986 or RFC3987 standards.+-- Performs URL- and Punycode-decoding.+{-# INLINEABLE uri #-} uri :: Parser Iri uri = labeled "URI" $ do parsedScheme <- scheme- colon+ _ <- colon parsedHierarchy <- hierarchy parsedQuery <- query parsedFragment <- fragment return (Iri parsedScheme parsedHierarchy parsedQuery parsedFragment) -{-|-Same as 'uri', but optimized specifially for the case of HTTP URIs.--}-{-# INLINABLE httpUri #-}+-- |+-- Same as 'uri', but optimized specifially for the case of HTTP URIs.+{-# INLINEABLE httpUri #-} httpUri :: Parser HttpIri httpUri = labeled "HTTP URI" $ do- satisfy (\ x -> x == 104 || x == 72)- satisfy (\ x -> x == 116 || x == 84)- satisfy (\ x -> x == 116 || x == 84)- satisfy (\ x -> x == 112 || x == 80)- secure <- satisfy (\ b -> b == 115 || b == 83) $> True <|> pure False- string "://"+ _ <- satisfy (\x -> x == 104 || x == 72)+ _ <- satisfy (\x -> x == 116 || x == 84)+ _ <- satisfy (\x -> x == 116 || x == 84)+ _ <- satisfy (\x -> x == 112 || x == 80)+ secure <- satisfy (\b -> b == 115 || b == 83) $> True <|> pure False+ _ <- string "://" parsedHost <- host parsedPort <- PresentPort <$> (colon *> port) <|> pure MissingPort parsedPath <- (forwardSlash *> path) <|> pure (Path mempty)@@ -145,64 +138,67 @@ {-# INLINE scheme #-} scheme :: Parser Scheme scheme =- labeled "Scheme" $- fmap Scheme (takeWhile1 (C.scheme . fromIntegral))+ labeled "Scheme"+ $ fmap Scheme (takeWhile1 (C.scheme . fromIntegral)) -{-# INLINABLE presentUserInfo #-}+{-# INLINEABLE presentUserInfo #-} presentUserInfo :: (User -> Password -> a) -> Parser a presentUserInfo result =- labeled "User info" $- do- user <- User <$> urlEncodedString (C.unencodedUserInfoComponent . fromIntegral)- passwordFollows <- True <$ colon <|> pure False- if passwordFollows- then do- password <- PresentPassword <$> urlEncodedString (C.unencodedUserInfoComponent . fromIntegral)- return (result user password)- else return (result user MissingPassword)+ labeled "User info"+ $ do+ user <- User <$> urlEncodedString (C.unencodedUserInfoComponent . fromIntegral)+ passwordFollows <- True <$ colon <|> pure False+ if passwordFollows+ then do+ password <- PresentPassword <$> urlEncodedString (C.unencodedUserInfoComponent . fromIntegral)+ return (result user password)+ else return (result user MissingPassword) {-# INLINE host #-} host :: Parser Host host =- labeled "Host" $- IpV6Host <$> ipV6 <|>- IpV4Host <$> M.parserUtf8 <|>- NamedHost <$> domainName+ labeled "Host"+ $ asum+ [ IpV6Host <$> ipV6,+ IpV4Host <$> M.parserUtf8,+ NamedHost <$> regName+ ] -{-# INLINABLE ipV6 #-}+{-# INLINEABLE ipV6 #-} ipV6 :: Parser IPv6 ipV6 = do a <- F.hexadecimal- colon+ _ <- colon b <- F.hexadecimal- colon+ _ <- colon c <- F.hexadecimal- colon+ _ <- colon d <- F.hexadecimal- colon+ _ <- colon mplus- (do- e <- F.hexadecimal- colon- f <- F.hexadecimal- colon- g <- F.hexadecimal- colon- h <- F.hexadecimal- return (N.fromWord16s a b c d e f g h))- (do- colon- return (N.fromWord16s a b c d 0 0 0 0))+ ( do+ e <- F.hexadecimal+ _ <- colon+ f <- F.hexadecimal+ _ <- colon+ g <- F.hexadecimal+ _ <- colon+ h <- F.hexadecimal+ return (N.fromWord16s a b c d e f g h)+ )+ ( do+ _ <- colon+ return (N.fromWord16s a b c d 0 0 0 0)+ ) -{-# INLINE domainName #-}-domainName :: Parser RegName-domainName =+{-# INLINE regName #-}+regName :: Parser RegName+regName = fmap RegName (E.sepBy1 domainLabel (word8 46)) -{-|-Domain label with Punycode decoding applied.--}+-- |+-- Domain label with Punycode decoding applied if need be. {-# INLINE domainLabel #-} domainLabel :: Parser DomainLabel domainLabel =@@ -230,19 +226,21 @@ else return (Path segments) where segmentsAreEmpty segments =- S.length segments == 1 &&- (case S.unsafeHead segments of PathSegment headSegmentText -> T.null headSegmentText)+ S.length segments+ == 1+ && (case S.unsafeHead segments of PathSegment headSegment -> K.null headSegment) {-# INLINE pathSegment #-} pathSegment :: Parser PathSegment pathSegment = fmap PathSegment (urlEncodedString (C.unencodedPathSegment . fromIntegral)) -{-# INLINABLE utf8Chunks #-}+{-# INLINEABLE utf8Chunks #-} utf8Chunks :: Parser ByteString -> Parser Text utf8Chunks chunk =- labeled "UTF8 chunks" $- R.foldlM progress (mempty, mempty, B.streamDecodeUtf8) chunk >>= finish+ labeled "UTF8 chunks"+ $ R.foldlM progress (mempty, mempty, B.streamDecodeUtf8) chunk+ >>= finish where progress (!builder, _, decode) bytes = case unsafeDupablePerformIO (try (evaluate (decode bytes))) of@@ -250,17 +248,20 @@ return (builder <> J.text decodedChunk, undecodedBytes, newDecode) Left (L.DecodeError error _) -> fail (showString "UTF8 decoding: " error)+ Left _ ->+ fail "Unexpected decoding error" finish (builder, undecodedBytes, _) = if K.null undecodedBytes- then return (J.run builder)+ then return (J.toText builder) else fail (showString "UTF8 decoding: Bytes remaining: " (show undecodedBytes)) -{-# INLINABLE urlEncodedString #-}-urlEncodedString :: (Word8 -> Bool) -> Parser Text+{-# INLINEABLE urlEncodedString #-}+urlEncodedString :: (Word8 -> Bool) -> Parser ByteString urlEncodedString unencodedBytesPredicate =- labeled "URL-encoded string" $- utf8Chunks $- takeWhile1 unencodedBytesPredicate <|> encoded+ labeled "URL-encoded string"+ $ R.foldByteString+ $ takeWhile1 unencodedBytesPredicate+ <|> encoded where encoded = K.singleton <$> percentEncodedByte@@ -269,30 +270,33 @@ percentEncodedByte :: Parser Word8 percentEncodedByte = labeled "Percent-encoded byte" $ do- percent + _ <- percent byte1 <- anyWord8 byte2 <- anyWord8 I.matchPercentEncodedBytes (fail "Broken percent encoding") return byte1 byte2 -{-# INLINABLE query #-}+{-# INLINE query #-} query :: Parser Query query =- labeled "Query" $- (question *> (Query <$> queryOrFragmentBody)) <|> pure (Query mempty)+ labeled "Query"+ $ (question *> (Query <$> queryOrFragmentBody))+ <|> pure (Query mempty) -{-# INLINABLE fragment #-}+{-# INLINE fragment #-} fragment :: Parser Fragment fragment =- labeled "Fragment" $- (hash *> (Fragment <$> queryOrFragmentBody)) <|> pure (Fragment mempty)+ labeled "Fragment"+ $ (hash *> (Fragment <$> queryOrFragmentBody))+ <|> pure (Fragment mempty) -{-|-The stuff after the question or the hash mark.--}-{-# INLINABLE queryOrFragmentBody #-}-queryOrFragmentBody :: Parser Text+-- |+-- The stuff after the question or the hash mark.+{-# INLINEABLE queryOrFragmentBody #-}+queryOrFragmentBody :: Parser ByteString queryOrFragmentBody =- utf8Chunks $- takeWhile1 (C.unencodedQuery . fromIntegral) <|>- " " <$ plus <|>- K.singleton <$> percentEncodedByte+ R.foldByteString+ $ takeWhile1 (C.unencodedQuery . fromIntegral)+ <|> " "+ <$ plus+ <|> K.singleton+ <$> percentEncodedByte
library/Iri/Parsing/Attoparsec/Text.hs view
@@ -1,56 +1,65 @@+{-# OPTIONS_GHC -Wno-unused-top-binds #-}+ module Iri.Parsing.Attoparsec.Text-(- iri,- httpIri,-)+ ( iri,+ httpIri,+ hierarchy,+ scheme,+ host,+ regName,+ domainLabel,+ port,+ path,+ pathSegment,+ query,+ fragment,+ ) where -import Iri.Prelude-import Iri.Data import Data.Attoparsec.Text hiding (try) import qualified Data.ByteString as K-import qualified Data.Text as T import qualified Data.Text.Encoding as B import qualified Data.Text.Encoding.Error as L import qualified Data.Vector as S-import qualified VectorBuilder.MonadPlus as E import qualified Iri.CodePointPredicates.Rfc3987 as C+import Iri.Data import qualified Iri.MonadPlus as R-import qualified Text.Builder as J+import Iri.Prelude import qualified Net.IPv4 as M import qualified Net.IPv6 as N-+import qualified Ptr.ByteString as ByteString+import qualified Ptr.Poking as Poking+import qualified TextBuilder as J+import qualified VectorBuilder.MonadPlus as E {-# INLINE labeled #-} labeled :: String -> Parser a -> Parser a labeled label parser = parser <?> label -{-|-Parser of a well-formed IRI conforming to the RFC3987 standard into IRI.-Performs URL-decoding.--}-{-# INLINABLE iri #-}+-- |+-- Parser of a well-formed IRI conforming to the RFC3987 standard into 'Iri'.+-- Performs URL-decoding.+{-# INLINEABLE iri #-} iri :: Parser Iri iri = labeled "IRI" $ do parsedScheme <- scheme- char ':'+ _ <- char ':' parsedHierarchy <- hierarchy parsedQuery <- query parsedFragment <- fragment return (Iri parsedScheme parsedHierarchy parsedQuery parsedFragment) -{-|-Same as 'iri', but optimized specifially for the case of HTTP IRIs.--}-{-# INLINABLE httpIri #-}+-- |+-- Same as 'iri', but optimized specifially for the case of HTTP IRIs.+{-# INLINEABLE httpIri #-} httpIri :: Parser HttpIri httpIri = labeled "HTTP IRI" $ do- asciiCI "http"- secure <- satisfy (\ x -> x == 's' || x == 'S') $> True <|> pure False- string "://"+ _ <- asciiCI "http"+ secure <- satisfy (\x -> x == 's' || x == 'S') $> True <|> pure False+ _ <- string "://" parsedHost <- host parsedPort <- PresentPort <$> (char ':' *> port) <|> pure MissingPort parsedPath <- ((char '/') *> path) <|> pure (Path mempty)@@ -84,43 +93,46 @@ {-# INLINE scheme #-} scheme :: Parser Scheme scheme =- labeled "Scheme" $- fmap (Scheme . B.encodeUtf8) (takeWhile1 (C.scheme . ord))+ labeled "Scheme"+ $ fmap (Scheme . B.encodeUtf8) (takeWhile1 (C.scheme . ord)) -{-# INLINABLE presentUserInfo #-}+{-# INLINEABLE presentUserInfo #-} presentUserInfo :: (User -> Password -> a) -> Parser a presentUserInfo result =- labeled "User info" $- do- user <- User <$> urlEncodedComponent (C.unencodedUserInfoComponent . ord)- passwordFollows <- True <$ char ':' <|> pure False- if passwordFollows- then do- password <- PresentPassword <$> urlEncodedComponent (C.unencodedUserInfoComponent . ord)- return (result user password)- else return (result user MissingPassword)+ labeled "User info"+ $ do+ user <- User <$> urlEncodedComponent (C.unencodedUserInfoComponent . ord)+ passwordFollows <- True <$ char ':' <|> pure False+ if passwordFollows+ then do+ password <- PresentPassword <$> urlEncodedComponent (C.unencodedUserInfoComponent . ord)+ return (result user password)+ else return (result user MissingPassword) {-# INLINE host #-} host :: Parser Host host =- labeled "Host" $- IpV6Host <$> N.parser <|>- IpV4Host <$> M.parser <|>- NamedHost <$> domainName+ labeled "Host"+ $ IpV6Host+ <$> N.parser+ <|> IpV4Host+ <$> M.parser+ <|> NamedHost+ <$> regName -{-# INLINE domainName #-}-domainName :: Parser RegName-domainName =+{-# INLINE regName #-}+regName :: Parser RegName+regName = fmap RegName (E.sepBy1 domainLabel (char '.')) -{-|-Domain label with Punycode decoding applied.--}+-- |+-- Domain label with Punycode decoding applied. {-# INLINE domainLabel #-} domainLabel :: Parser DomainLabel domainLabel =- labeled "Domain label" $- DomainLabel <$> takeWhile1 (C.unencodedRegName . ord)+ labeled "Domain label"+ $ DomainLabel+ <$> takeWhile1 (C.unencodedRegName . ord) {-# INLINE port #-} port :: Parser Word16@@ -137,25 +149,36 @@ else return (Path segments) where segmentsAreEmpty segments =- S.length segments == 1 &&- (case S.unsafeHead segments of PathSegment headSegmentText -> T.null headSegmentText)+ S.length segments+ == 1+ && (case S.unsafeHead segments of PathSegment headSegment -> K.null headSegment) {-# INLINE pathSegment #-} pathSegment :: Parser PathSegment pathSegment = fmap PathSegment (urlEncodedComponent (C.unencodedPathSegment . ord)) -{-# INLINABLE urlEncodedComponent #-}-urlEncodedComponent :: (Char -> Bool) -> Parser Text+{-# INLINEABLE urlEncodedComponent #-}+urlEncodedComponent :: (Char -> Bool) -> Parser ByteString urlEncodedComponent unencodedCharPredicate =- labeled "URL-encoded component" $- fmap J.run $- R.foldl mappend mempty $- (J.text <$> takeWhile1 unencodedCharPredicate) <|> urlEncodedSequence+ labeled "URL-encoded component"+ $ fmap ByteString.poking+ $ R.fold+ $ (Poking.bytes . B.encodeUtf8 <$> takeWhile1 unencodedCharPredicate)+ <|> (Poking.word8 <$> urlEncodedByte) -{-# INLINABLE urlEncodedSequence #-}-urlEncodedSequence :: Parser J.Builder-urlEncodedSequence =+{-# INLINEABLE urlEncodedComponentText #-}+urlEncodedComponentText :: (Char -> Bool) -> Parser Text+urlEncodedComponentText unencodedCharPredicate =+ labeled "URL-encoded component"+ $ fmap J.toText+ $ R.foldl mappend mempty+ $ (J.text <$> takeWhile1 unencodedCharPredicate)+ <|> urlEncodedSequenceTextBuilder++{-# INLINEABLE urlEncodedSequenceTextBuilder #-}+urlEncodedSequenceTextBuilder :: Parser J.TextBuilder+urlEncodedSequenceTextBuilder = labeled "URL-encoded sequence" $ do start <- progress (mempty, mempty, B.streamDecodeUtf8) =<< urlEncodedByte R.foldlM progress (start) urlEncodedByte >>= finish@@ -166,6 +189,8 @@ return (builder <> J.text decodedChunk, undecodedBytes, newDecode) Left (L.DecodeError error _) -> fail (showString "UTF8 decoding: " error)+ Left _ ->+ fail "Unexpected decoding error" finish (builder, undecodedBytes, _) = if K.null undecodedBytes then return builder@@ -175,7 +200,7 @@ urlEncodedByte :: Parser Word8 urlEncodedByte = do- char '%' + _ <- char '%' digit1 <- fromIntegral <$> hexadecimalDigit digit2 <- fromIntegral <$> hexadecimalDigit return (shiftL digit1 4 .|. digit2)@@ -188,29 +213,31 @@ let x = ord c if x >= 48 && x < 58 then return (x - 48)- else if x >= 65 && x < 71- then return (x - 55)- else if x >= 97 && x < 103- then return (x - 97)- else fail ("Not a hexadecimal digit: " <> show c)+ else+ if x >= 65 && x < 71+ then return (x - 55)+ else+ if x >= 97 && x < 103+ then return (x - 87)+ else fail ("Not a hexadecimal digit: " <> show c) -{-# INLINABLE query #-}+{-# INLINEABLE query #-} query :: Parser Query query =- labeled "Query" $- (char '?' *> queryBody) <|> pure (Query mempty)+ labeled "Query"+ $ (char '?' *> queryBody)+ <|> pure (Query mempty) -{-|-The stuff after the question mark.--}-{-# INLINABLE queryBody #-}+-- |+-- The stuff after the question mark.+{-# INLINEABLE queryBody #-} queryBody :: Parser Query queryBody = fmap Query (urlEncodedComponent (C.unencodedQuery . ord)) -{-# INLINABLE fragment #-}+{-# INLINEABLE fragment #-} fragment :: Parser Fragment fragment =- labeled "Fragment" $- (char '#' *> (Fragment <$> urlEncodedComponent (C.unencodedFragment . ord))) <|>- pure (Fragment mempty)+ labeled "Fragment"+ $ (char '#' *> (Fragment <$> urlEncodedComponent (C.unencodedFragment . ord)))+ <|> pure (Fragment mempty)
library/Iri/Parsing/ByteString.hs view
@@ -1,29 +1,55 @@+{-# OPTIONS_GHC -Wno-unused-top-binds #-}+ module Iri.Parsing.ByteString-(- uri,- httpUri,-)+ ( uri,+ httpUri,+ regName,+ uriQuery,+ ) where -import Iri.Prelude+import qualified Data.Attoparsec.ByteString as B+import qualified Data.ByteString as ByteString import Iri.Data import qualified Iri.Parsing.Attoparsec.ByteString as A-import qualified Data.Attoparsec.ByteString as B+import Iri.Prelude +parser :: B.Parser a -> ByteString -> Either Text a+parser parser =+ either (Left . fromString) Right+ . B.parseOnly (parser <* B.endOfInput) -{-|-Parser of a well-formed URI conforming to the RFC3986 standard into IRI.-Performs URL- and Punycode-decoding.--}+-- |+-- Parser of a well-formed URI conforming to the RFC3986 standard into IRI.+-- Performs URL- and Punycode-decoding. uri :: ByteString -> Either Text Iri uri =- either (Left . fromString) Right .- B.parseOnly (A.uri <* B.endOfInput)+ parser A.uri -{-|-Same as 'uri', but optimized specifially for the case of HTTP URIs.--}+-- |+-- Same as 'uri', but optimized specifially for the case of HTTP URIs. httpUri :: ByteString -> Either Text HttpIri httpUri =- either (Left . fromString) Right .- B.parseOnly (A.httpUri <* B.endOfInput)+ parser A.httpUri++-- |+-- Domain name parser.+regName :: ByteString -> Either Text RegName+regName =+ parser A.regName++-- |+-- Assuming we have a valid URI as input, extract the query part from it.+uriQuery :: ByteString -> Either Text Query+uriQuery x =+ case ByteString.break (== 63) x of+ (_, questionAndAfterQuestion) -> case ByteString.uncons questionAndAfterQuestion of+ Just (_, afterQuestion) -> case ByteString.break (== 35) afterQuestion of+ (beforeHash, _) -> Right (Query beforeHash)+ Nothing ->+ Left "Does not start with a question mark"++-- |+-- Assuming we have a valid URI as input, extract the fragment part from it.+uriFragment :: ByteString -> Fragment+uriFragment = ByteString.break (== 35) >>> snd >>> ByteString.drop 1 >>> Fragment
library/Iri/Parsing/Text.hs view
@@ -1,29 +1,66 @@ module Iri.Parsing.Text-(- iri,- httpIri,-)+ ( iri,+ httpIri,+ hierarchy,+ scheme,+ host,+ regName,+ domainLabel,+ port,+ path,+ pathSegment,+ query,+ fragment,+ ) where -import Iri.Prelude+import qualified Data.Attoparsec.Text as B import Iri.Data import qualified Iri.Parsing.Attoparsec.Text as A-import qualified Data.Attoparsec.Text as B+import Iri.Prelude +fromParser :: B.Parser a -> Text -> Either Text a+fromParser parser =+ either (Left . fromString) Right+ . B.parseOnly (parser <* B.endOfInput) -{-|-Parser of a well-formed IRI conforming to the RFC3987 standard into IRI.-Performs URL-decoding.--}+-- |+-- Parser of a well-formed IRI conforming to the RFC3987 standard into 'Iri'.+-- Performs URL-decoding. iri :: Text -> Either Text Iri-iri =- either (Left . fromString) Right .- B.parseOnly (A.iri <* B.endOfInput)+iri = fromParser A.iri -{-|-Same as 'iri', but optimized specifially for the case of HTTP IRIs.--}+-- |+-- Same as 'iri', but optimized specifially for the case of HTTP IRIs. httpIri :: Text -> Either Text HttpIri-httpIri =- either (Left . fromString) Right .- B.parseOnly (A.httpIri <* B.endOfInput)+httpIri = fromParser A.httpIri++hierarchy :: Text -> Either Text Hierarchy+hierarchy = fromParser A.hierarchy++scheme :: Text -> Either Text Scheme+scheme = fromParser A.scheme++host :: Text -> Either Text Host+host = fromParser A.host++regName :: Text -> Either Text RegName+regName = fromParser A.regName++domainLabel :: Text -> Either Text DomainLabel+domainLabel = fromParser A.domainLabel++port :: Text -> Either Text Word16+port = fromParser A.port++path :: Text -> Either Text Path+path = fromParser A.path++pathSegment :: Text -> Either Text PathSegment+pathSegment = fromParser A.pathSegment++query :: Text -> Either Text Query+query = fromParser A.query++fragment :: Text -> Either Text Fragment+fragment = fromParser A.fragment
library/Iri/PercentEncoding.hs view
@@ -2,17 +2,18 @@ import Iri.Prelude - {-# INLINE matchHexByte #-} matchHexByte :: a -> (Word8 -> a) -> Word8 -> a matchHexByte failure success x = if x >= 48 && x <= 57 then success (x - 48)- else if x >= 65 && x <= 70- then success (x - 55)- else if x >= 97 && x <= 102- then success (x - 87)- else failure+ else+ if x >= 65 && x <= 70+ then success (x - 55)+ else+ if x >= 97 && x <= 102+ then success (x - 87)+ else failure {-# INLINE matchPercentEncodedBytes #-} matchPercentEncodedBytes :: a -> (Word8 -> a) -> Word8 -> Word8 -> a
library/Iri/Prelude.hs view
@@ -1,64 +1,82 @@+{-# OPTIONS_GHC -Wno-dodgy-imports #-}+ module Iri.Prelude-(- module Exports,-)+ ( module Exports,+ ) where ---- base-prelude---------------------------import BasePrelude as Exports hiding (assert, left, right, isLeft, isRight, (<>), First(..), Last(..), ProtocolError, traceEvent, traceEventIO, traceMarker, traceMarkerIO)---- base---------------------------import Foreign as Exports hiding (void)---- contravariant--------------------------+import Control.Applicative as Exports+import Control.Arrow as Exports+import Control.Category as Exports+import Control.Concurrent as Exports+import Control.Exception as Exports+import Control.Monad as Exports hiding (forM, forM_, mapM, mapM_, msum, sequence, sequence_)+import Control.Monad.Fix as Exports hiding (fix)+import Control.Monad.IO.Class as Exports+import Control.Monad.ST as Exports+import Data.Bits as Exports+import Data.Bool as Exports+import Data.ByteString as Exports (ByteString)+import Data.Char as Exports+import Data.Coerce as Exports+import Data.Complex as Exports+import Data.Data as Exports+import Data.Dynamic as Exports+import Data.Either as Exports+import Data.Fixed as Exports+import Data.Foldable as Exports+import Data.Function as Exports hiding (id, (.))+import Data.Functor as Exports hiding (unzip) import Data.Functor.Contravariant as Exports import Data.Functor.Contravariant.Divisible as Exports---- profunctors---------------------------import Data.Profunctor.Unsafe as Exports+import Data.Functor.Identity as Exports+import Data.HashMap.Strict as Exports (HashMap)+import Data.Hashable as Exports+import Data.IORef as Exports+import Data.Int as Exports+import Data.Ix as Exports+import Data.List as Exports hiding (all, and, any, concat, concatMap, elem, find, foldl, foldl', foldl1, foldr, foldr1, isSubsequenceOf, mapAccumL, mapAccumR, maximum, maximumBy, minimum, minimumBy, notElem, or, product, sortOn, sum, uncons)+import Data.Maybe as Exports+import Data.Monoid as Exports hiding (First (..), Last (..))+import Data.Ord as Exports import Data.Profunctor.Choice as Exports import Data.Profunctor.Strong as Exports---- semigroups--------------------------+import Data.Profunctor.Unsafe as Exports+import Data.Proxy as Exports+import Data.Ratio as Exports+import Data.STRef as Exports import Data.Semigroup as Exports---- bytestring---------------------------import Data.ByteString as Exports (ByteString)---- text--------------------------+import Data.String as Exports import Data.Text as Exports (Text)---- ip--------------------------+import Data.Traversable as Exports+import Data.Tuple as Exports+import Data.Unique as Exports+import Data.Vector as Exports (Vector)+import Data.Vector.Instances ()+import Data.Version as Exports+import Data.Word as Exports+import Debug.Trace as Exports+import Foreign.ForeignPtr as Exports+import Foreign.Ptr as Exports+import Foreign.StablePtr as Exports+import Foreign.Storable as Exports hiding (alignment, sizeOf)+import GHC.Conc as Exports hiding (threadWaitRead, threadWaitReadSTM, threadWaitWrite, threadWaitWriteSTM, withMVar)+import GHC.Exts as Exports (groupWith, inline, lazy, sortWith)+import GHC.Generics as Exports (Generic)+import GHC.IO.Exception as Exports+import Instances.TH.Lift () import Net.IPv4 as Exports (IPv4) import Net.IPv6 as Exports (IPv6)---- vector---------------------------import Data.Vector as Exports (Vector)---- unordered-containers---------------------------import Data.HashMap.Strict as Exports (HashMap)---- th-lift-instances---------------------------import Instances.TH.Lift as Exports---- bug---------------------------import Bug as Exports------------------------------------------------------------------------------------import qualified Language.Haskell.TH.Lift as A-A.deriveLift ''IPv4-A.deriveLift ''IPv6+import Numeric as Exports+import System.Environment as Exports+import System.Exit as Exports+import System.IO as Exports+import System.IO.Error as Exports+import System.IO.Unsafe as Exports+import System.Mem as Exports+import System.Mem.StableName as Exports+import System.Timeout as Exports+import Text.Printf as Exports (hPrintf, printf)+import Text.Read as Exports (Read (..), readEither, readMaybe)+import Unsafe.Coerce as Exports+import Prelude as Exports hiding (all, and, any, concat, concatMap, elem, foldl, foldl1, foldr, foldr1, id, mapM, mapM_, maximum, minimum, notElem, or, product, sequence, sequence_, sum, (.))
library/Iri/QuasiQuoter.hs view
@@ -1,19 +1,18 @@ module Iri.QuasiQuoter-(- uri,- httpUri,-)+ ( uri,+ httpUri,+ iri,+ httpIri,+ ) where -import Iri.Prelude hiding (exp)-import Iri.Data-import Language.Haskell.TH.Syntax-import Language.Haskell.TH.Quote-import qualified Iri.Parsing.Attoparsec.ByteString as A import qualified Data.Attoparsec.ByteString as B import qualified Data.Attoparsec.Text as C+import qualified Iri.Parsing.Attoparsec.ByteString as A import qualified Iri.Parsing.Attoparsec.Text as D-+import Iri.Prelude hiding (exp)+import Language.Haskell.TH.Quote+import Language.Haskell.TH.Syntax exp :: (String -> Q Exp) -> QuasiQuoter exp exp =@@ -22,42 +21,38 @@ unsupported _ = fail "Not supported" -{-|-IRI literal parsed from textual URI.--}+-- |+-- 'Iri' literal from ASCII representation. uri :: QuasiQuoter uri =- exp $ \ string ->- case B.parseOnly (A.uri <* B.endOfInput) (fromString string) of- Right iri -> lift iri- Left error -> fail (showString "URI parsing: " error)+ exp $ \string ->+ case B.parseOnly (A.uri <* B.endOfInput) (fromString string) of+ Right uri -> lift uri+ Left error -> fail (showString "URI parsing: " error) -{-|-HTTP IRI literal parsed from textual URI.--}+-- |+-- 'HttpIri' literal from ASCII representation. httpUri :: QuasiQuoter httpUri =- exp $ \ string ->- case B.parseOnly (A.httpUri <* B.endOfInput) (fromString string) of- Right iri -> lift iri- Left error -> fail (showString "HTTP URI parsing: " error)+ exp $ \string ->+ case B.parseOnly (A.httpUri <* B.endOfInput) (fromString string) of+ Right uri -> lift uri+ Left error -> fail (showString "HTTP URI parsing: " error) -{-|-IRI literal parsed from textual IRI.--}+-- |+-- 'Iri' literal from IRI representation. iri :: QuasiQuoter iri =- exp $ \ string ->- case C.parseOnly (D.iri <* C.endOfInput) (fromString string) of- Right iri -> lift iri- Left error -> fail (showString "IRI parsing: " error)+ exp $ \string ->+ case C.parseOnly (D.iri <* C.endOfInput) (fromString string) of+ Right iri -> lift iri+ Left error -> fail (showString "IRI parsing: " error) -{-|-HTTP IRI literal parsed from textual IRI.--}+-- |+-- 'HttpIri' literal from IRI representation. httpIri :: QuasiQuoter httpIri =- exp $ \ string ->- case C.parseOnly (D.httpIri <* C.endOfInput) (fromString string) of- Right iri -> lift iri- Left error -> fail (showString "HTTP IRI parsing: " error)+ exp $ \string ->+ case C.parseOnly (D.httpIri <* C.endOfInput) (fromString string) of+ Right iri -> lift iri+ Left error -> fail (showString "HTTP IRI parsing: " error)
library/Iri/Rendering/ByteString.hs view
@@ -1,18 +1,16 @@-module Iri.Rendering.ByteString-where+module Iri.Rendering.ByteString where -import Iri.Prelude import Iri.Data+import Iri.Prelude import qualified Iri.Rendering.Ptr.Poking as A import qualified Ptr.ByteString as F --{-| Render as URI ASCII bytes -}+-- | Render as URI ASCII bytes uri :: Iri -> ByteString uri = F.poking . A.uri -{-| Render as URI ASCII bytes -}+-- | Render as URI ASCII bytes httpUri :: HttpIri -> ByteString httpUri = F.poking . A.httpUri
− library/Iri/Rendering/Poke.hs
@@ -1,14 +0,0 @@-module Iri.Rendering.Poke-where--import Iri.Prelude-import Iri.Data-import Ptr.Poke---{-# NOINLINE urlEncodedByte #-}-urlEncodedByte :: Poke Word8-urlEncodedByte =- divide (\byte -> ('%', byte))- asciiChar- (divide (flip divMod 16) asciiHexDigit asciiHexDigit)
+ library/Iri/Rendering/Ptr/Poke.hs view
@@ -0,0 +1,12 @@+module Iri.Rendering.Ptr.Poke where++import Iri.Prelude+import Ptr.Poke++{-# NOINLINE urlEncodedByte #-}+urlEncodedByte :: Poke Word8+urlEncodedByte =+ divide+ (\byte -> ('%', byte))+ asciiChar+ (divide (flip divMod 16) asciiHexDigit asciiHexDigit)
library/Iri/Rendering/Ptr/Poking.hs view
@@ -1,52 +1,52 @@+{-# OPTIONS_GHC -Wno-unused-top-binds #-}+ module Iri.Rendering.Ptr.Poking-(- uri,- httpUri,- scheme,- host,- path,- query,-)+ ( uri,+ httpUri,+ scheme,+ host,+ path,+ query,+ ) where -import Iri.Prelude hiding (null, poke)-import Iri.Data-import Ptr.Poking+import qualified Data.ByteString as ByteString+import qualified Data.Text as C import qualified Data.Text.Encoding as A-import qualified Data.Text.Encoding.Error as A import qualified Data.Text.Punycode as B-import qualified Data.Text as C-import qualified Data.HashMap.Strict as G-import qualified Data.Vector as H-import qualified Net.IPv4 as D-import qualified Net.IPv6 as E-import qualified Iri.Vector as F import qualified Iri.CodePointPredicates.Core as I import qualified Iri.CodePointPredicates.Rfc3986 as I+import Iri.Data+import Iri.Prelude hiding (null, poke)+import qualified Iri.Rendering.Ptr.Poke as L import qualified Iri.Utf8CodePoint as K-import qualified Iri.Rendering.Poke as L-+import qualified Iri.Vector as F+import qualified Net.IPv4 as D+import qualified Net.IPv6 as E+import Ptr.Poking uri :: Iri -> Poking uri (Iri schemeValue hierarchyValue queryValue fragmentValue) =- scheme schemeValue <> - asciiChar ':' <>- hierarchy hierarchyValue <>- (prependIfNotNull- (asciiChar '?')- (query queryValue)) <>- (prependIfNotNull- (asciiChar '#')- (fragment fragmentValue))+ scheme schemeValue+ <> asciiChar ':'+ <> hierarchy hierarchyValue+ <> ( prependIfNotNull+ (asciiChar '?')+ (query queryValue)+ )+ <> ( prependIfNotNull+ (asciiChar '#')+ (fragment fragmentValue)+ ) httpUri :: HttpIri -> Poking httpUri (HttpIri (Security secure) hostValue portValue pathValue queryValue fragmentValue) =- (if secure then bytes "https://" else bytes "http://") <>- host hostValue <>- prependIfNotNull (asciiChar ':') (port portValue) <>- prependIfNotNull (asciiChar '/') (path pathValue) <>- prependIfNotNull (asciiChar '?') (query queryValue) <>- prependIfNotNull (asciiChar '#') (fragment fragmentValue)+ (if secure then "https://" else "http://")+ <> host hostValue+ <> prependIfNotNull (asciiChar ':') (port portValue)+ <> prependIfNotNull (asciiChar '/') (path pathValue)+ <> prependIfNotNull (asciiChar '?') (query queryValue)+ <> prependIfNotNull (asciiChar '#') (fragment fragmentValue) scheme :: Scheme -> Poking scheme (Scheme value) =@@ -54,7 +54,7 @@ hierarchy :: Hierarchy -> Poking hierarchy =- \ case+ \case AuthorisedHierarchy authorityValue pathValue -> bytes "//" <> authority authorityValue <> prependIfNotNull (asciiChar '/') (path pathValue) AbsoluteHierarchy pathValue ->@@ -64,25 +64,25 @@ authority :: Authority -> Poking authority (Authority userInfoValue hostValue portValue) =- appendIfNotNull (asciiChar '@') (userInfo userInfoValue) <>- host hostValue <>- prependIfNotNull (asciiChar ':') (port portValue)+ appendIfNotNull (asciiChar '@') (userInfo userInfoValue)+ <> host hostValue+ <> prependIfNotNull (asciiChar ':') (port portValue) userInfo :: UserInfo -> Poking userInfo =- \ case+ \case PresentUserInfo (User user) password -> case password of PresentPassword password -> userInfoComponent user <> asciiChar ':' <> userInfoComponent password MissingPassword -> userInfoComponent user MissingUserInfo -> mempty -userInfoComponent :: Text -> Poking+userInfoComponent :: ByteString -> Poking userInfoComponent =- urlEncodedText I.unencodedUserInfoComponent+ urlEncodedBytes I.unencodedUserInfoComponent host :: Host -> Poking host =- \ case+ \case NamedHost value -> domainName value IpV4Host value -> ipV4 value IpV6Host value -> ipV6 value@@ -107,7 +107,7 @@ port :: Port -> Poking port =- \ case+ \case PresentPort value -> asciiIntegral value MissingPort -> mempty @@ -117,28 +117,38 @@ pathSegment :: PathSegment -> Poking pathSegment (PathSegment value) =- urlEncodedText I.unencodedPathSegment value+ urlEncodedBytes I.unencodedPathSegment value query :: Query -> Poking query (Query value) =- urlEncodedText I.unencodedQuery value+ urlEncodedBytes I.unencodedQuery value fragment :: Fragment -> Poking fragment (Fragment value) =- urlEncodedText I.unencodedFragment value+ urlEncodedBytes I.unencodedFragment value -{-| Apply URL-encoding to text -}+urlEncodedBytes :: I.Predicate -> ByteString -> Poking+urlEncodedBytes unencodedPredicate =+ ByteString.foldl' (\poking -> mappend poking . byte) mempty+ where+ byte x =+ if unencodedPredicate (fromIntegral x)+ then word8 x+ else urlEncodedByte x++-- | Apply URL-encoding to text urlEncodedText :: I.Predicate -> Text -> Poking urlEncodedText unencodedPredicate =- C.foldl' (\ poking -> mappend poking . urlEncodedUnicodeCodePoint unencodedPredicate . ord) mempty+ C.foldl' (\poking -> mappend poking . urlEncodedUnicodeCodePoint unencodedPredicate . ord) mempty urlEncodedUnicodeCodePoint :: I.Predicate -> Int -> Poking urlEncodedUnicodeCodePoint unencodedPredicate codePoint =- K.unicodeCodePoint codePoint- (\ b1 -> if unencodedPredicate codePoint then word8 b1 else urlEncodedByte b1)- (\ b1 b2 -> urlEncodedByte b1 <> urlEncodedByte b2)- (\ b1 b2 b3 -> urlEncodedByte b1 <> urlEncodedByte b2 <> urlEncodedByte b3)- (\ b1 b2 b3 b4 -> urlEncodedByte b1 <> urlEncodedByte b2 <> urlEncodedByte b3 <> urlEncodedByte b4)+ K.unicodeCodePoint+ codePoint+ (\b1 -> if unencodedPredicate codePoint then word8 b1 else urlEncodedByte b1)+ (\b1 b2 -> urlEncodedByte b1 <> urlEncodedByte b2)+ (\b1 b2 b3 -> urlEncodedByte b1 <> urlEncodedByte b2 <> urlEncodedByte b3)+ (\b1 b2 b3 b4 -> urlEncodedByte b1 <> urlEncodedByte b2 <> urlEncodedByte b3 <> urlEncodedByte b4) urlEncodedByte :: Word8 -> Poking urlEncodedByte =
library/Iri/Rendering/Text.hs view
@@ -1,8 +1,6 @@ module Iri.Rendering.Text-(- module Iri.Rendering.Text.Internal,-)+ ( module Iri.Rendering.Text.Internal,+ ) where import Iri.Rendering.Text.Internal-import Iri.Data
library/Iri/Rendering/Text/Internal.hs view
@@ -1,18 +1,56 @@-module Iri.Rendering.Text.Internal-where+module Iri.Rendering.Text.Internal where -import Iri.Prelude import Iri.Data.Types+import Iri.Prelude import qualified Iri.Rendering.TextBuilder.Internal as A-import qualified Text.Builder as B-+import qualified TextBuilder as B -{-| Render as a Unicode IRI text -}+-- | Render as a Unicode IRI text iri :: Iri -> Text-iri =- B.run . A.iri+iri = B.toText . A.iri -{-| Render as a Unicode IRI text -}+-- | Render as a Unicode IRI text httpIri :: HttpIri -> Text-httpIri =- B.run . A.httpIri+httpIri = B.toText . A.httpIri++scheme :: Scheme -> Text+scheme = B.toText . A.scheme++hierarchy :: Hierarchy -> Text+hierarchy = B.toText . A.hierarchy++authority :: Authority -> Text+authority = B.toText . A.authority++userInfo :: UserInfo -> Text+userInfo = B.toText . A.userInfo++host :: Host -> Text+host = B.toText . A.host++regName :: RegName -> Text+regName = B.toText . A.regName++domainLabel :: DomainLabel -> Text+domainLabel = B.toText . A.domainLabel++ipV4 :: IPv4 -> Text+ipV4 = B.toText . A.ipV4++ipV6 :: IPv6 -> Text+ipV6 = B.toText . A.ipV6++port :: Port -> Text+port = B.toText . A.port++path :: Path -> Text+path = B.toText . A.path++pathSegment :: PathSegment -> Text+pathSegment = B.toText . A.pathSegment++query :: Query -> Text+query = B.toText . A.query++fragment :: Fragment -> Text+fragment = B.toText . A.fragment
library/Iri/Rendering/TextBuilder.hs view
@@ -1,8 +1,6 @@ module Iri.Rendering.TextBuilder-(- module Iri.Rendering.TextBuilder.Internal,-)+ ( module Iri.Rendering.TextBuilder.Internal,+ ) where import Iri.Rendering.TextBuilder.Internal-import Iri.Data.Types
library/Iri/Rendering/TextBuilder/Internal.hs view
@@ -1,55 +1,68 @@ module Iri.Rendering.TextBuilder.Internal-(- iri,- httpIri,-)+ ( iri,+ httpIri,+ scheme,+ hierarchy,+ authority,+ userInfo,+ host,+ regName,+ domainLabel,+ ipV4,+ ipV6,+ port,+ path,+ pathSegment,+ query,+ fragment,+ ) where -import Iri.Prelude hiding (null)-import Iri.Data.Types-import Text.Builder+import qualified Data.ByteString as ByteString+import qualified Data.Text as C import qualified Data.Text.Encoding as A import qualified Data.Text.Encoding.Error as A-import qualified Data.Text.Punycode as B-import qualified Data.Text as C-import qualified Data.HashMap.Strict as G-import qualified Data.Vector as H+import qualified Iri.CodePointPredicates.Core as CorePredicates+import qualified Iri.CodePointPredicates.Rfc3986 as Rfc3986Predicates+import qualified Iri.CodePointPredicates.Rfc3987 as Rfc3987Predicates+import Iri.Data.Types+import Iri.Prelude hiding (null)+import qualified Iri.Utf8CodePoint as K+import qualified Iri.Vector as F import qualified Net.IPv4 as D import qualified Net.IPv6 as E-import qualified Iri.Vector as F-import qualified Iri.CodePointPredicates.Core as I-import qualified Iri.CodePointPredicates.Rfc3987 as I-import qualified Iri.Utf8CodePoint as K-+import TextBuilder -iri :: Iri -> Builder+iri :: Iri -> TextBuilder iri (Iri schemeValue hierarchyValue queryValue fragmentValue) =- scheme schemeValue <> - char ':' <>- hierarchy hierarchyValue <>- (prependIfNotNull- (char '?')- (query queryValue)) <>- (prependIfNotNull- (char '#')- (fragment fragmentValue))+ scheme schemeValue+ <> char ':'+ <> hierarchy hierarchyValue+ <> ( prependIfNotNull+ (char '?')+ (query queryValue)+ )+ <> ( prependIfNotNull+ (char '#')+ (fragment fragmentValue)+ ) -httpIri :: HttpIri -> Builder+httpIri :: HttpIri -> TextBuilder httpIri (HttpIri (Security secure) hostValue portValue pathValue queryValue fragmentValue) =- (if secure then string "https://" else string "http://") <>- host hostValue <>- prependIfNotNull (char ':') (port portValue) <>- prependIfNotNull (char '/') (path pathValue) <>- prependIfNotNull (char '?') (query queryValue) <>- prependIfNotNull (char '#') (fragment fragmentValue)+ (if secure then string "https://" else string "http://")+ <> host hostValue+ <> prependIfNotNull (char ':') (port portValue)+ <> prependIfNotNull (char '/') (path pathValue)+ <> prependIfNotNull (char '?') (query queryValue)+ <> prependIfNotNull (char '#') (fragment fragmentValue) -scheme :: Scheme -> Builder+scheme :: Scheme -> TextBuilder scheme (Scheme bytes) = text (A.decodeUtf8With A.lenientDecode bytes) -hierarchy :: Hierarchy -> Builder+hierarchy :: Hierarchy -> TextBuilder hierarchy =- \ case+ \case AuthorisedHierarchy authorityValue pathValue -> string "//" <> authority authorityValue <> prependIfNotNull (char '/') (path pathValue) AbsoluteHierarchy pathValue ->@@ -57,99 +70,115 @@ RelativeHierarchy pathValue -> path pathValue -authority :: Authority -> Builder+authority :: Authority -> TextBuilder authority (Authority userInfoValue hostValue portValue) =- appendIfNotNull (char '@') (userInfo userInfoValue) <>- host hostValue <>- prependIfNotNull (char ':') (port portValue)+ appendIfNotNull (char '@') (userInfo userInfoValue)+ <> host hostValue+ <> prependIfNotNull (char ':') (port portValue) -userInfo :: UserInfo -> Builder+userInfo :: UserInfo -> TextBuilder userInfo =- \ case+ \case PresentUserInfo (User user) password -> case password of- PresentPassword password -> userInfoComponent user <> char ':' <> userInfoComponent password+ PresentPassword password -> userInfoComponent user <> char ':' <> userInfoComponent password MissingPassword -> userInfoComponent user MissingUserInfo -> mempty -userInfoComponent :: Text -> Builder+userInfoComponent :: ByteString -> TextBuilder userInfoComponent =- urlEncodedText I.unencodedUserInfoComponent+ urlEncodedBytesOrText Rfc3987Predicates.unencodedUserInfoComponent Rfc3986Predicates.unencodedUserInfoComponent -host :: Host -> Builder+host :: Host -> TextBuilder host =- \ case- NamedHost value -> domainName value+ \case+ NamedHost value -> regName value IpV4Host value -> ipV4 value IpV6Host value -> ipV6 value -domainName :: RegName -> Builder-domainName (RegName vector) =+regName :: RegName -> TextBuilder+regName (RegName vector) = F.intercalate domainLabel (char '.') vector -domainLabel :: DomainLabel -> Builder-domainLabel (DomainLabel value) =- text value+domainLabel :: DomainLabel -> TextBuilder+domainLabel (DomainLabel x) =+ text x -ipV4 :: IPv4 -> Builder+ipV4 :: IPv4 -> TextBuilder ipV4 = text . D.encode -ipV6 :: IPv6 -> Builder+ipV6 :: IPv6 -> TextBuilder ipV6 = text . E.encode -port :: Port -> Builder+port :: Port -> TextBuilder port =- \ case- PresentPort value -> unsignedDecimal value+ \case+ PresentPort value -> decimal value MissingPort -> mempty -path :: Path -> Builder+path :: Path -> TextBuilder path (Path pathSegmentVector) = F.intercalate pathSegment (char '/') pathSegmentVector -pathSegment :: PathSegment -> Builder+pathSegment :: PathSegment -> TextBuilder pathSegment (PathSegment value) =- urlEncodedText I.unencodedPathSegment value+ urlEncodedBytesOrText Rfc3987Predicates.unencodedPathSegment Rfc3986Predicates.unencodedPathSegment value -query :: Query -> Builder+query :: Query -> TextBuilder query (Query value) =- urlEncodedText I.unencodedQuery value+ urlEncodedBytesOrText Rfc3987Predicates.unencodedQuery Rfc3986Predicates.unencodedQuery value -fragment :: Fragment -> Builder+fragment :: Fragment -> TextBuilder fragment (Fragment value) =- urlEncodedText I.unencodedFragment value+ urlEncodedBytesOrText Rfc3987Predicates.unencodedFragment Rfc3986Predicates.unencodedFragment value -{-| Apply URL-encoding to text -}-urlEncodedText :: I.Predicate -> Text -> Builder+urlEncodedBytesOrText :: CorePredicates.Predicate -> CorePredicates.Predicate -> ByteString -> TextBuilder+urlEncodedBytesOrText unencodedPredicate1 unencodedPredicate2 bytes =+ case A.decodeUtf8' bytes of+ Right text -> urlEncodedText unencodedPredicate1 text+ Left _ -> urlEncodedBytes unencodedPredicate2 bytes++-- | Apply URL-encoding to text+urlEncodedBytes :: CorePredicates.Predicate -> ByteString -> TextBuilder+urlEncodedBytes unencodedPredicate =+ ByteString.foldl'+ ( \builder ->+ mappend builder . \byte ->+ if unencodedPredicate (fromIntegral byte)+ then unicodeCodepoint (fromIntegral byte)+ else urlEncodedByte byte+ )+ mempty++-- | Apply URL-encoding to text+urlEncodedText :: CorePredicates.Predicate -> Text -> TextBuilder urlEncodedText unencodedPredicate =- C.foldl' (\ builder -> mappend builder . urlEncodedUnicodeCodePoint unencodedPredicate . ord) mempty+ C.foldl' (\builder -> mappend builder . urlEncodedUnicodeCodePoint unencodedPredicate . ord) mempty -urlEncodedUnicodeCodePoint :: I.Predicate -> Int -> Builder+urlEncodedUnicodeCodePoint :: CorePredicates.Predicate -> Int -> TextBuilder urlEncodedUnicodeCodePoint unencodedPredicate codePoint = if unencodedPredicate codePoint- then- unicodeCodePoint codePoint+ then unicodeCodepoint codePoint else- K.unicodeCodePoint codePoint- (\ b1 -> urlEncodedByte b1)- (\ b1 b2 -> urlEncodedByte b1 <> urlEncodedByte b2)- (\ b1 b2 b3 -> urlEncodedByte b1 <> urlEncodedByte b2 <> urlEncodedByte b3)- (\ b1 b2 b3 b4 -> urlEncodedByte b1 <> urlEncodedByte b2 <> urlEncodedByte b3 <> urlEncodedByte b4)+ K.unicodeCodePoint+ codePoint+ (\b1 -> urlEncodedByte b1)+ (\b1 b2 -> urlEncodedByte b1 <> urlEncodedByte b2)+ (\b1 b2 b3 -> urlEncodedByte b1 <> urlEncodedByte b2 <> urlEncodedByte b3)+ (\b1 b2 b3 b4 -> urlEncodedByte b1 <> urlEncodedByte b2 <> urlEncodedByte b3 <> urlEncodedByte b4) -urlEncodedByte :: Word8 -> Builder-urlEncodedByte x =- case divMod x 16 of- (d1, d2) -> char '%' <> hexadecimalDigit d1 <> hexadecimalDigit d2+urlEncodedByte :: Word8 -> TextBuilder+urlEncodedByte x = char '%' <> hexadecimal x -prependIfNotNull :: Builder -> Builder -> Builder+prependIfNotNull :: TextBuilder -> TextBuilder -> TextBuilder prependIfNotNull prepended it =- if null it+ if isEmpty it then mempty else prepended <> it -appendIfNotNull :: Builder -> Builder -> Builder+appendIfNotNull :: TextBuilder -> TextBuilder -> TextBuilder appendIfNotNull appended it =- if null it+ if isEmpty it then mempty else it <> appended
library/Iri/Utf8CodePoint.hs view
@@ -1,14 +1,11 @@-{- |-Utilities for the UTF-8 encoding.--}+-- |+-- Utilities for the UTF-8 encoding. module Iri.Utf8CodePoint where import Iri.Prelude --{-|-Church encoding of a UTF8-encoded character.--}+-- |+-- Church encoding of a UTF8-encoded character. type Utf8CodePoint = forall a. (Word8 -> a) ->@@ -19,31 +16,30 @@ {-# INLINE char #-} char :: Char -> Utf8CodePoint-char =- unicodeCodePoint . ord+char a =+ unicodeCodePoint (ord a) {-# INLINE unicodeCodePoint #-} unicodeCodePoint :: Int -> Utf8CodePoint unicodeCodePoint x f1 f2 f3 f4 = if x <= 0x7F- then- f1 (fromIntegral x)- else + then f1 (fromIntegral x)+ else if x <= 0x07FF then f2- (fromIntegral ((x `shiftR` 6) + 0xC0))- (fromIntegral ((x .&. 0x3F) + 0x80))+ (fromIntegral ((x `shiftR` 6) + 0xC0))+ (fromIntegral ((x .&. 0x3F) + 0x80)) else if x <= 0xFFFF then f3- (fromIntegral (x `shiftR` 12) + 0xE0)- (fromIntegral ((x `shiftR` 6) .&. 0x3F) + 0x80)- (fromIntegral (x .&. 0x3F) + 0x80)+ (fromIntegral (x `shiftR` 12) + 0xE0)+ (fromIntegral ((x `shiftR` 6) .&. 0x3F) + 0x80)+ (fromIntegral (x .&. 0x3F) + 0x80) else f4- (fromIntegral (x `shiftR` 18) + 0xF0)- (fromIntegral ((x `shiftR` 12) .&. 0x3F) + 0x80)- (fromIntegral ((x `shiftR` 6) .&. 0x3F) + 0x80)- (fromIntegral (x .&. 0x3F) + 0x80)+ (fromIntegral (x `shiftR` 18) + 0xF0)+ (fromIntegral ((x `shiftR` 12) .&. 0x3F) + 0x80)+ (fromIntegral ((x `shiftR` 6) .&. 0x3F) + 0x80)+ (fromIntegral (x .&. 0x3F) + 0x80)
library/Iri/Vector.hs view
@@ -1,12 +1,10 @@-module Iri.Vector-where+module Iri.Vector where -import Iri.Prelude hiding (null, length) import Data.Vector-+import Iri.Prelude hiding (length, null) {-# INLINE intercalate #-}-intercalate :: (Semigroup monoid, Monoid monoid) => (element -> monoid) -> monoid -> Vector element -> monoid+intercalate :: (Monoid monoid) => (element -> monoid) -> monoid -> Vector element -> monoid intercalate project separator vector = if null vector then mempty
+ punycode/Data/Text/Punycode.hs view
@@ -0,0 +1,4 @@+module Data.Text.Punycode (encode, PunycodeDecodeException (..), decode) where++import Data.Text.Punycode.Decode (PunycodeDecodeException (..), decode)+import Data.Text.Punycode.Encode (encode)
+ punycode/Data/Text/Punycode/Decode.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE DeriveDataTypeable #-}++module Data.Text.Punycode.Decode (PunycodeDecodeException (..), decode) where++import Control.Exception.Base+import qualified Data.ByteString as BS+import Data.Char+import Data.Serialize hiding (decode)+import qualified Data.Text as T+import Data.Text.Punycode.Shared+import Data.Typeable+import Data.Word++data PunycodeDecodeException+ = GenericDecodeException+ | InternalStringTooShort+ | InputTooShort+ | RightOfHyphenShouldBeAlphanumeric+ | LeftOfHyphenShouldBeBasic+ | CantStartWithDash+ | InvalidCodePoint+ deriving (Eq, Show, Typeable)++instance Exception PunycodeDecodeException++-- | Decode a string into its unicode form+decode :: BS.ByteString -> Either PunycodeDecodeException T.Text+decode input+ | input == BS.pack [45, 45] = Right $ T.pack "-"+ | not (BS.null input) && BS.length (BS.filter (== 45) input) == 1 && BS.head input == 45 = Left CantStartWithDash+ | T.any (not . isExtendedBasic) before = Left LeftOfHyphenShouldBeBasic+ | otherwise = case runGet (inner2 initial_n 0 initial_bias before) after of+ Right out -> out+ Left _ -> Left InputTooShort+ where+ (before, after)+ | BS.any f input = (T.pack $ map (chr . fromIntegral) $ BS.unpack $ BS.init b1, a1)+ | otherwise = (T.empty, input)+ f = (== (fromIntegral $ ord '-'))+ (b1, a1) = BS.breakEnd f input++inner2 :: Int -> Int -> Int -> T.Text -> Get (Either PunycodeDecodeException T.Text)+inner2 n oldi bias output = do+ b <- isEmpty+ helper b+ where+ helper False = do+ i <- inner base 1 oldi bias+ helper' i+ where+ helper' Nothing = return $ Left RightOfHyphenShouldBeAlphanumeric+ helper' (Just i) = case output' of+ Right output'' -> inner2 n' (i' + 1) bias' output''+ Left err -> return $ Left err+ where+ bias' = adapt (i - oldi) (T.length output + 1) (oldi == 0)+ n' = n + i `div` (T.length output + 1)+ i' = i `mod` (T.length output + 1)+ output' = insertInto output n' i'+ helper True = return $ Right output++inner :: Int -> Int -> Int -> Int -> Get (Maybe Int)+inner k w i bias = do+ word8 <- getWord8+ helper $ word8ToDigit word8+ where+ helper Nothing = return Nothing+ helper (Just digit)+ | digit < t = return $ Just i'+ | otherwise = inner (k + base) w' i' bias+ where+ w' = w * (base - t)+ i' = i + digit * w+ t+ | k <= bias + tmin = tmin+ | k >= bias + tmax = tmax+ | otherwise = k - bias++insertInto :: T.Text -> Int -> Int -> Either PunycodeDecodeException T.Text+insertInto input n i+ | T.length input < i = Left InternalStringTooShort+ | otherwise = case n' of+ Just n'' -> Right $ T.concat [T.take i input, T.singleton n'', T.drop i input]+ Nothing -> Left InvalidCodePoint+ where+ n' = safeChr n++safeChr :: Int -> Maybe Char+safeChr x+ | x >= 0 && x <= fromEnum (maxBound :: Char) = Just $ chr x+ | otherwise = Nothing++word8ToDigit :: Word8 -> Maybe Int+word8ToDigit = helper . fromIntegral+ where+ helper word8+ | word8 >= ord 'a' && word8 <= ord 'z' = Just $ word8 - (ord 'a')+ | word8 >= ord 'A' && word8 <= ord 'Z' = Just $ word8 - (ord 'A')+ | word8 >= ord '0' && word8 <= ord '9' = Just $ 26 + word8 - (ord '0')+ | otherwise = Nothing++isExtendedBasic :: Char -> Bool+isExtendedBasic x+ | isBasic x = True+ | ord x == 128 = True+ | otherwise = False
+ punycode/Data/Text/Punycode/Encode.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE FlexibleContexts #-}++module Data.Text.Punycode.Encode (encode) where++import Control.Monad+import Control.Monad.State hiding (state)+import Control.Monad.Writer+import qualified Data.ByteString as BS+import Data.Char+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import Data.Text.Punycode.Shared+import Data.Word++data PunycodeState = PunycodeState+ { n :: Int,+ delta :: Int,+ bias :: Int,+ h :: Int+ }++-- | Encode a string into its ascii form+encode :: T.Text -> BS.ByteString+encode = execWriter . initialWriter++initialWriter :: (MonadWriter BS.ByteString m) => T.Text -> m ()+initialWriter input = do+ tell basics+ when (b > 0) $ tell $ BS.singleton $ fromIntegral $ ord '-'+ evalStateT (inner3 (map ord $ T.unpack input) b) $+ PunycodeState+ { n = initial_n,+ delta = 0,+ bias = initial_bias,+ h = b+ }+ where+ basics = TE.encodeUtf8 $ T.filter isBasic input+ b = BS.length basics++inner3 :: (MonadState PunycodeState m, MonadWriter BS.ByteString m) => [Int] -> Int -> m ()+inner3 input b = do+ state <- get+ helper state+ where+ helper state+ | h' < length input = do+ put $ state {n = m, delta = delta'}+ mapM_ (inner2 b) input+ state' <- get+ put $ state' {delta = (delta state') + 1, n = (n state') + 1}+ inner3 input b+ | otherwise = return ()+ where+ m = minimum $ filter (>= n') input+ n' = n state+ h' = h state+ delta' = (delta state) + (m - n') * (h' + 1)++inner2 :: (MonadState PunycodeState m, MonadWriter BS.ByteString m) => Int -> Int -> m ()+inner2 b c = do+ state <- get+ helper state+ where+ helper state+ | c == n' = do+ q <- inner delta' base bias'+ tell $ BS.singleton $ baseToAscii q+ put $ state {bias = adapt delta' (h' + 1) (h' == b), delta = 0, h = (h state) + 1}+ | otherwise = put $ state {delta = delta'}+ where+ delta' = (delta state) + d+ where+ d+ | c < n' = 1+ | otherwise = 0+ n' = n state+ bias' = bias state+ h' = h state++inner :: (MonadWriter BS.ByteString m) => Int -> Int -> Int -> m Int+inner q k bias'+ | q < t = return q+ | otherwise = do+ tell $ BS.singleton $ baseToAscii $ t + ((q - t) `mod` (base - t))+ inner ((q - t) `div` (base - t)) (k + base) bias'+ where+ t+ | k <= bias' + tmin = tmin+ | k >= bias' + tmax = tmax+ | otherwise = k - bias'++baseToAscii :: Int -> Word8+baseToAscii i+ | i < 26 = fromIntegral $ i + (ord 'a')+ | otherwise = fromIntegral $ (i - 26) + (ord '0')
@@ -0,0 +1,39 @@+module Data.Text.Punycode.Shared where++import Data.Char (ord)++base :: Int+base = 36++tmin :: Int+tmin = 1++tmax :: Int+tmax = 26++skew :: Int+skew = 38++damp :: Int+damp = 700++initial_bias :: Int+initial_bias = 72++initial_n :: Int+initial_n = 128++adapt :: Int -> Int -> Bool -> Int+adapt delta numpoints firsttime = helper+ where+ helper = loop 0 $ delta' + (delta' `div` numpoints)+ where+ delta'+ | firsttime = delta `div` damp+ | otherwise = delta `div` 2+ loop k delta'+ | delta' > ((base - tmin) * tmax) `div` 2 = loop (k + base) $ delta' `div` (base - tmin)+ | otherwise = k + (((base - tmin + 1) * delta') `div` (delta' + skew))++isBasic :: Char -> Bool+isBasic = (< initial_n) . ord
+ test/Main.hs view
@@ -0,0 +1,124 @@+module Main where++import qualified Data.Text as T+import qualified Iri.Parsing.ByteString as D+import qualified Iri.Parsing.Text as F+import Iri.QuasiQuoter+import qualified Iri.Rendering.ByteString as E+import qualified Iri.Rendering.Text as C+import Test.Tasty+import Test.Tasty.HUnit+import Prelude++main :: IO ()+main =+ defaultMain+ $ testGroup "All tests"+ $ [ testCase "No path"+ $ assertEqual+ ""+ (Right "https://ru.wikipedia.org?query")+ ( fmap+ C.iri+ (D.uri "https://ru.wikipedia.org?query")+ ),+ testCase "Cyrillic path" $ do+ assertEqual+ ""+ (Right "https://ru.wikipedia.org/wiki/Баренцбург")+ ( fmap+ C.iri+ (D.uri "https://ru.wikipedia.org/wiki/%D0%91%D0%B0%D1%80%D0%B5%D0%BD%D1%86%D0%B1%D1%83%D1%80%D0%B3")+ ),+ testCase "Complex query" $ do+ assertEqual+ ""+ (Right "https://news.yandex.ru/yandsearch?cl4url=iz.ru/669426/2017-11-10/smi-inoagenty-obiazhut-soobshchat-ob-etom-v-sotcsetiakh&lang=ru&from=main_portal&stid=5bdMOjSopWu34pdT-391&lr=213&msid=1510328052.50316.22874.7658&mlid=1510327228.glob_225.904af5bb")+ ( fmap+ C.iri+ (D.uri "https://news.yandex.ru/yandsearch?cl4url=iz.ru/669426/2017-11-10/smi-inoagenty-obiazhut-soobshchat-ob-etom-v-sotcsetiakh&lang=ru&from=main_portal&stid=5bdMOjSopWu34pdT-391&lr=213&msid=1510328052.50316.22874.7658&mlid=1510327228.glob_225.904af5bb")+ ),+ testCase "Rendering"+ $ let inputs =+ [ "https://ru.wikipedia.org/wiki/%D0%91%D0%B0%D1%80%D0%B5%D0%BD%D1%86%D0%B1%D1%83%D1%80%D0%B3",+ "https://news.yandex.ru/yandsearch?cl4url=iz.ru/669426/2017-11-10/smi-inoagenty-obiazhut-soobshchat-ob-etom-v-sotcsetiakh&lang=ru&from=main_portal&stid=5bdMOjSopWu34pdT-391&lr=213&msid=1510328052.50316.22874.7658&mlid=1510327228.glob_225.904af5bb"+ ]+ in forM_ inputs $ \input ->+ assertEqual "" (Right input) (fmap E.uri (D.uri input)),+ testCase "about:blank" $ assertEqual "" "about:blank" (E.uri [uri|about:blank|]),+ testCase "Empty hierarchy" $ assertEqual "" "about:" (E.uri [uri|about:|]),+ testCase "HTTP URI Text Rendering"+ $ assertEqual+ ""+ "https://ru.wikipedia.org/wiki/Баренцбург"+ (C.httpIri [httpUri|https://ru.wikipedia.org/wiki/%D0%91%D0%B0%D1%80%D0%B5%D0%BD%D1%86%D0%B1%D1%83%D1%80%D0%B3|]),+ testCase "HTTP URI Show"+ $ assertEqual+ ""+ "\"https://ru.wikipedia.org/wiki/Баренцбург\""+ (show [httpUri|https://ru.wikipedia.org/wiki/%D0%91%D0%B0%D1%80%D0%B5%D0%BD%D1%86%D0%B1%D1%83%D1%80%D0%B3|]),+ testCase "Messed up URIs"+ $ let testUri uri =+ assertEqual "Original and parsed URIs are not equal" (Right uri) (fmap E.uri (D.uri uri))+ in traverse_+ testUri+ [ "http://m.ru.hellomagazine.com/krasota-i-zdorove/ukhod-za-kozhey-i-volosami/11373-5-prichesok-kotorye-sdelayut-vas-molozhe.html?utm_medium=teaser&utm_campaign=gnezdo1&utm_term=15376&utm_content=[TID",+ "https://m.tsargrad.tv/news/komediju_113156?%90\"%20"+ ],+ testCase "User Info"+ $ assertEqual "" (Right "http://user:password@localhost:993") (fmap E.uri (D.uri "http://user:password@localhost:993")),+ testGroup "IRI parser vs. URI parser"+ $ let testUriIri iri uri =+ testCase (iri <> " vs. " <> uri)+ $ assertEqual "" (F.iri (fromString iri)) (D.uri (fromString uri))+ in [ testUriIri+ "https://ru.wikipedia.org/wiki/Баренцбург"+ "https://ru.wikipedia.org/wiki/%D0%91%D0%B0%D1%80%D0%B5%D0%BD%D1%86%D0%B1%D1%83%D1%80%D0%B3",+ testUriIri+ "https://www.w3.org/International/articles/idn-and-iri/JP納豆/引き割り納豆.html"+ "https://www.w3.org/International/articles/idn-and-iri/JP%E7%B4%8D%E8%B1%86/%E5%BC%95%E3%81%8D%E5%89%B2%E3%82%8A%E7%B4%8D%E8%B1%86.html",+ testUriIri+ "http://点心和烤鸭.w3.mag.keio.ac.jp"+ "http://xn--0trv4xfvn8el34t.w3.mag.keio.ac.jp/"+ ],+ testCase "Hex digit parsing bug reproduction - lowercase hex digits"+ $ do+ -- Bug: lowercase hex digits 'a'-'f' were calculated as (ascii - 97) instead of (ascii - 87)+ -- This caused %6a (should be ASCII 106 = 'j') to be parsed incorrectly+ -- %6a = 6*16 + 10 = 106 = 'j', but with bug: 6*16 + 0 = 96 = '`'+ let result = F.iri "https://example.com/test%6a" -- should be 'j'+ case result of+ Left err -> assertFailure $ "Should parse successfully: " ++ T.unpack err+ Right iri ->+ let rendered = C.iri iri -- Use text rendering to see decoded content+ in assertBool "Should contain 'j' when correctly parsed" ('j' `T.elem` rendered),+ testCase "Lowercase hex digits a-f UTF-8 parsing"+ $ do+ -- Test that %c3%a9 (UTF-8 for é) parses correctly with lowercase hex+ -- This tests the fix for hex digits 'c' and 'a' which were affected by the bug+ let result = F.iri "https://example.com/caf%c3%a9"+ case result of+ Left _ -> assertFailure "Should parse UTF-8 encoded URL successfully"+ Right iri ->+ let rendered = C.iri iri -- Use text rendering to see the decoded UTF-8+ in assertBool "Should contain é character when correctly decoded" ('é' `T.elem` rendered),+ testGroup "Mess"+ $ [ testCase "1"+ $ assertEqual+ ""+ (Right "https://www.kupivip.ru/catalog/muzhchinam/odezhda?srcid=goo-tm-open&lpu=|source:google|medium:cpc|adposition:1t1|creative:195388077752|term:%2B%D0%B2%D0%B8%D0%BF%20%2B%D0%BA%D1%83%D0%BF%D0%B8|campaignid:801742449|campaign:goo_tm_mob_s_male|other:m|ad:1|&gclid=CjwKEAjwgb3OBRDNi_2TwZ6u7D4SJADsmW8QKiRxDZZ0hRAnyspAcVW582c7zzQSA7CC0FavPR63dBoCrKPw_wcB")+ (fmap E.uri (D.uri "https://www.kupivip.ru/catalog/muzhchinam/odezhda?srcid=goo-tm-open&lpu=|source:google|medium:cpc|adposition:1t1|creative:195388077752|term:%2B%D0%B2%D0%B8%D0%BF%20%2B%D0%BA%D1%83%D0%BF%D0%B8|campaignid:801742449|campaign:goo_tm_mob_s_male|other:m|ad:1|&gclid=CjwKEAjwgb3OBRDNi_2TwZ6u7D4SJADsmW8QKiRxDZZ0hRAnyspAcVW582c7zzQSA7CC0FavPR63dBoCrKPw_wcB")),+ testGroup "2"+ $ [ testCase "show"+ $ assertEqual+ ""+ (Right "\"https://www.pleer.ru/search_%e4%eb%ff+%e1%e5%eb%fc%ff+%f1%f3%f8%e8%eb%ea%e0.html\"")+ (fmap show (D.uri "https://www.pleer.ru/search_%e4%eb%ff+%e1%e5%eb%fc%ff+%f1%f3%f8%e8%eb%ea%e0.html")),+ testCase "Text rendering"+ $ assertEqual+ ""+ (Right "https://www.pleer.ru/search_%e4%eb%ff+%e1%e5%eb%fc%ff+%f1%f3%f8%e8%eb%ea%e0.html")+ (fmap C.iri (D.uri "https://www.pleer.ru/search_%e4%eb%ff+%e1%e5%eb%fc%ff+%f1%f3%f8%e8%eb%ea%e0.html"))+ ]+ ]+ ]
− tests/Main.hs
@@ -1,93 +0,0 @@-module Main where--import Prelude-import Test.QuickCheck.Instances-import Test.Tasty-import Test.Tasty.Runners-import Test.Tasty.HUnit-import Test.Tasty.QuickCheck-import Iri.QuasiQuoter-import qualified Iri.Data as A-import qualified Iri.Parsing.ByteString as D-import qualified Iri.Parsing.Text as F-import qualified Iri.Rendering.Text as C-import qualified Iri.Rendering.ByteString as E---main =- defaultMain $- testGroup "All tests" $- [- testCase "No path" $- assertEqual ""- (Right "https://ru.wikipedia.org?query")- (fmap C.iri- (D.uri "https://ru.wikipedia.org?query"))- ,- testCase "Cyrillic path" $ do- assertEqual ""- (Right "https://ru.wikipedia.org/wiki/Баренцбург")- (fmap C.iri- (D.uri "https://ru.wikipedia.org/wiki/%D0%91%D0%B0%D1%80%D0%B5%D0%BD%D1%86%D0%B1%D1%83%D1%80%D0%B3"))- ,- testCase "Complex query" $ do- assertEqual ""- (Right "https://news.yandex.ru/yandsearch?cl4url=iz.ru/669426/2017-11-10/smi-inoagenty-obiazhut-soobshchat-ob-etom-v-sotcsetiakh&lang=ru&from=main_portal&stid=5bdMOjSopWu34pdT-391&lr=213&msid=1510328052.50316.22874.7658&mlid=1510327228.glob_225.904af5bb")- (fmap C.iri- (D.uri "https://news.yandex.ru/yandsearch?cl4url=iz.ru/669426/2017-11-10/smi-inoagenty-obiazhut-soobshchat-ob-etom-v-sotcsetiakh&lang=ru&from=main_portal&stid=5bdMOjSopWu34pdT-391&lr=213&msid=1510328052.50316.22874.7658&mlid=1510327228.glob_225.904af5bb"))- ,- testCase "Rendering" $- let- inputs =- [- "https://ru.wikipedia.org/wiki/%D0%91%D0%B0%D1%80%D0%B5%D0%BD%D1%86%D0%B1%D1%83%D1%80%D0%B3"- ,- "https://news.yandex.ru/yandsearch?cl4url=iz.ru/669426/2017-11-10/smi-inoagenty-obiazhut-soobshchat-ob-etom-v-sotcsetiakh&lang=ru&from=main_portal&stid=5bdMOjSopWu34pdT-391&lr=213&msid=1510328052.50316.22874.7658&mlid=1510327228.glob_225.904af5bb"- ]- in- forM_ inputs $ \ input ->- let- Right iri = D.uri input- in assertEqual "" input (E.uri iri)- ,- testCase "about:blank" $ assertEqual "" "about:blank" (E.uri [uri|about:blank|])- ,- testCase "Empty hierarchy" $ assertEqual "" "about:" (E.uri [uri|about:|])- ,- testCase "HTTP URI Text Rendering" $- assertEqual ""- "https://ru.wikipedia.org/wiki/Баренцбург"- (C.httpIri [httpUri|https://ru.wikipedia.org/wiki/%D0%91%D0%B0%D1%80%D0%B5%D0%BD%D1%86%D0%B1%D1%83%D1%80%D0%B3|])- ,- testCase "HTTP URI Show" $- assertEqual ""- "https://ru.wikipedia.org/wiki/Баренцбург"- (show [httpUri|https://ru.wikipedia.org/wiki/%D0%91%D0%B0%D1%80%D0%B5%D0%BD%D1%86%D0%B1%D1%83%D1%80%D0%B3|])- ,- parsing- ]--parsing =- testGroup "Parsing" $- [- testCase "User Info" $- assertEqual "" (Right "http://user:password@localhost:993") (fmap E.uri (D.uri "http://user:password@localhost:993"))- ,- testCase "URI, IRI" $- assertEqual ""- (D.uri "https://ru.wikipedia.org/wiki/%D0%91%D0%B0%D1%80%D0%B5%D0%BD%D1%86%D0%B1%D1%83%D1%80%D0%B3")- (F.iri "https://ru.wikipedia.org/wiki/Баренцбург")- ,- testGroup "Mess" $- [- testCase "1" $- assertEqual ""- (Right "https://www.kupivip.ru/catalog/muzhchinam/odezhda?srcid=goo-tm-open&lpu=|source:google|medium:cpc|adposition:1t1|creative:195388077752|term:%2B%D0%B2%D0%B8%D0%BF%20%2B%D0%BA%D1%83%D0%BF%D0%B8|campaignid:801742449|campaign:goo_tm_mob_s_male|other:m|ad:1|&gclid=CjwKEAjwgb3OBRDNi_2TwZ6u7D4SJADsmW8QKiRxDZZ0hRAnyspAcVW582c7zzQSA7CC0FavPR63dBoCrKPw_wcB")- (fmap E.uri (D.uri "https://www.kupivip.ru/catalog/muzhchinam/odezhda?srcid=goo-tm-open&lpu=|source:google|medium:cpc|adposition:1t1|creative:195388077752|term:%2B%D0%B2%D0%B8%D0%BF%20%2B%D0%BA%D1%83%D0%BF%D0%B8|campaignid:801742449|campaign:goo_tm_mob_s_male|other:m|ad:1|&gclid=CjwKEAjwgb3OBRDNi_2TwZ6u7D4SJADsmW8QKiRxDZZ0hRAnyspAcVW582c7zzQSA7CC0FavPR63dBoCrKPw_wcB"))- ,- testCase "2" $- assertEqual ""- (Right "https://www.pleer.ru/search_%E4%EB%FF+%E1%E5%EB%FC%FF+%F1%F3%F8%E8%EB%EA%E0.html")- (fmap show (D.uri "https://www.pleer.ru/search_%E4%EB%FF+%E1%E5%EB%FC%FF+%F1%F3%F8%E8%EB%EA%E0.html"))- ]- ]