packages feed

hasmin 1.0.1 → 1.0.2

raw patch · 40 files changed

+1995/−1208 lines, 40 filesdep +quickcheck-instancesdep −bifunctorsdep ~basedep ~criteriondep ~doctestPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: quickcheck-instances

Dependencies removed: bifunctors

Dependency ranges changed: base, criterion, doctest

API changes (from Hackage documentation)

- Hasmin.Types.Dimension: Duration :: Number -> DurationUnit -> Duration
- Hasmin.Types.Dimension: data Duration
- Hasmin.Types.Dimension: data DurationUnit
- Hasmin.Types.Dimension: instance GHC.Classes.Eq Hasmin.Types.Dimension.Duration
- Hasmin.Types.Dimension: instance GHC.Classes.Eq Hasmin.Types.Dimension.DurationUnit
- Hasmin.Types.Dimension: instance GHC.Show.Show Hasmin.Types.Dimension.Duration
- Hasmin.Types.Dimension: instance GHC.Show.Show Hasmin.Types.Dimension.DurationUnit
- Hasmin.Types.Dimension: instance Hasmin.Class.Minifiable Hasmin.Types.Dimension.Duration
- Hasmin.Types.Dimension: instance Hasmin.Class.ToText Hasmin.Types.Dimension.Duration
- Hasmin.Types.Dimension: instance Hasmin.Class.ToText Hasmin.Types.Dimension.DurationUnit
- Hasmin.Types.Dimension: instance Hasmin.Types.Dimension.Unit Hasmin.Types.Dimension.DurationUnit
- Hasmin.Types.Value: DurationV :: Duration -> Value
+ Hasmin.Parser.BasicShape: circle :: Parser BasicShape
+ Hasmin.Parser.BasicShape: ellipse :: Parser BasicShape
+ Hasmin.Parser.BasicShape: inset :: Parser BasicShape
+ Hasmin.Parser.BasicShape: polygon :: Parser BasicShape
+ Hasmin.Parser.BasicShape: shapeRadius :: Parser ShapeRadius
+ Hasmin.Parser.BorderRadius: borderRadius :: Parser BorderRadius
+ Hasmin.Parser.Color: color :: Parser Color
+ Hasmin.Parser.Color: colorFunctionParser :: Text -> Parser Color
+ Hasmin.Parser.Color: colorFunctionsParsers :: [(Text, Parser Color)]
+ Hasmin.Parser.Color: functionPar :: Map Text (Parser a) -> Text -> Parser a
+ Hasmin.Parser.Color: hex :: Parser Color
+ Hasmin.Parser.Color: hsl :: Parser Color
+ Hasmin.Parser.Color: hsla :: Parser Color
+ Hasmin.Parser.Color: namedColorsParsersMap :: Map Text (Parser Color)
+ Hasmin.Parser.Color: rgb :: Parser Color
+ Hasmin.Parser.Color: rgba :: Parser Color
+ Hasmin.Parser.Dimension: angle :: Parser Angle
+ Hasmin.Parser.Dimension: angleConstructorsList :: [(Text, Number -> Angle)]
+ Hasmin.Parser.Dimension: dimensionParser :: Map Text (Number -> a) -> a -> Parser a
+ Hasmin.Parser.Dimension: distance :: Parser Length
+ Hasmin.Parser.Dimension: distanceConstructorsList :: [(Text, Number -> Length)]
+ Hasmin.Parser.Dimension: time :: Parser Time
+ Hasmin.Parser.Dimension: timeConstructorsList :: [(Text, Number -> Time)]
+ Hasmin.Parser.Gradient: colorStop :: Parser ColorStop
+ Hasmin.Parser.Gradient: colorStopList :: Parser [ColorStop]
+ Hasmin.Parser.Gradient: lineargradient :: Parser Gradient
+ Hasmin.Parser.Gradient: radialgradient :: Parser Gradient
+ Hasmin.Parser.Gradient: sideOrCorner :: Parser (Side, Maybe Side)
+ Hasmin.Parser.Numeric: alphavalue :: Parser Alphavalue
+ Hasmin.Parser.Numeric: int :: Parser Int
+ Hasmin.Parser.Numeric: number :: Parser Number
+ Hasmin.Parser.Numeric: percentage :: Parser Percentage
+ Hasmin.Parser.Numeric: rational :: Parser Rational
+ Hasmin.Parser.PercentageLength: percentageLength :: Parser PercentageLength
+ Hasmin.Parser.Position: position :: Parser Position
+ Hasmin.Parser.Primitives: digits :: Parser String
+ Hasmin.Parser.Primitives: escape :: Parser Builder
+ Hasmin.Parser.Primitives: ident :: Parser Text
+ Hasmin.Parser.Primitives: int :: Parser Int
+ Hasmin.Parser.Primitives: int' :: Parser String
+ Hasmin.Parser.Primitives: nmchar :: Parser Builder
+ Hasmin.Parser.Primitives: nmstart :: Parser Builder
+ Hasmin.Parser.Primitives: unicode :: Parser Builder
+ Hasmin.Parser.Selector: selector :: Parser Selector
+ Hasmin.Parser.Selector: selectors :: Parser [Selector]
+ Hasmin.Parser.String: convertEscaped :: Parser Text
+ Hasmin.Parser.String: stringtype :: Parser StringType
+ Hasmin.Parser.TimingFunction: cubicbezier :: Parser TimingFunction
+ Hasmin.Parser.TimingFunction: steps :: Parser TimingFunction
+ Hasmin.Parser.TimingFunction: timingFunction :: Parser TimingFunction
+ Hasmin.Parser.TransformFunction: matrix :: Parser TransformFunction
+ Hasmin.Parser.TransformFunction: matrix3d :: Parser TransformFunction
+ Hasmin.Parser.TransformFunction: rotate3d :: Parser TransformFunction
+ Hasmin.Parser.TransformFunction: scale :: Parser TransformFunction
+ Hasmin.Parser.TransformFunction: scale3d :: Parser TransformFunction
+ Hasmin.Parser.TransformFunction: skew :: Parser TransformFunction
+ Hasmin.Parser.TransformFunction: translate :: Parser TransformFunction
+ Hasmin.Parser.TransformFunction: translate3d :: Parser TransformFunction
+ Hasmin.Parser.Value: borderRadius :: Parser BorderRadius
+ Hasmin.Types.BasicShape: Circle :: (Maybe ShapeRadius) -> (Maybe Position) -> BasicShape
+ Hasmin.Types.BasicShape: Ellipse :: (AtMost2 ShapeRadius) -> (Maybe Position) -> BasicShape
+ Hasmin.Types.BasicShape: EvenOdd :: FillRule
+ Hasmin.Types.BasicShape: Inset :: (NonEmpty ShapeArg) -> (Maybe BorderRadius) -> BasicShape
+ Hasmin.Types.BasicShape: NonZero :: FillRule
+ Hasmin.Types.BasicShape: None :: AtMost2 a
+ Hasmin.Types.BasicShape: One :: a -> AtMost2 a
+ Hasmin.Types.BasicShape: Polygon :: (Maybe FillRule) -> (NonEmpty (ShapeArg, ShapeArg)) -> BasicShape
+ Hasmin.Types.BasicShape: SRClosestSide :: ShapeRadius
+ Hasmin.Types.BasicShape: SRFarthestSide :: ShapeRadius
+ Hasmin.Types.BasicShape: SRLength :: Length -> ShapeRadius
+ Hasmin.Types.BasicShape: SRPercentage :: Percentage -> ShapeRadius
+ Hasmin.Types.BasicShape: Two :: a -> a -> AtMost2 a
+ Hasmin.Types.BasicShape: data AtMost2 a
+ Hasmin.Types.BasicShape: data BasicShape
+ Hasmin.Types.BasicShape: data FillRule
+ Hasmin.Types.BasicShape: data ShapeRadius
+ Hasmin.Types.BasicShape: instance Data.Foldable.Foldable Hasmin.Types.BasicShape.AtMost2
+ Hasmin.Types.BasicShape: instance Data.Traversable.Traversable Hasmin.Types.BasicShape.AtMost2
+ Hasmin.Types.BasicShape: instance GHC.Base.Functor Hasmin.Types.BasicShape.AtMost2
+ Hasmin.Types.BasicShape: instance GHC.Classes.Eq Hasmin.Types.BasicShape.BasicShape
+ Hasmin.Types.BasicShape: instance GHC.Classes.Eq Hasmin.Types.BasicShape.FillRule
+ Hasmin.Types.BasicShape: instance GHC.Classes.Eq Hasmin.Types.BasicShape.ShapeRadius
+ Hasmin.Types.BasicShape: instance GHC.Classes.Eq a => GHC.Classes.Eq (Hasmin.Types.BasicShape.AtMost2 a)
+ Hasmin.Types.BasicShape: instance GHC.Show.Show Hasmin.Types.BasicShape.BasicShape
+ Hasmin.Types.BasicShape: instance GHC.Show.Show Hasmin.Types.BasicShape.FillRule
+ Hasmin.Types.BasicShape: instance GHC.Show.Show Hasmin.Types.BasicShape.ShapeRadius
+ Hasmin.Types.BasicShape: instance GHC.Show.Show a => GHC.Show.Show (Hasmin.Types.BasicShape.AtMost2 a)
+ Hasmin.Types.BasicShape: instance Hasmin.Class.Minifiable Hasmin.Types.BasicShape.BasicShape
+ Hasmin.Types.BasicShape: instance Hasmin.Class.ToText Hasmin.Types.BasicShape.BasicShape
+ Hasmin.Types.BasicShape: instance Hasmin.Class.ToText Hasmin.Types.BasicShape.FillRule
+ Hasmin.Types.BasicShape: instance Hasmin.Class.ToText Hasmin.Types.BasicShape.ShapeRadius
+ Hasmin.Types.BorderRadius: BorderRadius :: (NonEmpty PercentageLength) -> [PercentageLength] -> BorderRadius
+ Hasmin.Types.BorderRadius: data BorderRadius
+ Hasmin.Types.BorderRadius: instance GHC.Classes.Eq Hasmin.Types.BorderRadius.BorderRadius
+ Hasmin.Types.BorderRadius: instance GHC.Show.Show Hasmin.Types.BorderRadius.BorderRadius
+ Hasmin.Types.BorderRadius: instance Hasmin.Class.Minifiable Hasmin.Types.BorderRadius.BorderRadius
+ Hasmin.Types.BorderRadius: instance Hasmin.Class.ToText Hasmin.Types.BorderRadius.BorderRadius
+ Hasmin.Types.BorderRadius: isZeroBR :: BorderRadius -> Bool
+ Hasmin.Types.Dimension: Time :: Number -> TimeUnit -> Time
+ Hasmin.Types.Dimension: data Time
+ Hasmin.Types.Dimension: data TimeUnit
+ Hasmin.Types.Dimension: instance GHC.Classes.Eq Hasmin.Types.Dimension.Time
+ Hasmin.Types.Dimension: instance GHC.Classes.Eq Hasmin.Types.Dimension.TimeUnit
+ Hasmin.Types.Dimension: instance GHC.Enum.Bounded Hasmin.Types.Dimension.AngleUnit
+ Hasmin.Types.Dimension: instance GHC.Enum.Bounded Hasmin.Types.Dimension.FrequencyUnit
+ Hasmin.Types.Dimension: instance GHC.Enum.Bounded Hasmin.Types.Dimension.LengthUnit
+ Hasmin.Types.Dimension: instance GHC.Enum.Bounded Hasmin.Types.Dimension.ResolutionUnit
+ Hasmin.Types.Dimension: instance GHC.Enum.Bounded Hasmin.Types.Dimension.TimeUnit
+ Hasmin.Types.Dimension: instance GHC.Enum.Enum Hasmin.Types.Dimension.AngleUnit
+ Hasmin.Types.Dimension: instance GHC.Enum.Enum Hasmin.Types.Dimension.FrequencyUnit
+ Hasmin.Types.Dimension: instance GHC.Enum.Enum Hasmin.Types.Dimension.LengthUnit
+ Hasmin.Types.Dimension: instance GHC.Enum.Enum Hasmin.Types.Dimension.ResolutionUnit
+ Hasmin.Types.Dimension: instance GHC.Enum.Enum Hasmin.Types.Dimension.TimeUnit
+ Hasmin.Types.Dimension: instance GHC.Show.Show Hasmin.Types.Dimension.Time
+ Hasmin.Types.Dimension: instance GHC.Show.Show Hasmin.Types.Dimension.TimeUnit
+ Hasmin.Types.Dimension: instance Hasmin.Class.Minifiable Hasmin.Types.Dimension.Time
+ Hasmin.Types.Dimension: instance Hasmin.Class.ToText Hasmin.Types.Dimension.Time
+ Hasmin.Types.Dimension: instance Hasmin.Class.ToText Hasmin.Types.Dimension.TimeUnit
+ Hasmin.Types.Dimension: instance Hasmin.Types.Dimension.Unit Hasmin.Types.Dimension.TimeUnit
+ Hasmin.Types.PercentageLength: isNonZeroPercentage :: PercentageLength -> Bool
+ Hasmin.Types.PercentageLength: isZero :: (Num a, Eq a) => Either a Length -> Bool
+ Hasmin.Types.PercentageLength: minifyPL :: PercentageLength -> Reader Config PercentageLength
+ Hasmin.Types.PercentageLength: type PercentageLength = Either Percentage Length
+ Hasmin.Types.Position: centerpos :: Position
+ Hasmin.Types.Position: instance GHC.Enum.Bounded Hasmin.Types.Position.PosKeyword
+ Hasmin.Types.Position: instance GHC.Enum.Enum Hasmin.Types.Position.PosKeyword
+ Hasmin.Types.RepeatStyle: instance GHC.Enum.Bounded Hasmin.Types.RepeatStyle.RSKeyword
+ Hasmin.Types.RepeatStyle: instance GHC.Enum.Enum Hasmin.Types.RepeatStyle.RSKeyword
+ Hasmin.Types.TimingFunction: instance GHC.Enum.Bounded Hasmin.Types.TimingFunction.StepPosition
+ Hasmin.Types.TimingFunction: instance GHC.Enum.Enum Hasmin.Types.TimingFunction.StepPosition
+ Hasmin.Types.Value: BasicShapeV :: BasicShape -> Value
+ Hasmin.Types.Value: BorderRadiusV :: BorderRadius -> Value
+ Hasmin.Types.Value: TimeV :: Time -> Value
+ Hasmin.Types.Value: [_bgattachment] :: Value -> Maybe TextV
+ Hasmin.Types.Value: [_bgbox1] :: Value -> Maybe TextV
+ Hasmin.Types.Value: [_bgbox2] :: Value -> Maybe TextV
+ Hasmin.Types.Value: [_bgimage] :: Value -> Maybe Value
+ Hasmin.Types.Value: [_bgposition] :: Value -> Maybe Position
+ Hasmin.Types.Value: [_bgrepeat] :: Value -> Maybe RepeatStyle
+ Hasmin.Types.Value: [_bgsize] :: Value -> Maybe BgSize
+ Hasmin.Types.Value: [_time1] :: Value -> Maybe Time
+ Hasmin.Types.Value: [_time2] :: Value -> Maybe Time
+ Hasmin.Types.Value: [_transitionproperty] :: Value -> Maybe TextV
+ Hasmin.Types.Value: [_transitiontimingfunction] :: Value -> Maybe TimingFunction
+ Hasmin.Utils: mzip :: Applicative f => f a -> f b -> f (a, b)
+ Hasmin.Utils: reduceTRBL :: Eq a => NonEmpty a -> NonEmpty a
- Hasmin.Types.Dimension: Ms :: DurationUnit
+ Hasmin.Types.Dimension: Ms :: TimeUnit
- Hasmin.Types.Dimension: S :: DurationUnit
+ Hasmin.Types.Dimension: S :: TimeUnit
- Hasmin.Types.FilterFunction: Brightness :: (Either Number Percentage) -> FilterFunction
+ Hasmin.Types.FilterFunction: Brightness :: Amount -> FilterFunction
- Hasmin.Types.FilterFunction: Contrast :: (Either Number Percentage) -> FilterFunction
+ Hasmin.Types.FilterFunction: Contrast :: Amount -> FilterFunction
- Hasmin.Types.FilterFunction: Grayscale :: (Either Number Percentage) -> FilterFunction
+ Hasmin.Types.FilterFunction: Grayscale :: Amount -> FilterFunction
- Hasmin.Types.FilterFunction: Invert :: (Either Number Percentage) -> FilterFunction
+ Hasmin.Types.FilterFunction: Invert :: Amount -> FilterFunction
- Hasmin.Types.FilterFunction: Opacity :: (Either Number Percentage) -> FilterFunction
+ Hasmin.Types.FilterFunction: Opacity :: Amount -> FilterFunction
- Hasmin.Types.FilterFunction: Saturate :: (Either Number Percentage) -> FilterFunction
+ Hasmin.Types.FilterFunction: Saturate :: Amount -> FilterFunction
- Hasmin.Types.FilterFunction: Sepia :: (Either Number Percentage) -> FilterFunction
+ Hasmin.Types.FilterFunction: Sepia :: Amount -> FilterFunction
- Hasmin.Types.Value: BgLayer :: (Maybe Value) -> (Maybe Position) -> (Maybe BgSize) -> (Maybe RepeatStyle) -> (Maybe TextV) -> (Maybe TextV) -> (Maybe TextV) -> Value
+ Hasmin.Types.Value: BgLayer :: Maybe Value -> Maybe Position -> Maybe BgSize -> Maybe RepeatStyle -> Maybe TextV -> Maybe TextV -> Maybe TextV -> Value
- Hasmin.Types.Value: SingleAnimation :: (Maybe Duration) -> (Maybe TimingFunction) -> (Maybe Duration) -> (Maybe Value) -> (Maybe TextV) -> (Maybe TextV) -> (Maybe TextV) -> (Maybe Value) -> Value
+ Hasmin.Types.Value: SingleAnimation :: (Maybe Time) -> (Maybe TimingFunction) -> (Maybe Time) -> (Maybe Value) -> (Maybe TextV) -> (Maybe TextV) -> (Maybe TextV) -> (Maybe Value) -> Value
- Hasmin.Types.Value: SingleTransition :: (Maybe TextV) -> (Maybe Duration) -> (Maybe TimingFunction) -> (Maybe Duration) -> Value
+ Hasmin.Types.Value: SingleTransition :: Maybe TextV -> Maybe Time -> Maybe TimingFunction -> Maybe Time -> Value

Files

CHANGELOG.md view
@@ -1,6 +1,15 @@ # Changelog This project adheres to [PVP](https://pvp.haskell.org). +## 1.0.2+### Added+* `caret-color` and `font-display` to the property traits table, enabling their minification.+* `border-radius` minification.+* Parsing and minification of `<basic-shape>`.++### Improved+* Position minification+ ## 1.0.1  ### Added
Main.hs view
@@ -101,7 +101,7 @@ instructions :: ParserInfo Instructions instructions = info (helper <*> versionOption <*> liftA2 (,) command config)     (fullDesc <> header "Hasmin - A Haskell CSS Minifier")-  where versionOption = infoOption ("Hasmin " <> showVersion version <> " " <> take 8 $(gitHash))+  where versionOption = infoOption ("Version " <> showVersion version <> ", " <> "Git revision " <> $(gitHash))                                    (long "version" <> help "Show version and commit hash")  main :: IO ()
hasmin.cabal view
@@ -1,139 +1,155 @@-name:                hasmin
-version:             1.0.1
-license:             BSD3
-license-file:        LICENSE
-author:              (c) 2017 Cristian Adrián Ontivero <cristianontivero@gmail.com>
-maintainer:          Cristian Adrián Ontivero <cristianontivero@gmail.com>
-synopsis:            CSS Minifier
-homepage:            https://github.com/contivero/hasmin#readme
-bug-reports:         https://github.com/contivero/hasmin/issues
-category:            Text
-build-type:          Simple
-extra-source-files:  README.md
-                     CHANGELOG.md
-cabal-version:       >=1.10
-description:
-    A CSS minifier which not only aims at reducing the amount of bytes of the
-    output, but also at improving gzip compression. It may be used as a library,
-    or a stand-alone executable. For the library, refer to the Hasmin module
-    documentation. For the program: the output is the minified CSS file, but
-    hasmin allows also its compression into gzip using Google's Zopfli library.
-    .
-    To use it: ./hasmin input.css > output.css
-    .
-    By default, most minification techniques are enabled. For a list of
-    available flags, do: ./hasmin --help
-
-source-repository head
-  type: git
-  location: https://github.com/contivero/hasmin.git
-
-executable hasmin
-  default-language:    Haskell2010
-  ghc-options:         -O2 -Wall -fwarn-tabs -fwarn-unused-do-bind -Wincomplete-uni-patterns -Wincomplete-record-updates  -Wcompat -Wnoncanonical-monad-instances -Wnoncanonical-monadfail-instances -Wredundant-constraints
-  main-is:             Main.hs
-  other-modules:       Paths_hasmin
-  build-depends:       base                 >=4.9        && <5.1
-                     , optparse-applicative >=0.11       && <0.15
-                     , text                 >=1.2        && <1.3
-                     , hopfli               >=0.2        && <0.4
-                     , bytestring           >=0.10.2.0   && <0.11
-                     , gitrev               >=1.0.0      && <1.4
-                     , hasmin
-
-library
-  default-language:    Haskell2010
-  hs-source-dirs:      src
-  ghc-options:         -O2 -Wall -fwarn-tabs -fwarn-unused-do-bind -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat -Wnoncanonical-monad-instances -Wnoncanonical-monadfail-instances -Wredundant-constraints
-  exposed-modules:     Hasmin
-                     , Hasmin.Config
-                     , Hasmin.Class
-                     , Hasmin.Types.Stylesheet
-                     -- exposed because it is needed by tests:
-                     , Hasmin.Parser.Internal
-                     , Hasmin.Parser.Value
-                     , Hasmin.Types.Color
-                     , Hasmin.Types.Declaration
-                     , Hasmin.Types.Dimension
-                     , Hasmin.Types.Numeric
-                     , Hasmin.Types.TransformFunction
-                     , Hasmin.Types.Value
-                     , Hasmin.Types.RepeatStyle
-                     , Hasmin.Types.FilterFunction
-                     , Hasmin.Types.Position
-                     , Hasmin.Types.BgSize
-                     , Hasmin.Types.String
-                     , Hasmin.Utils
-                     , Hasmin.Types.TimingFunction
-  other-modules:       Hasmin.Parser.Utils
-                     , Hasmin.Properties
-                     , Hasmin.Types.Selector
-                     , Hasmin.Types.Gradient
-                     , Hasmin.Types.PercentageLength
-                     , Hasmin.Types.Shadow
-  build-depends:       base            >=4.9        && <5.1
-                     , bifunctors      >=5.4        && <5.5
-                     , attoparsec      >=0.12       && <0.14
-                     , containers      >=0.5        && <0.6
-                     , matrix          >=0.3.4      && <0.4
-                     , mtl             >=2.2.1      && <2.3
-                     , numbers         >=3000.2.0.0 && <3000.3
-                     , parsers         >=0.12.3     && <0.13
-                     , text            >=1.2        && <1.3
-
-test-suite spec
-  default-language:    Haskell2010
-  type:                exitcode-stdio-1.0
-  hs-source-dirs:      tests
-  main-is:             Spec.hs
-  ghc-options:         -O2 -Wall -fwarn-tabs -fwarn-unused-do-bind -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat -Wnoncanonical-monad-instances -Wnoncanonical-monadfail-instances -Wredundant-constraints
-  default-extensions:  OverloadedStrings
-  other-modules:       Hasmin.Parser.InternalSpec
-                     , Hasmin.Parser.ValueSpec
-                     , Hasmin.TestUtils
-                     , Hasmin.Types.ColorSpec
-                     , Hasmin.Types.DeclarationSpec
-                     , Hasmin.Types.DimensionSpec
-                     , Hasmin.Types.FilterFunctionSpec
-                     , Hasmin.Types.GradientSpec
-                     , Hasmin.Types.PositionSpec
-                     , Hasmin.Types.RepeatStyleSpec
-                     , Hasmin.Types.SelectorSpec
-                     , Hasmin.Types.ValueSpec
-                     , Hasmin.Types.BgSizeSpec
-                     , Hasmin.Types.ShadowSpec
-                     , Hasmin.Types.StringSpec
-                     , Hasmin.Types.StylesheetSpec
-                     , Hasmin.Types.TimingFunctionSpec
-                     , Hasmin.Types.TransformFunctionSpec
-  build-depends:       base             >=4.9     && <5.1
-                     , attoparsec       >=0.12    && <0.14
-                     , hspec            >=2.2     && <3.0
-                     , hspec-attoparsec >=0.1.0.0 && <0.2
-                     , mtl              >=2.2.1   && <2.3
-                     , QuickCheck       >=2.8     && <3.0
-                     , text             >=1.2     && <1.3
-                     , hasmin
-
-test-suite doctest
-  default-language:    Haskell2010
-  type:                exitcode-stdio-1.0
-  hs-source-dirs:      tests
-  main-is:             DocTest.hs
-  ghc-options:         -threaded -Wall -fno-warn-orphans
-  build-depends:       base             >=4.9     && <5.1
-                     , doctest          >=0.11    && <0.14
-                     , doctest-discover >=0.1.0.0 && <0.2
-                     , hasmin
-
-benchmark bench
-  default-language:    Haskell2010
-  type:                exitcode-stdio-1.0
-  hs-source-dirs:      benchmarks
-  main-is:             Benchmarks.hs
-  ghc-options:         -threaded -Wall -fno-warn-orphans
-  build-depends:       base             >=4.9     && <5.1
-                     , criterion        >=0.11    && <1.3
-                     , directory        >=1.3.0.0 && <1.4
-                     , text             >=1.2     && <1.3
-                     , hasmin
+name:                hasmin+version:             1.0.2+license:             BSD3+license-file:        LICENSE+author:              (c) 2017 Cristian Adrián Ontivero <cristianontivero@gmail.com>+maintainer:          Cristian Adrián Ontivero <cristianontivero@gmail.com>+synopsis:            CSS Minifier+homepage:            https://github.com/contivero/hasmin#readme+bug-reports:         https://github.com/contivero/hasmin/issues+category:            Text+build-type:          Simple+extra-source-files:  README.md+                     CHANGELOG.md+cabal-version:       >=1.10+description:+    A CSS minifier which not only aims at reducing the amount of bytes of the+    output, but also at improving gzip compression. It may be used as a library,+    or a stand-alone executable. For the library, refer to the Hasmin module+    documentation. For the program: the output is the minified CSS file, but+    hasmin allows also its compression into gzip using Google's Zopfli library.+    .+    To use it: ./hasmin input.css > output.css+    .+    By default, most minification techniques are enabled. For a list of+    available flags, do: ./hasmin --help++source-repository head+  type: git+  location: https://github.com/contivero/hasmin.git++executable hasmin+  default-language:    Haskell2010+  ghc-options:         -O2 -Wall -fwarn-tabs -fwarn-unused-do-bind -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat -Wnoncanonical-monad-instances -Wnoncanonical-monadfail-instances -Wredundant-constraints -Werror=incomplete-patterns+  main-is:             Main.hs+  other-modules:       Paths_hasmin+  build-depends:       base                 >=4.10       && <5.0+                     , optparse-applicative >=0.11       && <0.15+                     , text                 >=1.2        && <1.3+                     , hopfli               >=0.2        && <0.4+                     , bytestring           >=0.10.2.0   && <0.11+                     , gitrev               >=1.0.0      && <1.4+                     , hasmin++library+  default-language:    Haskell2010+  hs-source-dirs:      src+  ghc-options:         -O2 -Wall -fwarn-tabs -fwarn-unused-do-bind -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat -Wnoncanonical-monad-instances -Wnoncanonical-monadfail-instances -Wredundant-constraints -Werror=incomplete-patterns+  exposed-modules:     Hasmin+                     , Hasmin.Config+                     , Hasmin.Class+                     , Hasmin.Types.Stylesheet+                     -- exposed because it is needed by tests:+                     , Hasmin.Parser.BasicShape+                     , Hasmin.Parser.BorderRadius+                     , Hasmin.Parser.Color+                     , Hasmin.Parser.Dimension+                     , Hasmin.Parser.Gradient+                     , Hasmin.Parser.Internal+                     , Hasmin.Parser.Numeric+                     , Hasmin.Parser.PercentageLength+                     , Hasmin.Parser.Position+                     , Hasmin.Parser.Primitives+                     , Hasmin.Parser.Selector+                     , Hasmin.Parser.String+                     , Hasmin.Parser.TimingFunction+                     , Hasmin.Parser.TransformFunction+                     , Hasmin.Parser.Value+                     , Hasmin.Types.BasicShape+                     , Hasmin.Types.BgSize+                     , Hasmin.Types.BorderRadius+                     , Hasmin.Types.Color+                     , Hasmin.Types.Declaration+                     , Hasmin.Types.Dimension+                     , Hasmin.Types.FilterFunction+                     , Hasmin.Types.Numeric+                     , Hasmin.Types.Position+                     , Hasmin.Types.RepeatStyle+                     , Hasmin.Types.String+                     , Hasmin.Types.TimingFunction+                     , Hasmin.Types.TransformFunction+                     , Hasmin.Types.Value+                     , Hasmin.Types.PercentageLength+                     , Hasmin.Utils+  other-modules:       Hasmin.Parser.Utils+                     , Hasmin.Properties+                     , Hasmin.Types.Selector+                     , Hasmin.Types.Gradient+                     , Hasmin.Types.Shadow+  build-depends:       base            >=4.10       && <5.0+                     , attoparsec      >=0.12       && <0.14+                     , containers      >=0.5        && <0.6+                     , matrix          >=0.3.4      && <0.4+                     , mtl             >=2.2.1      && <2.3+                     , numbers         >=3000.2.0.0 && <3000.3+                     , parsers         >=0.12.3     && <0.13+                     , text            >=1.2        && <1.3++test-suite spec+  default-language:    Haskell2010+  type:                exitcode-stdio-1.0+  hs-source-dirs:      tests+  main-is:             Spec.hs+  ghc-options:         -O2 -Wall -fwarn-tabs -fwarn-unused-do-bind -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat -Wnoncanonical-monad-instances -Wnoncanonical-monadfail-instances -Wredundant-constraints -Werror=incomplete-patterns+  default-extensions:  OverloadedStrings+  other-modules:       Hasmin.Parser.InternalSpec+                     , Hasmin.Parser.ValueSpec+                     , Hasmin.TestUtils+                     , Hasmin.Types.BasicShapeSpec+                     , Hasmin.Types.BgSizeSpec+                     , Hasmin.Types.ColorSpec+                     , Hasmin.Types.DeclarationSpec+                     , Hasmin.Types.DimensionSpec+                     , Hasmin.Types.FilterFunctionSpec+                     , Hasmin.Types.GradientSpec+                     , Hasmin.Types.PositionSpec+                     , Hasmin.Types.RepeatStyleSpec+                     , Hasmin.Types.SelectorSpec+                     , Hasmin.Types.ShadowSpec+                     , Hasmin.Types.StringSpec+                     , Hasmin.Types.StylesheetSpec+                     , Hasmin.Types.TimingFunctionSpec+                     , Hasmin.Types.TransformFunctionSpec+                     , Hasmin.Types.ValueSpec+  build-depends:       base                 >=4.10    && <5.0+                     , attoparsec           >=0.12    && <0.14+                     , hspec                >=2.2     && <3.0+                     , hspec-attoparsec     >=0.1.0.0 && <0.2+                     , mtl                  >=2.2.1   && <2.3+                     , QuickCheck           >=2.8     && <3.0+                     , quickcheck-instances >=0.3.16  && <0.3.19+                     , text                 >=1.2     && <1.3+                     , hasmin++test-suite doctest+  default-language:    Haskell2010+  type:                exitcode-stdio-1.0+  hs-source-dirs:      tests+  main-is:             DocTest.hs+  ghc-options:         -threaded -Wall -fno-warn-orphans+  build-depends:       base             >=4.10    && <5.0+                     , doctest          >=0.11    && <0.16+                     , doctest-discover >=0.1.0.0 && <0.2+                     , hasmin++benchmark bench+  default-language:    Haskell2010+  type:                exitcode-stdio-1.0+  hs-source-dirs:      benchmarks+  main-is:             Benchmarks.hs+  ghc-options:         -threaded -Wall -fno-warn-orphans+  build-depends:       base             >=4.10    && <5.0+                     , criterion        >=0.11    && <1.5+                     , directory        >=1.3.0.0 && <1.4+                     , text             >=1.2     && <1.3+                     , hasmin
+ src/Hasmin/Parser/BasicShape.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE OverloadedStrings #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Parser.BasicShape+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-- Parsers for CSS <https://www.w3.org/TR/css-shapes/#typedef-basic-shape \<basic-shape>> values.+--+-----------------------------------------------------------------------------+module Hasmin.Parser.BasicShape where++import Control.Applicative ((<|>),  optional, many)+import Data.Attoparsec.Text (Parser)+import qualified Data.Attoparsec.Text as A+import Data.List.NonEmpty (NonEmpty((:|)))++import Hasmin.Parser.Utils+import Hasmin.Parser.PercentageLength+import Hasmin.Parser.Position+import Hasmin.Parser.BorderRadius+import Hasmin.Types.BasicShape+import Hasmin.Utils++-- inset( <shape-arg>{1,4} [round <border-radius>]? )+inset :: Parser BasicShape+inset = do+  sa  <- percentageLength <* skipComments+  sas <- atMost 3 (percentageLength <* skipComments)+  br  <- optional (A.asciiCI "round" *> skipComments *> borderRadius)+  pure $ Inset (sa:|sas) br++       -- circle( [<shape-radius>]? [at <position>]? )+circle :: Parser BasicShape+circle =+    Circle <$> optional (shapeRadius <* skipComments)+           <*> optional (A.asciiCI "at" *> skipComments *> position)++-- ellipse( [<shape-radius>{2}]? [at <position>]? )+ellipse :: Parser BasicShape+ellipse = Ellipse <$> twoSR <*> optional atPosition+  where atPosition = lexeme (A.asciiCI "at") *> position+        twoSR      = A.option None $ do+            sr1 <- shapeRadius+            (Two sr1 <$> (skipComments *> shapeRadius)) <|> pure (One sr1)++-- polygon( [<fill-rule>,]? [<shape-arg> <shape-arg>]# )+polygon :: Parser BasicShape+polygon = Polygon <$> optional fillrule <*> shapeargs+  where shapeargs = (:|) <$> pairOf percentageLength+                         <*> many (comma *> pairOf percentageLength)+        fillrule  = parserFromPairs+                      [("nonzero", pure NonZero)+                      ,("evenodd", pure EvenOdd)] <* comma+        pairOf p = mzip (p <* skipComments) p++shapeRadius :: Parser ShapeRadius+shapeRadius = either SRPercentage SRLength <$> percentageLength+           <|> parserFromPairs [("closest-side", pure SRClosestSide)+                               ,("farthest-side", pure SRFarthestSide)]+
+ src/Hasmin/Parser/BorderRadius.hs view
@@ -0,0 +1,30 @@+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Parser.BorderRadius+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-- Parsers for CSS values.+--+-----------------------------------------------------------------------------+module Hasmin.Parser.BorderRadius+    ( borderRadius+    ) where++import Data.Attoparsec.Text (Parser)+import qualified Data.Attoparsec.Text as A+import Data.List.NonEmpty (NonEmpty((:|)))++import Hasmin.Parser.Utils+import Hasmin.Parser.PercentageLength+import Hasmin.Types.BorderRadius++-- <length-percentage>{1,4} [ / <length-percentage>{1,4} ]?+borderRadius :: Parser BorderRadius+borderRadius = do+    x  <- percentageLength <* skipComments+    xs <- atMost 3 (percentageLength <* skipComments)+    ys <- A.option [] $ A.char '/' *> atMost 4 (skipComments *> percentageLength)+    pure $ BorderRadius (x:|xs) ys
+ src/Hasmin/Parser/Color.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE OverloadedStrings #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Parser.Color+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-- Parsers for CSS \<color> values.+--+-----------------------------------------------------------------------------+module Hasmin.Parser.Color where++import Control.Applicative ((<|>),  optional)+import Control.Monad (mzero)+import Data.Attoparsec.Text (Parser)+import Data.Maybe (fromMaybe)+import qualified Data.Attoparsec.Text as A+import Data.Text (Text)+import qualified Data.Text as T+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map++import Hasmin.Parser.Utils+import Hasmin.Parser.Numeric+import Hasmin.Types.Color++hex :: Parser Color+hex = do+    _ <- A.char '#'+    a <- hexadecimal+    b <- hexadecimal+    c <- hexadecimal+    x <- optional hexadecimal+    case x of+      Nothing -> pure $ mkHex3 a b c+      Just d  -> do y <- optional hexadecimal+                    case y of+                      Nothing -> pure $ mkHex4 a b c d+                      Just e  -> do f <- hexadecimal+                                    z <- optional hexadecimal+                                    case z of+                                      Nothing -> pure $ mkHex6 [a,b] [c,d] [e,f]+                                      Just g  -> do h <- hexadecimal+                                                    pure $ mkHex8 [a,b] [c,d] [e,f] [g,h]++-- Assumes "rgb(" has already been read+rgb :: Parser Color+rgb = functionParser (rgbInt <|> rgbPer)+  where rgbInt = mkRGBInt <$> word8 <* comma <*> word8 <* comma <*> word8+        rgbPer = mkRGBPer <$> percentage <* comma+                          <*> percentage <* comma <*> percentage++-- Assumes "rgba(" has already been read+rgba :: Parser Color+rgba = functionParser (rgbaInt <|> rgbaPer)+  where rgbaInt = mkRGBAInt <$> word8 <* comma <*> word8 <* comma+                            <*> word8 <* comma <*> alphavalue+        rgbaPer = mkRGBAPer <$> percentage <* comma <*> percentage <* comma+                            <*> percentage <* comma <*> alphavalue++-- Assumes "hsl(" has already been read+hsl :: Parser Color+hsl = functionParser p+  where p = mkHSL <$> int <* comma <*> percentage <* comma <*> percentage++-- Assumes "hsla(" has already been read+hsla :: Parser Color+hsla = functionParser p+  where p = mkHSLA <$> int <* comma <*> percentage <* comma+                   <*> percentage <* comma <*> alphavalue++-- | Parser for <https://drafts.csswg.org/css-color-3/#colorunits \<color\>>.+color :: Parser Color+color = hex <|> othercolor+  where+    othercolor = do+      i <- ident+      let t = T.toLower i+      fromMaybe (colorFunctionParser t) (Map.lookup t namedColorsParsersMap)++namedColorsParsersMap :: Map Text (Parser Color)+namedColorsParsersMap = Map.fromList $ foldr f [] keywordColors+  where f x xs = let a = fst x+                 in (a, pure $ Named a) : xs++colorFunctionsParsers :: [(Text, Parser Color)]+colorFunctionsParsers =+          [("rgb",  rgb)+          ,("rgba", rgba)+          ,("hsl",  hsl)+          ,("hsla", hsla)+          ]++functionPar :: Map Text (Parser a) -> Text -> Parser a+functionPar m i = A.char '(' *> fromMaybe mzero (Map.lookup t m)+  where t = T.toLower i++colorFunctionParser :: Text -> Parser Color+colorFunctionParser = functionPar (Map.fromList colorFunctionsParsers)
+ src/Hasmin/Parser/Dimension.hs view
@@ -0,0 +1,70 @@+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Parser.Dimension+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-- Parsers for CSS dimensions, i.e. \<length>, \<time>, etc.+--+-----------------------------------------------------------------------------+module Hasmin.Parser.Dimension where++import Data.Attoparsec.Text (Parser)+import qualified Data.Attoparsec.Text as A+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import qualified Data.Char as C+import Data.Text (Text)+import qualified Data.Text as T+import Control.Monad (mzero)+import Control.Arrow ((&&&))++import Hasmin.Parser.Utils+import Hasmin.Parser.Numeric+import Hasmin.Types.Numeric+import Hasmin.Class+import Hasmin.Types.Dimension++distance :: Parser Length+distance = dimensionParser distanceConstructorsMap NullLength+  where distanceConstructorsMap = Map.fromList distanceConstructorsList++angle :: Parser Angle+angle = dimensionParser angleConstructorsMap NullAngle+  where angleConstructorsMap = Map.fromList angleConstructorsList++time :: Parser Time+time = do+    n <- number+    u <- opt (A.takeWhile1 C.isAlpha)+    if T.null u+       then mzero+       else case Map.lookup (T.toLower u) timeConstructorsMap of+              Just f  -> pure $ f n+              Nothing -> mzero -- parsed units aren't angle units, fail+  where timeConstructorsMap = Map.fromList timeConstructorsList++timeConstructorsList :: [(Text, Number -> Time)]+timeConstructorsList = fmap (toText &&& flip Time) [minBound..]++angleConstructorsList :: [(Text, Number -> Angle)]+angleConstructorsList = fmap (toText &&& flip Angle) [minBound..]++distanceConstructorsList :: [(Text, Number -> Length)]+distanceConstructorsList = fmap (toText &&& flip Length) [minBound..]++-- Create a numerical parser based on a Map.+-- See for instance, the "angle" parser+dimensionParser :: Map Text (Number -> a) -> a -> Parser a+dimensionParser m unitlessValue = do+    n <- number+    u <- opt (A.takeWhile1 C.isAlpha)+    if T.null u+       then if n == 0+               then pure unitlessValue -- <angle> 0, without units+               else mzero -- Non-zero <number>, fail+       else case Map.lookup (T.toCaseFold u) m of+              Just f  -> pure $ f n+              Nothing -> mzero -- parsed units aren't angle units, fail
+ src/Hasmin/Parser/Gradient.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Parser.Value+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-- Parsers for CSS \<gradient> values.+--+-----------------------------------------------------------------------------+module Hasmin.Parser.Gradient where++import Control.Applicative ((<|>), many, optional)+import Data.Functor (($>))+import Text.Parser.Permutation ((<|?>), (<$$>), (<$?>), (<||>), permute)+import Data.Maybe (isNothing)+import Data.Attoparsec.Text (Parser)+import qualified Data.Attoparsec.Text as A++import Hasmin.Parser.Utils+import Hasmin.Parser.Color+import Hasmin.Parser.Position+import Hasmin.Parser.Dimension+import Hasmin.Parser.PercentageLength+import Hasmin.Types.Gradient+import Hasmin.Utils++radialgradient :: Parser Gradient+radialgradient = functionParser $ do+    (def, c) <- A.option (True, RadialGradient Nothing Nothing) ((False,) <$> endingShapeAndSize <* skipComments)+    p  <- optional (A.asciiCI "at" *> skipComments *> position)+    _  <- if def && isNothing p+             then pure '*' -- do nothing+             else comma+    cs <- colorStopList+    pure $ c p cs+  where circle = A.asciiCI "circle" $> Just Circle <* skipComments+        ellipse = A.asciiCI "ellipse" $> Just Ellipse <* skipComments+        endingShapeAndSize = r1 <|> r2 <|> r3+          where r1 = permute (RadialGradient <$?> (Nothing, ellipse) <||> (Just <$> (PL <$> percentageLength <*> lexeme percentageLength)))+                r2 = permute (RadialGradient <$?> (Nothing, circle) <||> ((Just . SL) <$> distance <* skipComments))+                r3 = permute (RadialGradient <$?> (Nothing, circle <|> ellipse) <||> extentKeyword)+                   <|> permute (RadialGradient <$$> (circle <|> ellipse) <|?> (Nothing, extentKeyword))+                extentKeyword = Just <$>+                    parserFromPairs [("closest-corner",  pure ClosestCorner)+                                    ,("closest-side",    pure ClosestSide)+                                    ,("farthest-corner", pure FarthestCorner)+                                    ,("farthest-side",   pure FarthestSide)] <* skipComments++-- | Assumes "linear-gradient(", or one of its prefixed equivalents, has been parsed.+-- : [<angle>|to <side-or-corner> ,]? <color-stop> [, <color-stop>]++lineargradient :: Parser Gradient+lineargradient = functionParser (lg <|> oldLg)+  where lg = LinearGradient <$> optional angleOrSide <*> colorStopList+        oldLg = OldLinearGradient <$> optional ((ga <|> sc) <* comma)+                                  <*> colorStopList+        angleOrSide = (ga <|> gs) <* comma+        ga = Left <$> angle+        gs = A.asciiCI "to" *> skipComments *> sc+        sc = Right <$> sideOrCorner++-- <side-or-corner> = [left | right] || [top | bottom]+sideOrCorner :: Parser (Side, Maybe Side)+sideOrCorner = orderOne <|> orderTwo+  where orderOne = mzip (leftright <* skipComments) (optional topbottom)+        orderTwo = mzip (topbottom <* skipComments) (optional leftright)++        leftright :: Parser Side+        leftright =  parserFromPairs [("left", pure LeftSide), ("right", pure RightSide)]++        topbottom :: Parser Side+        topbottom = parserFromPairs [("top", pure TopSide), ("bottom", pure BottomSide)]++colorStopList :: Parser [ColorStop]+colorStopList = do+    c1 <- colorStop+    _  <- A.char ',' <* skipComments+    c2 <- colorStop+    cs <- many (A.char ',' *> skipComments *> colorStop)+    pure $ c1:c2:cs++colorStop :: Parser ColorStop+colorStop = ColorStop <$> color <* skipComments+        <*> optional (percentageLength <* skipComments)
src/Hasmin/Parser/Internal.hs view
@@ -21,204 +21,28 @@     , supportsCondition     ) where -import Control.Applicative ((<|>), many, some, empty, optional)+import Control.Applicative ((<|>), many, some, optional) import Data.Functor (($>))-import Data.Attoparsec.Combinator (lookAhead, sepBy, endOfInput)-import Data.Attoparsec.Text (asciiCI, char, many1, manyTill,-  option, Parser, satisfy, string)-import Data.List.NonEmpty (NonEmpty( (:|) ))+import Data.Attoparsec.Combinator (lookAhead, endOfInput)+import Data.Attoparsec.Text (asciiCI, char, manyTill,+  Parser, satisfy)+import Data.List.NonEmpty (NonEmpty) import Data.Monoid ((<>)) import Data.Maybe (fromMaybe) import Data.Text (Text)-import Data.Text.Lazy.Builder as LB-import Data.Map.Strict (Map) import qualified Data.List.NonEmpty as NE import qualified Data.Map.Strict as Map import qualified Data.Set as Set import qualified Data.Attoparsec.Text as A import qualified Data.Char as C import qualified Data.Text as T-import qualified Data.Text.Lazy as TL  import Hasmin.Parser.Utils import Hasmin.Parser.Value-import Hasmin.Types.Selector+import Hasmin.Parser.Selector import Hasmin.Types.Stylesheet import Hasmin.Types.Declaration-import Hasmin.Types.String --- | Parser for CSS complex selectors (see 'Selector' for more details).-selector :: Parser Selector-selector = Selector <$> compoundSelector <*> combinatorsAndSelectors-  where combinatorsAndSelectors = many ((,) <$> combinator <* skipComments  <*> compoundSelector)---- First tries with '>>' (descendant), '>' (child), '+' (adjacent sibling), and--- '~' (general sibling) combinators. If those fail, it tries with the--- descendant (whitespace) combinator. This is done to allow comments in-between.------ | Parser for selector combinators, i.e. ">>" (descendant), '>' (child), '+'--- (adjacent sibling), '~' (general sibling), and ' ' (descendant) combinators.-combinator :: Parser Combinator-combinator =  (skipComments *> ((string ">>" $> DescendantBrackets)-          <|> (char '>' $> Child)-          <|> (char '+' $> AdjacentSibling)-          <|> (char '~' $> GeneralSibling)))-          <|> (satisfy ws $> DescendantSpace)-  where ws c = c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'--compoundSelector :: Parser CompoundSelector-compoundSelector =-    (:|) <$> (typeSelector <|> universal)-                           <*> many p-                       <|> ((Universal mempty :|) <$> many1 p)-  where p = idSel <|> classSel <|> attributeSel <|> pseudo--- | Parses a \"number sign\" (U+0023, \#) immediately followed by the ID value,--- which must be a CSS identifier.-idSel :: Parser SimpleSelector-idSel = do-    _    <- char '#'-    name <- mconcat <$> many1 nmchar-    pure . IdSel . TL.toStrict $ toLazyText name---- class: '.' IDENT-classSel :: Parser SimpleSelector-classSel = char '.' *> (ClassSel <$> ident)--{--attrib-  : '[' S* [ namespace_prefix ]? IDENT S*-        [ [ PREFIXMATCH |-            SUFFIXMATCH |-            SUBSTRINGMATCH |-            '=' |-            INCLUDES |-            DASHMATCH ] S* [ IDENT | STRING ] S*-        ]? ']'-  ;--}--- FIXME namespace prefixes aren't allowed inside attribute selectors,--- but they should be.-attributeSel :: Parser SimpleSelector-attributeSel = do-    _     <- char '['-    attId <- lexeme ident-    g     <- option Attribute attValue-    _     <- char ']'-    pure $ AttributeSel (g attId)-  where attValue = do-          f <- ((string "^=" $> (:^=:)) <|>-                (string "$=" $> (:$=:)) <|>-                (string "*=" $> (:*=:)) <|>-                (string "="  $> (:=:))  <|>-                (string "~=" $> (:~=:)) <|>-                (string "|=" $> (:|=:))) <* skipComments-          attval <- identOrString <* skipComments-          pure (`f` attval)--identOrString :: Parser (Either Text StringType)-identOrString = (Left <$> ident) <|> (Right <$> stringtype)---- type_selector: [namespace_prefix]? element_name-typeSelector :: Parser SimpleSelector-typeSelector = Type <$> opt namespacePrefix <*> ident---- universal: [ namespace_prefix ]? '*'-universal :: Parser SimpleSelector-universal = Universal <$> opt namespacePrefix <* char '*'---- namespace_prefix: [ IDENT | '*' ]? '|'-namespacePrefix :: Parser Text-namespacePrefix = opt (ident <|> string "*") <* char '|'--{- '::' starts a pseudo-element, ':' a pseudo-class-   Exceptions: :first-line, :first-letter, :before and :after.-   Note that pseudo-elements are restricted to one per selector and-   occur only in the last simple_selector_sequence.--   <pseudo-class-selector> = ':' <ident-token> |-                             ':' <function-token> <any-value> ')'-   <pseudo-element-selector> = ':' <pseudo-class-selector>--}--- pseudo: ':' ':'? [ IDENT | functional_pseudo ]-pseudo :: Parser SimpleSelector-pseudo = char ':' *> (pseudoElementSelector <|> pseudoClassSelector)-  where pseudoClassSelector = do-            i <- ident-            c <- A.peekChar-            case c of-              Just '(' -> char '(' *> case Map.lookup (T.toCaseFold i) fpcMap of-                            Just p  -> functionParser p-                            Nothing -> functionParser (FunctionalPseudoClass i <$> A.takeWhile (/= ')'))-              _        -> pure $ PseudoClass i-        pseudoElementSelector =-            (char ':' *> (PseudoElem <$> ident)) <|> (ident >>= handleSpecialCase)-          where-            handleSpecialCase :: Text -> Parser SimpleSelector-            handleSpecialCase t-                | isSpecialPseudoElement = pure $ PseudoElem t-                | otherwise              = empty-              where isSpecialPseudoElement = T.toCaseFold t `elem` specialPseudoElements---- \<An+B> microsyntax parser.-anplusb :: Parser AnPlusB-anplusb = (asciiCI "even" $> Even)-      <|> (asciiCI "odd" $> Odd)-      <|> do-        s    <- optional parseSign-        dgts <- option mempty digits-        case dgts of-          [] -> ciN *> skipComments *> option (A s Nothing) (AB s Nothing <$> bValue)-          _  -> let n = read dgts :: Int-                in (ciN *> skipComments *> option (A s $ Just n) (AB s (Just n) <$> bValue))-                    <|> (pure . B $ getSign s * n)-  where ciN       = satisfy (\c -> c == 'N' || c == 'n')-        parseSign = (char '-' $> Minus) <|> (char '+' $> Plus)-        getSign (Just Minus) = -1-        getSign _            = 1-        bValue    = do-            readPlus <- (char '-' $> False) <|> (char '+' $> True)-            d        <- skipComments *> digits-            if readPlus-               then pure $ read d-               else pure $ read ('-':d)---- Functional pseudo classes parsers map-fpcMap :: Map Text (Parser SimpleSelector)-fpcMap = Map.fromList-    [buildTuple "nth-of-type"      (\x -> FunctionalPseudoClass2 x <$> anplusb)-    ,buildTuple "nth-last-of-type" (\x -> FunctionalPseudoClass2 x <$> anplusb)-    ,buildTuple "nth-column"       (\x -> FunctionalPseudoClass2 x <$> anplusb)-    ,buildTuple "nth-last-column"  (\x -> FunctionalPseudoClass2 x <$> anplusb)-    ,buildTuple "not"              (\x -> FunctionalPseudoClass1 x <$> compoundSelectorList)-    ,buildTuple "matches"          (\x -> FunctionalPseudoClass1 x <$> compoundSelectorList)-    ,buildTuple "nth-child"        (anbAndSelectors . FunctionalPseudoClass3)-    ,buildTuple "nth-last-child"   (anbAndSelectors . FunctionalPseudoClass3)-    ,buildTuple "lang"             (const (Lang <$> identOrString))-    ---    -- :drop( [ active || valid || invalid ]? )-    -- The :drop() functional pseudo-class is identical to :drop-    -- ,("drop", anplusb)-    ---    -- It accepts a comma-separated list of one or more language ranges as its-    -- argument. Each language range in :lang() must be a valid CSS <ident> or-    -- <string>.-    -- ,("lang", anplusb)-    ---    -- ,("dir", text)-    -- ,("has", relative selectors)-    ]-  where buildTuple t c = (t, c t)-        compoundSelectorList = (:) <$> compoundSelector <*> many (comma *> compoundSelector)-        anbAndSelectors constructor = do-            a <- anplusb <* skipComments-            o <- option [] (asciiCI "of" *> skipComments *> compoundSelectorList)-            pure $ constructor a o---- | Parse a list of comma-separated selectors, ignoring whitespace and--- comments.-selectors :: Parser [Selector]-selectors = lexeme selector `sepBy` char ','- -- | Parser for a declaration, starting by the property name. declaration :: Parser Declaration declaration = do@@ -239,10 +63,10 @@ -- | Used to parse the "!important" at the end of declarations, ignoring spaces -- and comments after the '!'. important :: Parser Bool-important = option False (char '!' *> skipComments *> asciiCI "important" $> True)+important = A.option False (char '!' *> skipComments *> asciiCI "important" $> True)  iehack :: Parser Bool-iehack = option False (string "\\9" $> True)+iehack = A.option False (A.string "\\9" $> True)  -- Note: The handleSemicolons outside is needed to handle parsing "h1 { ; }". --@@ -250,7 +74,7 @@ -- declarations (e.g. ; ;) declarations :: Parser [Declaration] declarations = many (declaration <* handleSemicolons) <* handleSemicolons-  where handleSemicolons = many (string ";" *> skipComments)+  where handleSemicolons = many (A.char ';' *> skipComments)  -- | Parser for CSS at-rules (e.g. \@keyframes, \@media) atRule :: Parser Rule@@ -278,7 +102,7 @@ atImport :: Parser Rule atImport = do     esu <- skipComments *> stringOrUrl-    mql <- option [] mediaQueryList+    mql <- A.option [] mediaQueryList     _   <- skipComments <* char ';'     pure $ AtImport esu mql @@ -290,7 +114,7 @@ -- <namespace-prefix> = IDENT atNamespace :: Parser Rule atNamespace = do-    i   <- skipComments *> option mempty ident+    i   <- skipComments *> A.option mempty ident     ret <- if T.null i               then (AtNamespace i . Left) <$> stringtype               else decideBasedOn i@@ -323,19 +147,19 @@  atMedia :: Parser Rule atMedia = do-  m <- satisfy C.isSpace *> mediaQueryList-  _ <- char '{' <* skipComments-  r <- manyTill (rule <* skipComments) (lookAhead (char '}'))-  _ <- char '}'-  pure $ AtMedia m r+    m <- satisfy C.isSpace *> mediaQueryList+    _ <- char '{' <* skipComments+    r <- manyTill (rule <* skipComments) (lookAhead (char '}'))+    _ <- char '}'+    pure $ AtMedia m r  atSupports :: Parser Rule atSupports = do-  sc <- satisfy C.isSpace *> supportsCondition-  _  <- lexeme (char '{')-  r  <- manyTill (rule <* skipComments) (lookAhead (char '}'))-  _ <- char '}'-  pure $ AtSupports sc r+    sc <- satisfy C.isSpace *> supportsCondition+    _  <- lexeme (char '{')+    r  <- manyTill (rule <* skipComments) (lookAhead (char '}'))+    _ <- char '}'+    pure $ AtSupports sc r  -- | Parser for a <https://drafts.csswg.org/css-conditional-3/#supports_condition supports_condition>, -- needed by @\@supports@ rules.@@ -406,7 +230,7 @@ -- and comments. stylesheet :: Parser [Rule] stylesheet = do-  charset    <- option [] ((:[]) <$> atCharset <* skipComments)+  charset    <- A.option [] ((:[]) <$> atCharset <* skipComments)   imports    <- many (atImport <* skipComments)   namespaces <- many (atNamespace <* skipComments)   _ <- skipComments -- if there is no charset, import, or namespace at rule we need this here.@@ -443,7 +267,7 @@         mediaType = lexeme ident         andExpressions = many (h *> expression)         h = lexeme (asciiCI "and" *> satisfy C.isSpace)-        optionalNotOrOnly = option mempty (asciiCI "not" <|> asciiCI "only")+        optionalNotOrOnly = A.option mempty (asciiCI "not" <|> asciiCI "only")  -- https://www.w3.org/TR/mediaqueries-4/#typedef-media-condition-without-or expression :: Parser Expression
+ src/Hasmin/Parser/Numeric.hs view
@@ -0,0 +1,51 @@+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Parser.Numeric+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-----------------------------------------------------------------------------+module Hasmin.Parser.Numeric+    ( alphavalue+    , rational+    , int+    , percentage+    , number+    ) where++import Control.Applicative ((<|>))+import Numeric (readSigned, readFloat)+import Data.Attoparsec.Text (Parser)+import qualified Data.Attoparsec.Text as A++import Hasmin.Types.Numeric+import Hasmin.Parser.Utils+import Hasmin.Parser.Primitives++-- | Parser for <https://www.w3.org/TR/css-values-3/#numbers \<number\>>.+number :: Parser Number+number = Number <$> rational++-- | Parser for <https://drafts.csswg.org/css-values-3/#percentages \<percentage\>>.+percentage :: Parser Percentage+percentage = Percentage <$> rational <* A.char '%'++alphavalue :: Parser Alphavalue+alphavalue = mkAlphavalue <$> rational++-- Note that many properties that allow an integer or real number as a value+-- actually restrict the value to some range, often to a non-negative value.+--+-- | Real number parser. \<number\>: <'int' integer> | [0-9]*.[0-9]++rational :: Parser Rational+rational = do+    sign <- A.option [] (wrapMinus <$> (A.char '-' <|> A.char '+'))+    dgts <- ((++) <$> digits <*> A.option "" fractionalPart)+           <|> ("0"++) <$> fractionalPart -- append a zero for read not to fail+    e <- A.option [] expo+    pure . fst . head $ readSigned readFloat (sign ++ dgts ++ e)+  where fractionalPart = (:) <$> A.char '.' <*> digits+        expo = (:) <$> A.satisfy (\c -> c == 'e' || c == 'E') <*> int'+        wrapMinus x = [x | x == '-'] -- we use this since read fails with leading '+'
+ src/Hasmin/Parser/PercentageLength.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE OverloadedStrings #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Parser.PercentageLength+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-----------------------------------------------------------------------------+module Hasmin.Parser.PercentageLength where++import Control.Applicative ((<|>))+import Control.Monad (mzero)+import Data.Attoparsec.Text (Parser)+import qualified Data.Attoparsec.Text as A+import qualified Data.Map.Strict as Map+import qualified Data.Char as C+import qualified Data.Text as T++import Hasmin.Parser.Utils+import Hasmin.Parser.Numeric+import Hasmin.Parser.Dimension+import Hasmin.Types.Numeric+import Hasmin.Types.Dimension+import Hasmin.Types.PercentageLength++percentageLength :: Parser PercentageLength+percentageLength = do+    n    <- number+    rest <- opt (A.string "%" <|> A.takeWhile1 C.isAlpha)+    if T.null rest  -- if true, then it was just a <number> value+       then if n == 0+               then pure $ Right NullLength+               else mzero+       else let r = T.toLower rest+            in case Map.lookup r plMap of+                 Just f  -> pure $ f n+                 Nothing -> mzero+  where plMap = Map.fromList $+            ("%", Left . toPercentage):(fmap . fmap) (Right .) distanceConstructorsList
+ src/Hasmin/Parser/Position.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE OverloadedStrings #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Parser.Position+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-- Parsers for CSS \<position> values.+--+-----------------------------------------------------------------------------+module Hasmin.Parser.Position where++import Control.Applicative ((<|>), optional)+import Data.Functor (($>))+import Data.Attoparsec.Text (Parser)+import qualified Data.Attoparsec.Text as A+import qualified Data.Text as T+import Control.Monad (mzero)++import Hasmin.Parser.Utils+import Hasmin.Parser.PercentageLength+import Hasmin.Types.Position+import Hasmin.Types.PercentageLength++-- | Parser for <https://drafts.csswg.org/css-values-3/#position \<position\>>.+position :: Parser Position+position = perLen <|> keyword+  where+    perLen = percentageLength >>= startsWithPL+    keyword =+        parserFromPairs [("left",   startsWith (Just PosLeft)   tb)+                        ,("right",  startsWith (Just PosRight)  tb)+                        ,("top",    startsWith (Just PosTop)    lr)+                        ,("bottom", startsWith (Just PosBottom) lr)+                        ,("center", startsWithCenter)]+    tb = (A.asciiCI "top"  $> Just PosTop,  A.asciiCI "bottom" $> Just PosBottom)+    lr = (A.asciiCI "left" $> Just PosLeft, A.asciiCI "right"  $> Just PosRight)++    startsWithPL :: PercentageLength -> Parser Position+    startsWithPL x = skipComments *>+        (followsWithPL <|> someKeyword <|> wasASinglePL)+      where+        pl = Just x+        followsWithPL = Position Nothing pl Nothing <$> (Just <$> percentageLength)+        wasASinglePL  = pure $ Position Nothing pl Nothing Nothing+        someKeyword   = do+            i <- ident+            case T.toCaseFold i of+              "center" -> pure $ Position Nothing pl (Just PosCenter) Nothing+              "top"    -> pure $ Position Nothing pl (Just PosTop) Nothing+              "bottom" -> pure $ Position Nothing pl (Just PosBottom) Nothing+              _        -> mzero++    maybePL :: Parser (Maybe PercentageLength)+    maybePL = optional percentageLength++    startsWithCenter :: Parser Position+    startsWithCenter = skipComments *>+        (followsWithPL <|> followsWithAKeyword <|> pure (posTillNow Nothing Nothing))+      where+        followsWithPL = (posTillNow Nothing . Just) <$> percentageLength+        followsWithAKeyword = do+            i <- ident <* skipComments+            let f x = posTillNow (Just x) <$> maybePL+            case T.toCaseFold i of+              "left"   -> f PosLeft+              "right"  -> f PosRight+              "top"    -> f PosTop+              "bottom" -> f PosBottom+              "center" -> pure $ posTillNow (Just PosCenter) Nothing+              _        -> mzero+        posTillNow = Position (Just PosCenter) Nothing++    -- Used for the cases when a position starts with the X axis (left and right+    -- keywords) or Y axis (top and bottom)+    startsWith :: Maybe PosKeyword+               -> (Parser (Maybe PosKeyword), Parser (Maybe PosKeyword))+               -> Parser Position+    startsWith x (p1, p2) = do+        _  <- skipComments+        pl <- optional (percentageLength <* skipComments)+        let endsWithCenter = Position x pl <$> center <*> pure Nothing+            endsWithKeywordAndMaybePL = Position x pl <$> posKeyword <*> maybePL+            endsWithPL = pure $ Position x Nothing Nothing pl+        endsWithCenter <|> endsWithKeywordAndMaybePL <|> endsWithPL+      where+        posKeyword = (p1 <|> p2)  <* skipComments+        center = A.asciiCI "center" $> Just PosCenter+
+ src/Hasmin/Parser/Primitives.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE OverloadedStrings #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Parser.Primitives+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-- Parsers for very basic CSS grammar tokens.+--+-----------------------------------------------------------------------------+module Hasmin.Parser.Primitives+    ( ident+    , escape+    , unicode+    , nmstart+    , nmchar+    , int+    , int'+    , digits+    ) where++import Control.Applicative ((<|>), some, many)+import Data.Attoparsec.Text (Parser, (<?>))+import Data.Monoid ((<>))+import Data.Text.Lazy.Builder (Builder)+import Data.Text (Text)+import qualified Data.Attoparsec.Text as A+import qualified Data.Char as C+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as B++-- TODO combine with unicode to make it more efficient+-- escape: {unicode}|\\[^\n\r\f0-9a-f]+escape :: Parser Builder+escape =  unicode+      <|> (mappend <$> (B.singleton <$> A.char '\\') <*> (B.singleton <$> A.satisfy cond))+      <?> "not an escape token: {unicode}|\\\\[^\\n\\r\\f0-9a-f]"+  where cond c = c /= '\n'+              && c /= '\r'+              && c /= '\f'+              && (not . C.isHexDigit) c++-- unicode        \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?+unicode :: Parser Builder+unicode = do+    backslash <- A.char '\\'+    hexChars  <- A.takeWhile1 C.isHexDigit+    _         <- A.option mempty (A.string "\r\n" <|> (T.singleton <$> A.satisfy ws))+    if T.length hexChars <= 6+       then pure $ B.singleton backslash <> B.fromText hexChars+       else fail "unicode escaped character with length greater than 6"+  where ws x = x == ' ' || x == '\n' || x == '\r' || x == '\t' || x == '\f'++-- ident: -?{nmstart}{nmchar}*+ident :: Parser Text+ident = do+    dash <- A.option mempty (B.singleton <$> A.char '-')+    ns   <- nmstart+    nc   <- mconcat <$> many nmchar+    pure $ TL.toStrict (B.toLazyText (dash <> ns <> nc))++-- nmstart: [_a-z]|{nonascii}|{escape}+nmstart :: Parser Builder+nmstart = B.singleton <$> A.satisfy (\c -> C.isAlpha c || (not . C.isAscii) c || c == '_')+       <|> escape+       <?> "not an nmstart token: [_a-z]|{nonascii}|{escape}"++-- nmchar: [_a-z0-9-]|{nonascii}|{escape}+nmchar :: Parser Builder+nmchar = B.singleton <$> A.satisfy cond <|> escape+  where cond x = C.isAlphaNum x || x == '_' || x == '-'+              || (not . C.isAscii) x++-- | \<integer\> data type parser, but into a String instead of an Int, for other+-- parsers to use (e.g.: see the parsers int, or rational)+int' :: Parser String+int' = do+  sign <- A.char '-' <|> pure '+'+  d    <- digits+  case sign of+    '+' -> pure d+    '-' -> pure (sign:d)+    _   -> error "int': parsed a number starting with other than [+|-]"++-- | Parser for \<integer\>: [+|-][0-9]++int :: Parser Int+int = read <$> int'++-- | Parser one or more digits.+digits :: Parser String+digits = some A.digit
+ src/Hasmin/Parser/Selector.hs view
@@ -0,0 +1,209 @@+{-# LANGUAGE OverloadedStrings #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Parser.Selector+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-----------------------------------------------------------------------------+module Hasmin.Parser.Selector+    ( selectors+    , selector+    ) where++import Control.Applicative ((<|>), many, some, empty, optional)+import Data.Attoparsec.Combinator (sepBy)+import Data.Attoparsec.Text (asciiCI, char, Parser, satisfy, string)+import qualified Data.Attoparsec.Text as A+import Data.Map.Strict (Map)+import qualified Data.Map.Strict as Map+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import Data.Functor (($>))+import qualified Data.Text.Lazy.Builder as LB+import Data.List.NonEmpty (NonEmpty( (:|) ))++import Hasmin.Parser.Utils+import Hasmin.Parser.String+import Hasmin.Utils++import Hasmin.Types.Selector+import Hasmin.Types.String++-- | Parser for CSS complex selectors (see 'Selector' for more details).+selector :: Parser Selector+selector = Selector <$> compoundSelector <*> combinatorsAndSelectors+  where combinatorsAndSelectors = many $ mzip (combinator <* skipComments) compoundSelector++-- First tries with '>>' (descendant), '>' (child), '+' (adjacent sibling), and+-- '~' (general sibling) combinators. If those fail, it tries with the+-- descendant (whitespace) combinator. This is done to allow comments in-between.+--+-- | Parser for selector combinators, i.e. ">>" (descendant), '>' (child), '+'+-- (adjacent sibling), '~' (general sibling), and ' ' (descendant) combinators.+combinator :: Parser Combinator+combinator =  (skipComments *> ((string ">>" $> DescendantBrackets)+          <|> (char '>' $> Child)+          <|> (char '+' $> AdjacentSibling)+          <|> (char '~' $> GeneralSibling)))+          <|> (satisfy ws $> DescendantSpace)+  where ws c = c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'++compoundSelector :: Parser CompoundSelector+compoundSelector = cs1 <|> cs2+  where cs1 = do+            sel  <- typeSelector <|> universal+            sels <- many p+            pure $ sel:|sels+        cs2 = (Universal mempty :|) <$> some p+        p = idSel <|> classSel <|> attributeSel <|> pseudo++-- | Parses a \"number sign\" (U+0023, \#) immediately followed by the ID value,+-- which must be a CSS identifier.+idSel :: Parser SimpleSelector+idSel = do+    _    <- char '#'+    name <- mconcat <$> some nmchar+    pure . IdSel . TL.toStrict $ LB.toLazyText name++-- class: '.' IDENT+classSel :: Parser SimpleSelector+classSel = char '.' *> (ClassSel <$> ident)++{-+attrib+  : '[' S* [ namespace_prefix ]? IDENT S*+        [ [ PREFIXMATCH |+            SUFFIXMATCH |+            SUBSTRINGMATCH |+            '=' |+            INCLUDES |+            DASHMATCH ] S* [ IDENT | STRING ] S*+        ]? ']'+  ;+-}+-- FIXME namespace prefixes aren't allowed inside attribute selectors,+-- but they should be.+attributeSel :: Parser SimpleSelector+attributeSel = do+    _     <- char '['+    attId <- lexeme ident+    g     <- A.option Attribute attValue+    _     <- char ']'+    pure $ AttributeSel (g attId)+  where attValue = do+          f <- ((string "^=" $> (:^=:)) <|>+                (string "$=" $> (:$=:)) <|>+                (string "*=" $> (:*=:)) <|>+                (string "="  $> (:=:))  <|>+                (string "~=" $> (:~=:)) <|>+                (string "|=" $> (:|=:))) <* skipComments+          attval <- identOrString <* skipComments+          pure (`f` attval)++-- type_selector: [namespace_prefix]? element_name+typeSelector :: Parser SimpleSelector+typeSelector = Type <$> opt namespacePrefix <*> ident++-- universal: [ namespace_prefix ]? '*'+universal :: Parser SimpleSelector+universal = Universal <$> opt namespacePrefix <* char '*'++-- namespace_prefix: [ IDENT | '*' ]? '|'+namespacePrefix :: Parser Text+namespacePrefix = opt (ident <|> string "*") <* char '|'++{- '::' starts a pseudo-element, ':' a pseudo-class+   Exceptions: :first-line, :first-letter, :before and :after.+   Note that pseudo-elements are restricted to one per selector and+   occur only in the last simple_selector_sequence.++   <pseudo-class-selector> = ':' <ident-token> |+                             ':' <function-token> <any-value> ')'+   <pseudo-element-selector> = ':' <pseudo-class-selector>+-}+-- pseudo: ':' ':'? [ IDENT | functional_pseudo ]+pseudo :: Parser SimpleSelector+pseudo = char ':' *> (pseudoElementSelector <|> pseudoClassSelector)+  where pseudoClassSelector = do+            i <- ident+            c <- A.peekChar+            case c of+              Just '(' -> char '(' *> case Map.lookup (T.toLower i) fpcMap of+                            Just p  -> functionParser p+                            Nothing -> functionParser (FunctionalPseudoClass i <$> A.takeWhile (/= ')'))+              _        -> pure $ PseudoClass i+        pseudoElementSelector =+            (char ':' *> (PseudoElem <$> ident)) <|> (ident >>= handleSpecialCase)+          where+            handleSpecialCase :: Text -> Parser SimpleSelector+            handleSpecialCase t+                | isSpecialPseudoElement = pure $ PseudoElem t+                | otherwise              = empty+              where isSpecialPseudoElement = T.toLower t `elem` specialPseudoElements++-- \<An+B> microsyntax parser.+anplusb :: Parser AnPlusB+anplusb = (asciiCI "even" $> Even)+      <|> (asciiCI "odd" $> Odd)+      <|> do+        s    <- optional parseSign+        dgts <- A.option mempty digits+        case dgts of+          [] -> ciN *> skipComments *> A.option (A s Nothing) (AB s Nothing <$> bValue)+          _  -> let n = read dgts :: Int+                in (ciN *> skipComments *> A.option (A s $ Just n) (AB s (Just n) <$> bValue))+                    <|> (pure . B $ getSign s * n)+  where ciN       = satisfy (\c -> c == 'N' || c == 'n')+        parseSign = (char '-' $> Minus) <|> (char '+' $> Plus)+        getSign (Just Minus) = -1+        getSign _            = 1+        bValue    = do+            readPlus <- (char '-' $> False) <|> (char '+' $> True)+            d        <- skipComments *> digits+            if readPlus+               then pure $ read d+               else pure $ read ('-':d)++-- Functional pseudo classes parsers map+fpcMap :: Map Text (Parser SimpleSelector)+fpcMap = Map.fromList+    [buildTuple "nth-of-type"      (\x -> FunctionalPseudoClass2 x <$> anplusb)+    ,buildTuple "nth-last-of-type" (\x -> FunctionalPseudoClass2 x <$> anplusb)+    ,buildTuple "nth-column"       (\x -> FunctionalPseudoClass2 x <$> anplusb)+    ,buildTuple "nth-last-column"  (\x -> FunctionalPseudoClass2 x <$> anplusb)+    ,buildTuple "not"              (\x -> FunctionalPseudoClass1 x <$> compoundSelectorList)+    ,buildTuple "matches"          (\x -> FunctionalPseudoClass1 x <$> compoundSelectorList)+    ,buildTuple "nth-child"        (anbAndSelectors . FunctionalPseudoClass3)+    ,buildTuple "nth-last-child"   (anbAndSelectors . FunctionalPseudoClass3)+    ,buildTuple "lang"             (const (Lang <$> identOrString))+    --+    -- :drop( [ active || valid || invalid ]? )+    -- The :drop() functional pseudo-class is identical to :drop+    -- ,("drop", anplusb)+    --+    -- It accepts a comma-separated list of one or more language ranges as its+    -- argument. Each language range in :lang() must be a valid CSS <ident> or+    -- <string>.+    -- ,("lang", anplusb)+    --+    -- ,("dir", text)+    -- ,("has", relative selectors)+    ]+  where buildTuple t c = (t, c t)+        compoundSelectorList = (:) <$> compoundSelector <*> many (comma *> compoundSelector)+        anbAndSelectors constructor = do+            a <- anplusb <* skipComments+            o <- A.option [] (asciiCI "of" *> skipComments *> compoundSelectorList)+            pure $ constructor a o++-- | Parse a list of comma-separated selectors, ignoring whitespace and+-- comments.+selectors :: Parser [Selector]+selectors = lexeme selector `sepBy` char ','++identOrString :: Parser (Either Text StringType)+identOrString = (Left <$> ident) <|> (Right <$> stringtype)
+ src/Hasmin/Parser/String.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE OverloadedStrings #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Parser.String+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-- Parsers for CSS \<string> values.+--+-----------------------------------------------------------------------------+module Hasmin.Parser.String+    ( convertEscaped+    , stringtype+    ) where++import Control.Applicative ((<|>))+import Data.Attoparsec.Text (Parser)+import Data.Functor (($>))+import qualified Data.Attoparsec.Text as A+import qualified Data.Text as T++import Hasmin.Types.String++-- <string> data type parser+stringtype :: Parser StringType+stringtype = doubleQuotes <|> singleQuotes+  where+    doubleQuotes :: Parser StringType+    doubleQuotes =  A.char '\"' *> (DoubleQuotes <$> untilDoubleQuotes)+      where untilDoubleQuotes = mappend <$> A.takeWhile (\c -> c /= '\\' && c /= '\"') <*> checkCharacter+            checkCharacter = (A.string "\"" $> mempty)+                          <|> (T.cons <$> A.char '\\' <*> untilDoubleQuotes)++    singleQuotes :: Parser StringType+    singleQuotes = A.char '\'' *> (SingleQuotes <$> untilSingleQuotes)+      where untilSingleQuotes = mappend <$> A.takeWhile (\c -> c /= '\\' && c /= '\'') <*> checkCharacter+            checkCharacter = (A.string "\'" $> mempty)+                          <|> (T.cons <$> A.char '\\' <*> untilSingleQuotes)
+ src/Hasmin/Parser/TimingFunction.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE OverloadedStrings #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Parser.TimingFunction+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-- Parsers for \<single-timing-function> values.+--+-----------------------------------------------------------------------------+module Hasmin.Parser.TimingFunction+    ( timingFunction+    , cubicbezier+    , steps+    ) where++import Control.Applicative (optional)+import Data.Attoparsec.Text (Parser)+import qualified Data.Attoparsec.Text as A++import Hasmin.Parser.Primitives+import Hasmin.Parser.Numeric+import Hasmin.Parser.Utils+import Hasmin.Types.TimingFunction++-- | Parser for <https://drafts.csswg.org/css-timing-1/#single-timing-function-production \<single-timing-function\>>.+timingFunction :: Parser TimingFunction+timingFunction = parserFromPairs [("ease",         pure Ease)+                                 ,("ease-in",      pure EaseIn)+                                 ,("ease-in-out",  pure EaseInOut)+                                 ,("ease-out",     pure EaseOut)+                                 ,("linear",       pure Linear)+                                 ,("step-end",     pure StepEnd)+                                 ,("step-start",   pure StepStart)+                                 ,("steps",        A.char '(' *> steps)+                                 ,("cubic-bezier", A.char '(' *> cubicbezier)]++-- | Parses what's between parenthesis in the "cubic-bezier()" function.+cubicbezier :: Parser TimingFunction+cubicbezier = functionParser $+    CubicBezier <$> number <* comma <*> number <* comma+                <*> number <* comma <*> number++-- | Parses what's between parenthesis in the "steps()" function.+steps :: Parser TimingFunction+steps = functionParser $ Steps <$> int <*> optional startOrEnd+  where startOrEnd = comma *> parserFromPairs [("end",   pure End)+                                              ,("start", pure Start)]
+ src/Hasmin/Parser/TransformFunction.hs view
@@ -0,0 +1,81 @@+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Parser.TransformFunction+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-- Parsers for CSS values.+--+-----------------------------------------------------------------------------+module Hasmin.Parser.TransformFunction+    ( matrix+    , matrix3d+    , rotate3d+    , scale+    , scale3d+    , skew+    , translate+    , translate3d+    ) where++import Control.Applicative (liftA3, optional)+import Data.Attoparsec.Text (count, Parser)++import Hasmin.Parser.Numeric+import Hasmin.Parser.Dimension+import Hasmin.Parser.PercentageLength+import Hasmin.Parser.Utils+import Hasmin.Types.TransformFunction++matrix :: Parser TransformFunction+matrix = functionParser $ do+    n  <-  number+    ns <-  count 5 (comma *> number)+    pure $ mkMat (n:ns)++matrix3d :: Parser TransformFunction+matrix3d = functionParser $ do+    n  <-  number+    ns <-  count 15 (comma *> number)+    pure $ mkMat3d (n:ns)++rotate3d :: Parser TransformFunction+rotate3d = functionParser $ do+    x  <-  number <* comma+    y  <-  number <* comma+    z  <-  number <* comma+    a  <-  angle+    pure $ Rotate3d x y z a++-- | Parser of scale() function. Assumes "scale(" has been already parsed+scale :: Parser TransformFunction+scale = functionParser $ do+    n  <- number+    mn <- optional (comma *> number)+    pure $ Scale n mn++scale3d :: Parser TransformFunction+scale3d = functionParser $ liftA3 Scale3d n n number+  where n = number <* comma++skew :: Parser TransformFunction+skew = functionParser $ do+    a  <- angle+    ma <- optional (comma *> angle)+    pure $ Skew a ma++-- | Assumes "translate(" has been already parsed+translate :: Parser TransformFunction+translate = functionParser $ do+    pl  <- percentageLength+    mpl <- optional (comma *> percentageLength)+    pure $ Translate pl mpl++translate3d :: Parser TransformFunction+translate3d = functionParser $+    Translate3d <$> percentageLength <* comma+                <*> percentageLength <* comma+                <*> distance+
src/Hasmin/Parser/Utils.hs view
@@ -10,8 +10,6 @@ ----------------------------------------------------------------------------- module Hasmin.Parser.Utils     ( ident-    , fontfamilyname-    , unquotedURL     , skipComments     , lexeme     , functionParser@@ -22,20 +20,25 @@     , opt     , nmchar     , hexadecimal+    , word8+    , parserFromPairs+    , atMost     ) where -import Control.Applicative ((<|>), many)+import Control.Applicative (liftA2, (<|>), many) import Control.Monad (void, mzero)-import Data.Attoparsec.Text (char, many1, digit,-  option, Parser, satisfy, skipSpace, string, takeWhile1, (<?>))-import Data.Monoid ((<>))+import Data.Attoparsec.Text (char,+  option, Parser, satisfy, skipSpace, string) import Data.Text (Text)-import Data.Text.Lazy.Builder as LB+import Data.Maybe (fromMaybe)+import qualified Data.Text as T+import Data.Word (Word8) import qualified Data.Attoparsec.Text as A import qualified Data.Char as C-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL+import qualified Data.Map.Strict as Map +import Hasmin.Parser.Primitives+ -- | Skip whatever comments and whitespaces are found. skipComments :: Parser () skipComments = void $ many (skipSpace *> comment) <* skipSpace@@ -63,81 +66,25 @@ opt :: Monoid m => Parser m -> Parser m opt = option mempty --- | Parse a URL without enclosing quotes.--- See <https://drafts.csswg.org/css-syntax-3/#consume-a-url-token §4.3.6. Consume a url token>-unquotedURL :: Parser Text-unquotedURL = do-    t <- many (escape <|> (LB.singleton <$> satisfy validChar))-    pure $ TL.toStrict (toLazyText (mconcat t))-  where validChar x = x /= '\"' && x /= '\'' && x /= '(' && x /= ')'-                   && x /= '\\' && notWhitespace x && notNonprintable x-        notWhitespace x = x /= '\n' &&  x /= '\t' && x /= ' '-        notNonprintable x = not (C.chr 0 <= x && x <= C.chr 8)-                         && x /= '\t'-                         && not ('\SO' <= x && x <= C.chr 31)-                         && x /= '\DEL'--fontfamilyname :: Parser Text-fontfamilyname = do-    i  <- ident-    is <- many (skipComments *> ident)-    if T.toLower i `elem` invalidNames-       then mzero-       else pure $ i <> foldMap (" "<>) is-  where invalidNames = ["serif", "sans-serif", "monospace", "cursive",-                        "fantasy", "inherit", "initial", "unset", "default"]---- ident: -?{nmstart}{nmchar}*-ident :: Parser Text-ident = do-    dash <- option mempty (LB.singleton <$> char '-')-    ns   <- nmstart-    nc   <- mconcat <$> many nmchar-    pure $ TL.toStrict (toLazyText (dash <> ns <> nc))---- nmstart: [_a-z]|{nonascii}|{escape}-nmstart :: Parser Builder-nmstart = LB.singleton <$> satisfy (\c -> C.isAlpha c || (not . C.isAscii) c || c == '_')-       <|> escape-       <?> "not an nmstart token: [_a-z]|{nonascii}|{escape}"---- nmchar: [_a-z0-9-]|{nonascii}|{escape}-nmchar :: Parser Builder-nmchar = LB.singleton <$> satisfy cond <|> escape-  where cond x = C.isAlphaNum x || x == '_' || x == '-'-              || (not . C.isAscii) x---- TODO combine with unicode to make it more efficient--- escape: {unicode}|\\[^\n\r\f0-9a-f]-escape :: Parser Builder-escape =  unicode-      <|> (mappend <$> (LB.singleton <$> char '\\') <*> (LB.singleton <$> satisfy cond))-      <?> "not an escape token: {unicode}|\\\\[^\\n\\r\\f0-9a-f]"-  where cond c = c /= '\n'-              && c /= '\r'-              && c /= '\f'-              && (not . C.isHexDigit) c---- unicode        \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?-unicode :: Parser Builder-unicode = do-    backslash <- char '\\'-    hexChars  <- takeWhile1 C.isHexDigit-    _         <- opt (string "\r\n" <|> (T.singleton <$> satisfy ws))-    if T.length hexChars <= 6-       then pure $ LB.singleton backslash <> LB.fromText hexChars-       else fail "unicode escaped character with length greater than 6"-  where ws x = x == ' ' || x == '\n' || x == '\r' || x == '\t' || x == '\f'- -- | Assumes the identifier and the left parenthesis have been parsed -- Parses p, ignoring surrounding whitespace and comments, and consumes the -- final right parenthesis. functionParser :: Parser a -> Parser a functionParser p = lexeme p <* char ')' --- | Parser one or more digits.-digits :: Parser String-digits = many1 digit- hexadecimal :: Parser Char hexadecimal = satisfy C.isHexDigit++word8 :: Parser Word8+word8 = read <$> digits++parserFromPairs :: [(Text, Parser a)] -> Parser a+parserFromPairs ls = do+    i <- ident+    let t = T.toLower i+    fromMaybe mzero (Map.lookup t m)+  where m = Map.fromList ls++atMost :: Int -> Parser a -> Parser [a]+atMost 0 _ = pure []+atMost n p = A.option [] $ liftA2 (:) p (atMost (n-1) p)
src/Hasmin/Parser/Value.hs view
@@ -20,7 +20,6 @@     , percentage     , url     , stringtype-    , textualvalue     , stringvalue    -- used in StringSpec     , shadowList     -- used in ShadowSpec     , timingFunction@@ -29,116 +28,57 @@     , color     , number     , fontStyle+    , textualvalue+    , borderRadius     ) where -import Control.Applicative ((<|>), many, liftA3, optional)-import Control.Arrow (first, (&&&))+import Control.Applicative ((<|>), many, optional)+import Control.Arrow (first) import Control.Monad (mzero) import Data.Functor (($>))-import Data.Attoparsec.Text (asciiCI, char, count, option, Parser, satisfy,-       skipSpace, string)-import Data.Map.Strict (Map) import Data.Monoid ((<>)) import Data.Maybe (fromMaybe, isNothing) import Data.Text (Text)-import Data.Word (Word8) import Data.Char (isAscii) import Text.Parser.Permutation ((<|?>), (<$$>), (<$?>), (<||>), permute)-import Numeric (readSigned, readFloat) import qualified Data.Set as Set+import Data.Attoparsec.Text (asciiCI, char, option, Parser, skipSpace, string) import qualified Data.Attoparsec.Text as A import qualified Data.Char as C+import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import qualified Data.List as L import qualified Data.Text as T +import Hasmin.Parser.BasicShape+import Hasmin.Parser.BorderRadius+import Hasmin.Parser.Color+import Hasmin.Parser.Dimension+import Hasmin.Parser.Gradient+import Hasmin.Parser.Numeric+import Hasmin.Parser.PercentageLength+import Hasmin.Parser.Position+import Hasmin.Parser.String+import Hasmin.Parser.TimingFunction+import Hasmin.Parser.TransformFunction import Hasmin.Parser.Utils import Hasmin.Types.BgSize-import Hasmin.Class-import Hasmin.Types.Color import Hasmin.Types.Dimension import Hasmin.Types.FilterFunction-import Hasmin.Types.Gradient import Hasmin.Types.Numeric-import Hasmin.Types.PercentageLength import Hasmin.Types.Position import Hasmin.Types.RepeatStyle import Hasmin.Types.Shadow import Hasmin.Types.String-import Hasmin.Types.TimingFunction import Hasmin.Types.TransformFunction import Hasmin.Types.Value+import Hasmin.Utils  -- | Given a propery name, it returns a specific parser of values for that -- property. Fails if no specific parser is found. valuesFor :: Text -> Parser Values-valuesFor propName =-  case Map.lookup (T.toLower propName) propertyValueParsersMap of-    Just x  -> x <* skipComments-    Nothing -> mzero---- | Parser for <https://www.w3.org/TR/css-values-3/#numbers \<number\>>.-number :: Parser Number-number = Number <$> rational---- ------------------------------------------------------------------------------ Color Parsers--- ------------------------------------------------------------------------------- Assumes "rgb(" has already been read-rgb :: Parser Color-rgb = functionParser (rgbInt <|> rgbPer)-  where rgbInt = mkRGBInt <$> word8 <* comma <*> word8 <* comma <*> word8-        rgbPer = mkRGBPer <$> percentage <* comma-                          <*> percentage <* comma <*> percentage---- Assumes "rgba(" has already been read-rgba :: Parser Color-rgba = functionParser (rgbaInt <|> rgbaPer)-  where rgbaInt = mkRGBAInt <$> word8 <* comma <*> word8 <* comma-                            <*> word8 <* comma <*> alphavalue-        rgbaPer = mkRGBAPer <$> percentage <* comma <*> percentage <* comma-                            <*> percentage <* comma <*> alphavalue---- Assumes "hsl(" has already been read-hsl :: Parser Color-hsl = functionParser p-  where p = mkHSL <$> int <* comma <*> percentage <* comma <*> percentage---- Assumes "hsla(" has already been read-hsla :: Parser Color-hsla = functionParser p-  where p = mkHSLA <$> int <* comma <*> percentage <* comma-                   <*> percentage <* comma <*> alphavalue--alphavalue :: Parser Alphavalue-alphavalue = mkAlphavalue <$> rational--hexvalue :: Parser Value-hexvalue = ColorV <$> hex--hex :: Parser Color-hex = do-    _ <- char '#'-    a <- hexadecimal-    b <- hexadecimal-    c <- hexadecimal-    x <- optional hexadecimal-    case x of-      Nothing -> pure $ mkHex3 a b c-      Just d  -> do y <- optional hexadecimal-                    case y of-                      Nothing -> pure $ mkHex4 a b c d-                      Just e  -> do f <- hexadecimal-                                    z <- optional hexadecimal-                                    case z of-                                      Nothing -> pure $ mkHex6 [a,b] [c,d] [e,f]-                                      Just g  -> do h <- hexadecimal-                                                    pure $ mkHex8 [a,b] [c,d] [e,f] [g,h]---- ------------------------------------------------------------------------------ Dimensions Parsers--- ---------------------------------------------------------------------------+valuesFor propName = maybe mzero (<* skipComments) parser+  where parser = Map.lookup (T.toLower propName) propertyValueParsersMap  -- A map relating dimension units and the percentage symbol, -- to functions that construct that value. Meant to unify all the numerical@@ -146,19 +86,17 @@ -- See numericalvalue. numericalConstructorsMap :: Map Text (Number -> Value) numericalConstructorsMap   = Map.fromList $ fmap (first T.toCaseFold) l-  where durationFunc u v   = DurationV (Duration v u)-        frequencyFunc u v  = FrequencyV (Frequency v u)+  where frequencyFunc u v  = FrequencyV (Frequency v u)         resolutionFunc u v = ResolutionV (Resolution v u)-        l = [("s",    durationFunc S)-            ,("ms",   durationFunc Ms)-            ,("hz",   frequencyFunc Hz)+        l = [("hz",   frequencyFunc Hz)             ,("khz",  frequencyFunc Khz)             ,("dpi",  resolutionFunc Dpi)             ,("dpcm", resolutionFunc Dpcm)             ,("dppx", resolutionFunc Dppx)             ,("%", \x -> PercentageV (Percentage $ toRational x))-            ] ++ (fmap . fmap) (LengthV .) distanceConstructorsList+            ] ++ (fmap . fmap) (TimeV .) timeConstructorsList               ++ (fmap . fmap) (AngleV .) angleConstructorsList+              ++ (fmap . fmap) (LengthV .) distanceConstructorsList  -- Unified numerical parser. -- Parses <number>, dimensions (i.e. <length>, <angle>, ...), and <percentage>@@ -172,88 +110,8 @@               Just f  -> pure $ f n               Nothing -> mzero -- TODO see if we should return an "Other" value --- Create a numerical parser based on a Map.--- See for instance, the "angle" parser-dimensionParser :: Map Text (Number -> a) -> a -> Parser a-dimensionParser m unitlessValue = do-    n <- number-    u <- opt (A.takeWhile1 C.isAlpha)-    if T.null u-       then if n == 0-               then pure unitlessValue -- <angle> 0, without units-               else mzero -- Non-zero <number>, fail-       else case Map.lookup (T.toCaseFold u) m of-              Just f  -> pure $ f n-              Nothing -> mzero -- parsed units aren't angle units, fail--distance :: Parser Length-distance = dimensionParser distanceConstructorsMap NullLength-  where distanceConstructorsMap = Map.fromList distanceConstructorsList--angle :: Parser Angle-angle = dimensionParser angleConstructorsMap NullAngle-  where angleConstructorsMap = Map.fromList angleConstructorsList--duration :: Parser Duration-duration = do-    n <- number-    u <- opt (A.takeWhile1 C.isAlpha)-    if T.null u-       then mzero-       else case Map.lookup (T.toCaseFold u) durationConstructorsMap of-              Just f  -> pure $ f n-              Nothing -> mzero -- parsed units aren't angle units, fail-  where durationConstructorsMap = Map.fromList $-            fmap (toText &&& flip Duration) [S, Ms]--angleConstructorsList :: [(Text, Number -> Angle)]-angleConstructorsList = fmap (toText &&& flip Angle)-    [Deg, Grad, Rad, Turn]--distanceConstructorsList :: [(Text, Number -> Length)]-distanceConstructorsList = fmap (toText &&& flip Length)-    [EM, EX, CH, VH, VW, VMIN, VMAX, REM, Q, CM, MM, IN, PC, PT, PX]---- | Parser for <https://drafts.csswg.org/css-values-3/#percentages \<percentage\>>.-percentage :: Parser Percentage-percentage = Percentage <$> rational <* char '%'---- ------------------------------------------------------------------------------ Primitives--- ------------------------------------------------------------------------------- | \<integer\> data type parser, but into a String instead of an Int, for other--- parsers to use (e.g.: see the parsers int, or rational)-int' :: Parser String-int' = do-  sign <- char '-' <|> pure '+'-  d    <- digits-  case sign of-    '+' -> pure d-    '-' -> pure (sign:d)-    _   -> error "int': parsed a number starting with other than [+|-]"---- | Parser for \<integer\>: [+|-][0-9]+-int :: Parser Int-int = read <$> int'--word8 :: Parser Word8-word8 = read <$> digits---- Note that many properties that allow an integer or real number as a value--- actually restrict the value to some range, often to a non-negative value.------ | Real number parser. \<number\>: <'int' integer> | [0-9]*.[0-9]+-rational :: Parser Rational-rational = do-    sign <- option [] (wrapMinus <$> (char '-' <|> char '+'))-    dgts <- ((++) <$> digits <*> option "" fractionalPart)-           <|> ("0"++) <$> fractionalPart -- append a zero for read not to fail-    e <- option [] expo-    pure . fst . head $ readSigned readFloat (sign ++ dgts ++ e)-  where fractionalPart = (:) <$> char '.' <*> digits-        expo = (:) <$> satisfy (\c -> c == 'e' || c == 'E') <*> int'-        wrapMinus x = [x | x == '-'] -- we use this since read fails with leading '+'+hexvalue :: Parser Value+hexvalue = ColorV <$> hex  -- | Parser for <https://drafts.csswg.org/css-fonts-3/#propdef-font-style \<font-style\>>, -- used in the @font-style@ and @font@ properties.@@ -289,76 +147,6 @@ positionvalue :: Parser Value positionvalue = PositionV <$> position --- | Parser for <https://drafts.csswg.org/css-values-3/#position \<position\>>.-position :: Parser Position-position = perLen <|> kword-  where-    perLen = percentageLength >>= startsWithPL-    kword = do-        i <- ident-        case Map.lookup (T.toCaseFold i) keywords of-          Just x  -> skipComments *> x-          Nothing -> mzero-    keywords = Map.fromList-        [("left",   startsWith (Just PosLeft)   tb)-        ,("right",  startsWith (Just PosRight)  tb)-        ,("top",    startsWith (Just PosTop)    lr)-        ,("bottom", startsWith (Just PosBottom) lr)-        ,("center", startsWithCenter)]-    tb = (asciiCI "top"  $> Just PosTop,  asciiCI "bottom" $> Just PosBottom)-    lr = (asciiCI "left" $> Just PosLeft, asciiCI "right"  $> Just PosRight)--    startsWithPL :: PercentageLength -> Parser Position-    startsWithPL x = skipComments *>-        (followsWithPL <|> someKeyword <|> wasASinglePL)-      where-        pl = Just x-        followsWithPL = Position Nothing pl Nothing <$> (Just <$> percentageLength)-        wasASinglePL  = pure $ Position Nothing pl Nothing Nothing-        someKeyword   = do-            i <- ident-            case T.toCaseFold i of-              "center" -> pure $ Position Nothing pl (Just PosCenter) Nothing-              "top"    -> pure $ Position Nothing pl (Just PosTop) Nothing-              "bottom" -> pure $ Position Nothing pl (Just PosBottom) Nothing-              _        -> mzero--    maybePL :: Parser (Maybe PercentageLength)-    maybePL = optional percentageLength--    startsWithCenter :: Parser Position-    startsWithCenter =  followsWithPL-                    <|> followsWithAKeyword-                    <|> pure (posTillNow Nothing Nothing)-      where-        followsWithPL = (posTillNow Nothing . Just) <$> percentageLength-        followsWithAKeyword = do-            i <- ident <* skipComments-            let f x = posTillNow (Just x) <$> maybePL-            case T.toCaseFold i of-              "left"   -> f PosLeft-              "right"  -> f PosRight-              "top"    -> f PosTop-              "bottom" -> f PosBottom-              "center" -> pure $ posTillNow (Just PosCenter) Nothing-              _        -> mzero-        posTillNow = Position (Just PosCenter) Nothing--    -- Used for the cases when a position starts with the X axis (left and right-    -- keywords) or Y axis (top and bottom)-    startsWith :: Maybe PosKeyword-               -> (Parser (Maybe PosKeyword), Parser (Maybe PosKeyword))-               -> Parser Position-    startsWith x (p1, p2) = do-        pl <- optional (percentageLength <* skipComments)-        let endsWithCenter = Position x pl <$> center <*> pure Nothing-            endsWithKeywordAndMaybePL = Position x pl <$> posKeyword <*> maybePL-            endsWithPL = pure $ Position x Nothing Nothing pl-        endsWithCenter <|> endsWithKeywordAndMaybePL <|> endsWithPL-      where-        posKeyword = (p1 <|> p2)  <* skipComments-        center = asciiCI "center" $> Just PosCenter- {- transformOrigin :: Parser Values transformOrigin = twoVal <|> oneVal@@ -386,9 +174,9 @@ -}  bgSize :: Parser BgSize-bgSize = twovaluesyntax <|> contain <|> cover-  where cover   = asciiCI "cover" $> Cover-        contain = asciiCI "contain" $> Contain+bgSize = twovaluesyntax <|> containOrCover+  where containOrCover = parserFromPairs [("cover", pure Cover)+                                         ,("contain", pure Contain)]         twovaluesyntax = do             x <- bgsizeValue <* skipComments             (BgSize2 x <$> bgsizeValue) <|> pure (BgSize1 x)@@ -455,7 +243,7 @@ -- used for the background property, which takes among other things: -- <position> [ / <bg-size> ]? positionAndBgSize :: Parser (Position, Maybe BgSize)-positionAndBgSize = (,) <$> position <*> optional (slash *> bgSize)+positionAndBgSize = mzip position (optional (slash *> bgSize))  matchKeywords :: [Text] -> Parser TextV matchKeywords listOfKeywords = do@@ -475,7 +263,7 @@                if Set.member lowercased possibilities                   then fromMaybe mzero (Map.lookup lowercased functionsMap)                   else mzero-  where possibilities = Set.fromList $ map T.toCaseFold+  where possibilities = Set.fromList             ["url", "element", "linear-gradient", "radial-gradient"]  transition :: Parser Values@@ -485,9 +273,9 @@ singleTransition :: Parser Value singleTransition = do     st <- permute (mkSingleTransition <$?> (Nothing, Just <$> singleTransitionProperty <* skipComments)-                                      <|?> (Nothing, Just <$> duration <* skipComments)+                                      <|?> (Nothing, Just <$> time <* skipComments)                                       <|?> (Nothing, Just <$> timingFunction <* skipComments)-                                      <|?> (Nothing, Just <$> duration <* skipComments))+                                      <|?> (Nothing, Just <$> time <* skipComments))     if singleTransitionIsEmpty st        then mzero        else pure st@@ -503,22 +291,6 @@                else pure $ TextV i         excludedKeywords = Set.fromList ["initial", "inherit", "unset", "default", "none"] --- | Parser for <https://drafts.csswg.org/css-timing-1/#single-timing-function-production \<single-timing-function\>>.-timingFunction :: Parser TimingFunction-timingFunction = do-    i <- ident-    fromMaybe mzero $ Map.lookup (T.toLower i) timingFunctionKeywords-  where timingFunctionKeywords = Map.fromList-          [("ease",         pure Ease)-          ,("ease-in",      pure EaseIn)-          ,("ease-in-out",  pure EaseInOut)-          ,("ease-out",     pure EaseOut)-          ,("linear",       pure Linear)-          ,("step-end",     pure StepEnd)-          ,("step-start",   pure StepStart)-          ,("steps",        char '(' *> steps)-          ,("cubic-bezier", char '(' *> cubicbezier)]- backgroundSize :: Parser Values backgroundSize = parseCommaSeparated (BgSizeV <$> bgSize) @@ -537,6 +309,7 @@     ,("background",                 background)     ,("background-repeat",          parseCommaSeparated (RepeatStyleV <$> repeatStyle))     ,("background-size",            backgroundSize)+    ,("border-radius",              singleValue (BorderRadiusV <$> borderRadius))     ,("box-shadow",                 shadowList)     ,("-o-box-shadow",              shadowList)     ,("-moz-box-shadow",            shadowList)@@ -631,11 +404,12 @@                            FontWeight -> pure $ storeProperty' j (mkOther i)                            _          -> pure $ storeProperty  j x (TextV i)               Nothing -> mzero-          where m  = Map.fromList $ zip ["ultra-condensed", "extra-condensed", "condensed", "semi-condensed", "semi-expanded", "expanded", "extra-expanded", "ultra-expanded"] (repeat FontStretch)-                                 ++ zip ["small-caps"] (repeat FontVariant)-                                 ++ zip ["italic", "oblique"] (repeat FontStyle)-                                 ++ zip ["bold", "bolder", "lighter"] (repeat FontWeight)-                                 ++ [("normal", Ambiguous)]+          where m =+                  Map.fromList $ zip ["ultra-condensed", "extra-condensed", "condensed", "semi-condensed", "semi-expanded", "expanded", "extra-expanded", "ultra-expanded"] (repeat FontStretch)+                              ++ zip ["small-caps"] (repeat FontVariant)+                              ++ zip ["italic", "oblique"] (repeat FontStyle)+                              ++ zip ["bold", "bolder", "lighter"] (repeat FontWeight)+                              ++ [("normal", Ambiguous)]         parse4 :: Parser (Maybe TextV, Maybe TextV, Maybe Value, Maybe TextV)         parse4 = do             let initialized = (Nothing, Nothing, Nothing, Nothing, 0)@@ -680,12 +454,12 @@  fontFamilyValues :: Parser Values fontFamilyValues = singleValue csswideKeyword <|> do-    v <- fontfamily-    vs <- many ((,) <$> separator <*> fontfamily)+    v  <- fontfamily+    vs <- many (mzip separator fontfamily)     pure $ Values v vs  fontfamily :: Parser Value-fontfamily = (StringV <$> stringtype) <|> (mkOther <$> unquotedFontFamily)+fontfamily = stringvalue <|> (mkOther <$> unquotedFontFamily)  local :: Parser Value local = functionParser $@@ -697,22 +471,11 @@     vs <- many (skipComments *> ident)     pure $ v <> foldMap (T.singleton ' ' <>) vs --textualvalue :: Parser Value-textualvalue = do-    i <- ident-    if i == "\\9" -- iehack-       then mzero-       else do c <- A.peekChar-               case c of-                 Just '(' -> functionParsers i-                 Just ':' -> mzero -- invalid-                 _        -> textualParsers i- textualParsers :: Text -> Parser Value textualParsers i = let t = T.toCaseFold i                    in fromMaybe (pure $ mkOther i) (Map.lookup t textualParsersMap)-  where textualParsersMap = Map.union csswideKeywordsMap namedColorsParsersMap+  where textualParsersMap = Map.union csswideKeywordsMap namedColorsValueParsersMap+        namedColorsValueParsersMap = (fmap . fmap) ColorV namedColorsParsersMap  csswideKeyword :: Parser Value csswideKeyword = do@@ -728,18 +491,18 @@                                   else mzero  csswideKeywordsMap :: Map Text (Parser Value)-csswideKeywordsMap  = Map.fromList $ map (first T.toCaseFold)-                                 [("initial", pure Initial)-                                 ,("inherit", pure Inherit)-                                 ,("unset",   pure Unset)]+csswideKeywordsMap  = Map.fromList+    [("initial", pure Initial)+    ,("inherit", pure Inherit)+    ,("unset",   pure Unset)]  stringvalue :: Parser Value stringvalue = StringV <$> stringtype  functionParsers :: Text -> Parser Value functionParsers i = char '(' *>-    case Map.lookup (T.toCaseFold i) functionsMap of-      Just x -> x <|> genericFunc i+    case Map.lookup (T.toLower i) functionsMap of+      Just x  -> x <|> genericFunc i       Nothing -> genericFunc i                  <|> (mkOther <$> (f i "(" <$> someText <*> string ")"))   where f x y z w = x <> y <> z <> w@@ -749,11 +512,11 @@ genericFunc i = (GenericFunc i <$> valuesInParens) <* char ')'  valuesInParens :: Parser Values-valuesInParens = Values <$> v <*> many ((,) <$> separator <*> v) <* skipComments+valuesInParens = Values <$> v <*> many (mzip separator v) <* skipComments  where v =  textualvalue          <|> numericalvalue          <|> hexvalue-         <|> (StringV <$> stringtype)+         <|> stringvalue  stringOrUrl :: Parser (Either StringType Url) stringOrUrl = (Left <$> stringtype) <|> (Right <$> someUrl)@@ -782,103 +545,95 @@                                     ,("round",     RsRound)]  functionsMap :: Map Text (Parser Value)-functionsMap = Map.fromList $ fmap (first T.toCaseFold)-          [("rgb",                     ColorV <$> rgb)-          ,("rgba",                    ColorV <$> rgba)-          ,("hsl",                     ColorV <$> hsl)-          ,("hsla",                    ColorV <$> hsla)-          ,("url",                     UrlV <$> url)-          ,("format",                  format)-          ,("local",                   local)-          -- <gradient> parsers-          ,("linear-gradient",         GradientV "linear-gradient" <$> lineargradient)-          ,("-o-linear-gradient",      GradientV "-o-linear-gradient" <$> lineargradient)-          ,("-ms-linear-gradient",     GradientV "-ms-linear-gradient" <$> lineargradient)-          ,("-moz-linear-gradient",    GradientV "-moz-linear-gradient" <$> lineargradient)-          ,("-webkit-linear-gradient", GradientV "-webkit-linear-gradient" <$> lineargradient)-          ,("radial-gradient",         GradientV "radial-gradient" <$> radialgradient)-          ,("-o-radial-gradient",      GradientV "-o-radial-gradient" <$> radialgradient)-          ,("-moz-radial-gradient",    GradientV "-moz-radial-gradient" <$> radialgradient)-          ,("-webkit-radial-gradient", GradientV "-webkit-radial-gradient" <$> radialgradient)-          --,("repeating-linear-gradient", ? ) -- TODO-          --,("repeating-radial-gradient", ? ) -- TODO-          -- <shape>-          ,("rect",                    rect)-          -- <transform-function>-          ,("matrix",                  TransformV <$> matrix)-          ,("matrix3d",                TransformV <$> matrix3d)-          ,("rotate",                  (TransformV . Rotate) <$> functionParser angle)-          ,("rotate3d",                TransformV <$> rotate3d)-          ,("rotateX",                 (TransformV . Rotate) <$> functionParser angle)-          ,("rotateY",                 (TransformV . Rotate) <$> functionParser angle)-          ,("rotateZ",                 (TransformV . Rotate) <$> functionParser angle)-          ,("scale",                   TransformV <$> scale)-          ,("scale3d",                 TransformV <$> scale3d)-          ,("scaleX",                  (TransformV . ScaleY) <$> functionParser number)-          ,("scaleY",                  (TransformV . ScaleY) <$> functionParser number)-          ,("scaleZ",                  (TransformV . ScaleZ) <$> functionParser number)-          ,("skew",                    TransformV <$> skew)-          ,("skewX",                   (TransformV . SkewX) <$> functionParser angle)-          ,("skewY",                   (TransformV . SkewY) <$> functionParser angle)-          ,("translate",               TransformV <$> translate)-          ,("translate3d",             TransformV <$> translate3d)-          ,("translateX",              (TransformV . TranslateX) <$> functionParser percentageLength)-          ,("translateY",              (TransformV . TranslateY) <$> functionParser percentageLength)-          ,("translateZ",              (TransformV . TranslateZ) <$> functionParser distance)-          ,("perspective",             (TransformV . Perspective) <$> functionParser distance)-          -- <timing-function>-          ,("cubic-bezier",            TimingFuncV <$> cubicbezier)-          ,("steps",                   TimingFuncV <$> steps)-          -- <filter-function>-          ,("blur",                    (FilterV . Blur) <$> functionParser distance)-          ,("contrast",                (FilterV . Contrast) <$> functionParser numberPercentage)-          ,("grayscale",               (FilterV . Grayscale) <$> functionParser numberPercentage)-          ,("invert",                  (FilterV . Invert) <$> functionParser numberPercentage)-          ,("opacity",                 (FilterV . Opacity) <$> functionParser numberPercentage)-          ,("saturate",                (FilterV . Saturate) <$> functionParser numberPercentage)-          ,("sepia",                   (FilterV . Sepia) <$> functionParser numberPercentage)-          ,("brightness",              (FilterV . Brightness) <$> functionParser numberPercentage)-          ,("drop-shadow",             FilterV <$> dropShadow)-          ,("hue-rotate",              (FilterV . HueRotate) <$> functionParser angle)-          ,("element", genericFunc "element")-          ---          -- <basic-shape>-          -- circle() = circle( [<shape-radius>]? [at <position>]? )-          -- polygon( [<fill-rule>,]? [<shape-arg> <shape-arg>]# )-          -- inset()-          -- ellipse( [<shape-radius>{2}]? [at <position>]? )-          ---          -- <image> https://drafts.csswg.org/css-images-          -- Note: <gradient> is a type of <image> !-          -- cross-fade()-          -- image()-          -- image-set()-          -- image-set()-          -- element()-          ---          ---          --,("stylistic", functionParser ident) -- IDENT-          --,("styleset", ? ) -- ident#-          --,("swash", ? ) -- ident-          --,("annotation", ? ) -- ident-          --,("attr", ? ) --  <attr-name> <type-or-unit>? [, <attr-fallback> ]?-          --,("calc", ? )  -- <calc-sum> , experimental-          --,("character-variant", ? ) -- ident#-          --,("element", ? )  -- id selector, experimental-          --,("local", ? )-          --,("ornaments", ? ) -- ident-          --,("symbols", ? )  --  <symbols-type>? [ <string> | <image> ]+-          --,("var", ? )  -- experimental: <custom-property-name> [, <declaration-value> ]?--- minmax()-          ]+functionsMap = Map.fromList (colorFunctionValueParsers ++ l)+  where colorFunctionValueParsers = (fmap . fmap . fmap) ColorV colorFunctionsParsers+        l = [("url",                     UrlV <$> url)+            ,("format",                  format)+            ,("local",                   local)+            -- <gradient> parsers+            ,("linear-gradient",         GradientV "linear-gradient" <$> lineargradient)+            ,("-o-linear-gradient",      GradientV "-o-linear-gradient" <$> lineargradient)+            ,("-ms-linear-gradient",     GradientV "-ms-linear-gradient" <$> lineargradient)+            ,("-moz-linear-gradient",    GradientV "-moz-linear-gradient" <$> lineargradient)+            ,("-webkit-linear-gradient", GradientV "-webkit-linear-gradient" <$> lineargradient)+            ,("radial-gradient",         GradientV "radial-gradient" <$> radialgradient)+            ,("-o-radial-gradient",      GradientV "-o-radial-gradient" <$> radialgradient)+            ,("-moz-radial-gradient",    GradientV "-moz-radial-gradient" <$> radialgradient)+            ,("-webkit-radial-gradient", GradientV "-webkit-radial-gradient" <$> radialgradient)+            --,("repeating-linear-gradient", ? ) -- TODO+            --,("repeating-radial-gradient", ? ) -- TODO+            -- <shape>+            ,("rect",                    rect)+            -- <transform-function>+            ,("matrix",                  TransformV <$> matrix)+            ,("matrix3d",                TransformV <$> matrix3d)+            ,("rotate",                  (TransformV . Rotate) <$> functionParser angle)+            ,("rotate3d",                TransformV <$> rotate3d)+            ,("rotatex",                 (TransformV . Rotate) <$> functionParser angle)+            ,("rotatey",                 (TransformV . Rotate) <$> functionParser angle)+            ,("rotatez",                 (TransformV . Rotate) <$> functionParser angle)+            ,("scale",                   TransformV <$> scale)+            ,("scale3d",                 TransformV <$> scale3d)+            ,("scalex",                  (TransformV . ScaleY) <$> functionParser number)+            ,("scaley",                  (TransformV . ScaleY) <$> functionParser number)+            ,("scalez",                  (TransformV . ScaleZ) <$> functionParser number)+            ,("skew",                    TransformV <$> skew)+            ,("skewx",                   (TransformV . SkewX) <$> functionParser angle)+            ,("skewy",                   (TransformV . SkewY) <$> functionParser angle)+            ,("translate",               TransformV <$> translate)+            ,("translate3d",             TransformV <$> translate3d)+            ,("translatex",              (TransformV . TranslateX) <$> functionParser percentageLength)+            ,("translatey",              (TransformV . TranslateY) <$> functionParser percentageLength)+            ,("translatez",              (TransformV . TranslateZ) <$> functionParser distance)+            ,("perspective",             (TransformV . Perspective) <$> functionParser distance)+            -- <timing-function>+            ,("cubic-bezier",            TimingFuncV <$> cubicbezier)+            ,("steps",                   TimingFuncV <$> steps)+            -- <filter-function>+            ,("blur",                    (FilterV . Blur) <$> functionParser distance)+            ,("contrast",                (FilterV . Contrast) <$> functionParser numberPercentage)+            ,("grayscale",               (FilterV . Grayscale) <$> functionParser numberPercentage)+            ,("invert",                  (FilterV . Invert) <$> functionParser numberPercentage)+            ,("opacity",                 (FilterV . Opacity) <$> functionParser numberPercentage)+            ,("saturate",                (FilterV . Saturate) <$> functionParser numberPercentage)+            ,("sepia",                   (FilterV . Sepia) <$> functionParser numberPercentage)+            ,("brightness",              (FilterV . Brightness) <$> functionParser numberPercentage)+            ,("drop-shadow",             FilterV <$> dropShadow)+            ,("hue-rotate",              (FilterV . HueRotate) <$> functionParser angle)+            ,("element",                 genericFunc "element")+            -- <basic-shape>+            ,("circle",                  BasicShapeV <$> functionParser circle)+            ,("ellipse",                 BasicShapeV <$> functionParser ellipse)+            ,("inset",                   BasicShapeV <$> functionParser inset)+            ,("polygon",                 BasicShapeV <$> functionParser polygon)+            --+            -- <image> https://drafts.csswg.org/css-images+            -- Note: <gradient> is a type of <image> !+            -- cross-fade()+            -- image()+            -- image-set()+            -- image-set()+            -- element()+            --+            --+            --,("stylistic", functionParser ident) -- IDENT+            --,("styleset", ? ) -- ident#+            --,("swash", ? ) -- ident+            --,("annotation", ? ) -- ident+            --,("attr", ? ) --  <attr-name> <type-or-unit>? [, <attr-fallback> ]?+            --,("calc", ? )  -- <calc-sum> , experimental+            --,("character-variant", ? ) -- ident#+            --,("element", ? )  -- id selector, experimental+            --,("ornaments", ? ) -- ident+            --,("symbols", ? )  --  <symbols-type>? [ <string> | <image> ]++            --,("var", ? )  -- experimental: <custom-property-name> [, <declaration-value> ]?+  -- minmax()+            ]  dropShadow :: Parser FilterFunction-dropShadow = functionParser $ do-    l1 <- distance-    l2 <- lexeme distance-    l3 <- optional (distance <* skipComments)-    c  <- optional color-    pure $ DropShadow l1 l2 l3 c+dropShadow = functionParser $+    DropShadow <$> len <*> len <*> optional len <*> optional color+  where len = distance <* skipComments  textShadow :: Parser Values textShadow = parseCommaSeparated shadowText@@ -904,7 +659,7 @@ parseCommaSeparated :: Parser Value -> Parser Values parseCommaSeparated p = do     v  <- p-    vs <- lexeme $ many ((,) <$> commaSeparator <*> p)+    vs <- lexeme $ many (mzip commaSeparator p)     c  <- A.peekChar     case c of       Just x  -> if x `elem` ['!', ';', '}']@@ -925,93 +680,6 @@             l4 <- optional (distance <* skipComments)             pure (l1,l2,l3,l4) -radialgradient :: Parser Gradient-radialgradient = functionParser $ do-    (def, c) <- option (True, RadialGradient Nothing Nothing) ((False,) <$> endingShapeAndSize <* skipComments)-    p  <- optional (asciiCI "at" *> skipComments *> position)-    _  <- if def && isNothing p-             then pure '*' -- do nothing-             else comma-    cs <- colorStopList-    pure $ c p cs-  where circle = asciiCI "circle" $> Just Circle <* skipComments-        ellipse = asciiCI "ellipse" $> Just Ellipse <* skipComments-        endingShapeAndSize = r1 <|> r2 <|> r3-          where r1 = permute (RadialGradient <$?> (Nothing, ellipse) <||> (Just <$> (PL <$> percentageLength <*> lexeme percentageLength)))-                r2 = permute (RadialGradient <$?> (Nothing, circle) <||> ((Just . SL) <$> distance <* skipComments))-                r3 = permute (RadialGradient <$?> (Nothing, circle <|> ellipse) <||> extentKeyword)-                   <|> permute (RadialGradient <$$> (circle <|> ellipse) <|?> (Nothing, extentKeyword))-                extentKeyword = do-                    i <- ident-                    _ <- skipComments-                    case Map.lookup i extentKeywords of-                      Just x -> pure (Just x)-                      Nothing -> mzero-                extentKeywords :: Map Text Size-                extentKeywords = Map.fromList [("closest-corner",  ClosestCorner)-                                              ,("closest-side",    ClosestSide)-                                              ,("farthest-corner", FarthestCorner)-                                              ,("farthest-side",   FarthestSide)]---- | Assumes "linear-gradient(", or one of its prefixed equivalents, has been parsed.--- : [<angle>|to <side-or-corner> ,]? <color-stop> [, <color-stop>]+-lineargradient :: Parser Gradient-lineargradient = functionParser (lg <|> oldLg)-  where lg = LinearGradient <$> optional angleOrSide <*> colorStopList-        oldLg = OldLinearGradient <$> optional ((ga <|> sc) <* comma)-                                  <*> colorStopList-        angleOrSide = (ga <|> gs) <* comma-        ga = Left <$> angle-        gs = asciiCI "to" *> skipComments *> sc-        sc = Right <$> sideOrCorner---- <side-or-corner> = [left | right] || [top | bottom]-sideOrCorner :: Parser (Side, Maybe Side)-sideOrCorner = orderOne <|> orderTwo-  where orderOne = (,) <$> leftright <* skipComments-                       <*> optional topbottom-        orderTwo = (,) <$> topbottom <* skipComments-                       <*> optional leftright--leftright :: Parser Side-leftright =  (asciiCI "left" $> LeftSide)-         <|> (asciiCI "right" $> RightSide)--topbottom :: Parser Side-topbottom =  (asciiCI "top" $> TopSide)-         <|> (asciiCI "bottom" $> BottomSide)--colorStopList :: Parser [ColorStop]-colorStopList = do-    c1 <- colorStop-    _  <- char ',' <* skipComments-    c2 <- colorStop-    cs <- many (char ',' *> skipComments *> colorStop)-    pure $ c1:c2:cs--colorStop :: Parser ColorStop-colorStop = ColorStop <$> color <* skipComments-        <*> optional (percentageLength <* skipComments)---- | Parser for <https://drafts.csswg.org/css-color-3/#colorunits \<color\>>.-color :: Parser Color-color = hex <|> othercolor-  where othercolor = do-            t <- textualvalue-            case t of-              ColorV c -> pure c-              _        -> mzero---- TODO make parser specifically for it instead of reusing numericalvalue-percentageLength :: Parser PercentageLength-percentageLength = do-    n <- numericalvalue-    case n of-      PercentageV p -> pure $ Left p-      NumberV 0     -> pure $ Right NullLength-      LengthV d     -> pure $ Right d-      _             -> mzero- numberPercentage :: Parser (Either Number Percentage) numberPercentage = do     n <- numericalvalue@@ -1022,78 +690,8 @@  -- | Assumes "rect(" has been already parsed rect :: Parser Value-rect = functionParser $ do-    length1 <- distance <* comma-    length2 <- distance <* comma-    length3 <- distance <* comma-    length4 <- distance-    pure $ Rect length1 length2 length3 length4---- | Assumes "translate(" has been already parsed-translate :: Parser TransformFunction-translate = functionParser $ do-    pl  <- percentageLength-    mpl <- optional (comma *> percentageLength)-    pure $ Translate pl mpl---- | Parser of scale() function. Assumes "scale(" has been already parsed-scale :: Parser TransformFunction-scale = functionParser $ do-    n  <- number-    mn <- optional (comma *> number)-    pure $ Scale n mn--scale3d :: Parser TransformFunction-scale3d = functionParser $ liftA3 Scale3d n n number-  where n = number <* comma--skew :: Parser TransformFunction-skew = functionParser $ do-    a  <- angle-    ma <- optional (comma *> angle)-    pure $ Skew a ma--translate3d :: Parser TransformFunction-translate3d = functionParser $-    Translate3d <$> percentageLength <* comma-                <*> percentageLength <* comma-                <*> distance--matrix :: Parser TransformFunction-matrix = functionParser $ do-    n  <-  number-    ns <-  count 5 (comma *> number)-    pure $ mkMat (n:ns)--matrix3d :: Parser TransformFunction-matrix3d = functionParser $ do-    n  <-  number-    ns <-  count 15 (comma *> number)-    pure $ mkMat3d (n:ns)--rotate3d :: Parser TransformFunction-rotate3d = functionParser $ do-    x  <-  number <* comma-    y  <-  number <* comma-    z  <-  number <* comma-    a  <-  angle-    pure $ Rotate3d x y z a--cubicbezier :: Parser TimingFunction-cubicbezier = functionParser $ do-    p0 <- number <* comma-    p1 <- number <* comma-    p2 <- number <* comma-    p3 <- number-    pure $ CubicBezier p0 p1 p2 p3--steps :: Parser TimingFunction-steps = functionParser $ do-    i <- int-    s <- optional (comma *> startOrEnd)-    pure $ Steps i s-  where startOrEnd = (asciiCI "end" $> End)-                 <|> (asciiCI "start" $> Start)+rect = functionParser (Rect <$> dc <*> dc <*> dc <*> distance)+  where dc = distance <* comma  -- It uses skipSpace instead of skipComments, since comments aren't valid inside -- the url-token. From the spec:@@ -1113,20 +711,15 @@ format = Format <$> functionParser p   where p = (:) <$> stringtype <*> many (comma *> stringtype) -namedColorsParsersMap :: Map Text (Parser Value)-namedColorsParsersMap = Map.fromList $ foldr f [] keywordColors-  where f x xs = let a = fst x-                 in (a, pure $ ColorV (Named a)) : xs- -- | For cases when CSS hacks are used, e.g.: @margin-top: 1px \\9;@. valuesFallback :: Parser Values-valuesFallback = Values <$> value <*> many ((,) <$> separator <*> value) <* skipComments+valuesFallback = Values <$> value <*> many (mzip separator value) <* skipComments  value :: Parser Value value =  textualvalue      <|> numericalvalue      <|> hexvalue-     <|> (StringV <$> stringtype)+     <|> stringvalue      <|> invalidvalue  separator :: Parser Separator@@ -1137,22 +730,6 @@ commaSeparator :: Parser Separator commaSeparator = lexeme (char ',' $> Comma) --- <string> data type parser-stringtype :: Parser StringType-stringtype = doubleQuotesString <|> singleQuotesString--doubleQuotesString :: Parser StringType-doubleQuotesString =  char '\"' *> (DoubleQuotes <$> untilDoubleQuotes)-  where untilDoubleQuotes = mappend <$> A.takeWhile (\c -> c /= '\\' && c /= '\"') <*> checkCharacter-        checkCharacter = (string "\"" $> mempty)-                      <|> (T.cons <$> char '\\' <*> untilDoubleQuotes)--singleQuotesString :: Parser StringType-singleQuotesString = char '\'' *> (SingleQuotes <$> untilSingleQuotes)-  where untilSingleQuotes = mappend <$> A.takeWhile (\c -> c /= '\\' && c /= '\'') <*> checkCharacter-        checkCharacter = (string "\'" $> mempty)-                      <|> (T.cons <$> char '\\' <*> untilSingleQuotes)- -- <single-animation># animation :: Parser Values animation = parseCommaSeparated singleAnimation@@ -1162,8 +739,8 @@ singleAnimation = do     sa <- permute (mkSingleAnimation <$?> (Nothing, Just <$> keyframesName <* skipComments)                                      <|?> (Nothing, Just <$> iterationCount <* skipComments)-                                     <|?> (Nothing, Just <$> duration <* skipComments)-                                     <|?> (Nothing, Just <$> duration <* skipComments)+                                     <|?> (Nothing, Just <$> time <* skipComments)+                                     <|?> (Nothing, Just <$> time <* skipComments)                                      <|?> (Nothing, Just <$> timingFunction <* skipComments)                                      <|?> (Nothing, Just <$> animationDirection <* skipComments)                                      <|?> (Nothing, Just <$> animationFillMode <* skipComments)@@ -1190,3 +767,14 @@   where s = Set.fromList ls  -- <single-animation-fill-mode> = none | forwards | backwards | both++textualvalue :: Parser Value+textualvalue = do+    i <- ident+    if i == "\\9" -- iehack+       then mzero+       else do c <- A.peekChar+               case c of+                 Just '(' -> functionParsers i+                 Just ':' -> mzero -- invalid+                 _        -> textualParsers i
src/Hasmin/Properties.hs view
@@ -130,6 +130,7 @@     ,("break-before",                       "auto",                     NonInherited, mempty, mempty)     ,("break-inside",                       "auto",                     NonInherited, mempty, mempty)     ,("caption-side",                       "top",                      Inherited, mempty, mempty)+    ,("caret-color",                        "auto",                     Inherited, mempty, mempty)     ,("clear",                              "none",                     NonInherited, mempty, mempty)     ,("clip",                               "auto",                     NonInherited, mempty, mempty) -- deprecated!     ,("color",                              mempty {-UA dependent-},    Inherited, mempty, mempty)@@ -161,21 +162,26 @@     ,("flex-shrink",                        "1",                        NonInherited, ["flex"], mempty)     ,("flex-wrap",                          "nowrap", NonInherited, ["flex-flow"], mempty)     ,("float",                              "none", NonInherited, mempty, mempty)-    ,("font",                               mempty {-shorthand-}, Inherited, mempty, ["font-style", "font-variant", "font-weight", "font-stretch", "font-size", "line-height", "font-family"])++    -- It seems it's neither inherited nor not inherited. It's not even a+    -- property, but an @font-face "descriptor".+    ,("font-display",                       "auto", NonInherited, mempty, mempty)+     ,("font-family",                        mempty {-UA dependent-}, Inherited, ["font"], mempty)     ,("font-feature-settings",              "normal", Inherited, mempty, mempty)     ,("font-kerning",                       "auto", Inherited, ["font"], mempty)     ,("font-language-override",             "normal", Inherited, ["font"], mempty)-    ,("font-size",                          "medium", Inherited, ["font"], mempty)+    ,("font",                               mempty {-shorthand-}, Inherited, mempty, ["font-style", "font-variant", "font-weight", "font-stretch", "font-size", "line-height", "font-family"])     ,("font-size-adjust",                   "none", Inherited, ["font"], mempty)+    ,("font-size",                          "medium", Inherited, ["font"], mempty)     ,("font-stretch",                       "normal", Inherited, ["font"], mempty)     ,("font-style",                         "normal", Inherited, ["font"], mempty)     ,("font-synthesis",                     "weight style", Inherited, mempty, mempty)-    ,("font-variant",                       "normal", Inherited, ["font"], mempty)     ,("font-variant-alternates",            "normal", Inherited, mempty, mempty)     ,("font-variant-caps",                  "normal", Inherited, mempty, mempty)     ,("font-variant-east-asian",            "normal", Inherited, mempty, mempty)     ,("font-variant-ligatures",             "normal", Inherited, mempty, mempty)+    ,("font-variant",                       "normal", Inherited, ["font"], mempty)     ,("font-variant-numeric",               "normal", Inherited, mempty, mempty)     ,("font-variant-position",              "normal", Inherited, mempty, mempty)     ,("font-weight",                        "normal", Inherited, ["font"], mempty)
+ src/Hasmin/Types/BasicShape.hs view
@@ -0,0 +1,198 @@+{-# LANGUAGE OverloadedStrings, FlexibleInstances, FlexibleContexts,+             StandaloneDeriving, DeriveFunctor, DeriveFoldable,+             DeriveTraversable #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Types.BasicShape+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-----------------------------------------------------------------------------+module Hasmin.Types.BasicShape+    ( BasicShape(..)+    , ShapeRadius(..)+    , AtMost2(..)+    , FillRule(..)+    ) where++import Control.Monad.Reader (Reader)+import Data.Monoid ((<>), mempty)+import Data.Bitraversable (bitraverse)+import Data.Maybe (isJust)+import Data.List.NonEmpty (NonEmpty((:|)))+import qualified Data.List.NonEmpty as NE+import Data.Text.Lazy.Builder (Builder)++import Hasmin.Types.Position+import Hasmin.Types.BorderRadius+import Hasmin.Types.Dimension+import Hasmin.Types.PercentageLength+import Hasmin.Types.Numeric+import Hasmin.Config+import Hasmin.Class+import Hasmin.Utils++type ShapeArg = PercentageLength++-- | CSS <https://drafts.csswg.org/css-shapes/#basic-shape-functions \<basic-shape\>> data type.+data BasicShape+       -- inset( <shape-arg>{1,4} [round <border-radius>]? )+        = Inset (NonEmpty ShapeArg) (Maybe BorderRadius)+       -- circle( [<shape-radius>]? [at <position>]? )+        | Circle (Maybe ShapeRadius) (Maybe Position)+       -- ellipse( [<shape-radius>{2}]? [at <position>]? )+        | Ellipse (AtMost2 ShapeRadius) (Maybe Position)+       -- polygon( [<fill-rule>,]? [<shape-arg> <shape-arg>]# )+        | Polygon (Maybe FillRule) (NonEmpty (ShapeArg, ShapeArg))+  deriving Show++instance Eq BasicShape where+    Inset sas1 mbr1 == Inset sas2 mbr2     = eqUsing sasEq sas1 sas2 && mbrEq mbr1 mbr2+    Circle msr1 mp1 == Circle msr2 mp2     = msrEq msr1 msr2 && mpEq mp1 mp2+    Ellipse sr2 mp1 == Ellipse sr2' mp2    = sr2Eq sr2 sr2' && mpEq mp1 mp2+    Polygon mfr1 sas1 == Polygon mfr2 sas2 = mfrEq mfr1 mfr2 && eqUsing pairEq sas1 sas2+    _ == _                                 = False++eqUsing :: (a -> a -> Bool) -> NonEmpty a -> NonEmpty a -> Bool+eqUsing f (x:|xs) (y:|ys) = f x y && go xs ys+  where go [] []    = True+        go (_:_) [] = False+        go [] (_:_) = False+        go (c:cs) (d:ds) = f c d && go cs ds++pairEq :: (Num a, Eq a) => (Either a Length, Either a Length)+                        -> (Either a Length, Either a Length) -> Bool+pairEq (a1, a2) (b1, b2) = a1 `sasEq` b1 && a2 `sasEq` b2++sasEq :: (Num a, Eq a) => Either a Length -> Either a Length -> Bool+sasEq a b = isZero a && isZero b || a == b++mfrEq :: Maybe FillRule -> Maybe FillRule -> Bool+mfrEq Nothing (Just NonZero) = True+mfrEq (Just NonZero) Nothing = True+mfrEq x y                    = x == y++sr2Eq :: AtMost2 ShapeRadius -> AtMost2 ShapeRadius -> Bool+sr2Eq None x =+    case x of+      One SRClosestSide               -> True+      Two SRClosestSide SRClosestSide -> True+      None                            -> True+      _                               -> False+sr2Eq (One SRClosestSide) None               = True+sr2Eq (One x) (Two y SRClosestSide)          = x == y+sr2Eq (One x) (One y)                        = x == y+sr2Eq One{} _                                = False+sr2Eq (Two x SRClosestSide) (One y)          = x == y+sr2Eq (Two SRClosestSide SRClosestSide) None = True+sr2Eq (Two a b) (Two c d)                    = a == c && b == d+sr2Eq Two{} _                                = False++msrEq :: Maybe ShapeRadius -> Maybe ShapeRadius -> Bool+msrEq Nothing (Just SRClosestSide) = True+msrEq (Just SRClosestSide) Nothing = True+msrEq x y                          = x == y++mbrEq :: Maybe BorderRadius -> Maybe BorderRadius -> Bool+mbrEq Nothing y = maybe True isZeroBR y+mbrEq x Nothing = mbrEq Nothing x+mbrEq x y       = x == y++mpEq :: Maybe Position -> Maybe Position -> Bool+mpEq Nothing (Just x) = x == centerpos+mpEq (Just x) Nothing = x == centerpos+mpEq x y              = x == y++data ShapeRadius = SRLength Length+                 | SRPercentage Percentage+                 | SRClosestSide+                 | SRFarthestSide+  deriving (Show, Eq)++instance ToText ShapeRadius where+  toBuilder (SRLength l)     = toBuilder l+  toBuilder (SRPercentage p) = toBuilder p+  toBuilder SRClosestSide    = "closest-side"+  toBuilder SRFarthestSide   = "farthest-side"++minifySR :: ShapeRadius -> Reader Config ShapeRadius+minifySR (SRLength l) = SRLength <$> minify l+minifySR sr           = pure sr++data FillRule = NonZero | EvenOdd+  deriving (Show, Eq)++data AtMost2 a = None | One a | Two a a+  deriving (Functor, Foldable, Traversable)++deriving instance Show a => Show (AtMost2 a)+deriving instance Eq a => Eq (AtMost2 a)++instance ToText FillRule where+  toBuilder NonZero = "nonzero"+  toBuilder EvenOdd = "evenodd"++instance Minifiable BasicShape where+  minify (Inset xs Nothing) = pure $ Inset (reduceTRBL xs) Nothing+  minify (Inset xs (Just br)) = Inset (reduceTRBL xs) <$> br'+    where br' = do+              x <- minify br+              pure $ if isZeroBR x+                        then Nothing+                        else Just x+  minify (Circle msr mp) = do+      mp' <- traverse minify mp+      let newPos = if mp' == Just centerpos then Nothing else mp'+      Circle <$> minifyMSR msr <*> pure newPos+    where minifyMSR :: Maybe ShapeRadius -> Reader Config (Maybe ShapeRadius)+          minifyMSR Nothing   = pure Nothing+          minifyMSR (Just sr) =+              case sr of+                SRLength l    -> (Just . SRLength) <$> minify l+                SRClosestSide -> pure Nothing+                _             -> pure (Just sr)+  minify (Ellipse sr2 mp) = do+      sr' <- minifySR2 sr2+      mp' <- traverse minify mp+      let newPos = if mp' == Just centerpos+                      then Nothing+                      else mp'+      pure $ Ellipse sr' newPos+    where minifySR2 (One x) =+              case x of+                SRClosestSide -> pure None+                SRLength l    -> (One . SRLength) <$> minify l+                _             -> pure (One x)+          minifySR2 (Two x SRClosestSide) = minifySR2 (One x)+          minifySR2 t@Two{}               = traverse minifySR t+          minifySR2 None                  = pure None+  minify (Polygon mfr mp) =+      case mfr of+        Just NonZero -> Polygon Nothing <$> mp'+        _            -> Polygon mfr <$> mp'+    where mp' = traverse (bitraverse minifyPL minifyPL) mp++instance ToText BasicShape where+  toBuilder (Inset xs mys) = surround "inset" $ mconcatIntersperse toBuilder " " (NE.toList xs) <> mys'+    where mys' = maybe mempty (\x -> " round " <> toBuilder x) mys+  toBuilder (Circle msr mp) = surround "circle" $ msr' <> ms <> mp'+    where msr' = maybe mempty toBuilder msr+          mp'  = maybe mempty (\x -> "at " <> toBuilder x) mp+          ms   = if isJust msr && isJust mp then " " else mempty+  toBuilder (Ellipse m2sr mp) = surround "ellipse" $ bsr2 <> ms <> mp'+    where ms   = if bsr2 == mempty || mp' == mempty then mempty else " "+          mp'  = maybe mempty (\x -> "at " <> toBuilder x) mp+          bsr2 =+              case m2sr of+                One rx    -> toBuilder rx+                Two rx ry -> toBuilder rx <> " " <> toBuilder ry+                None      -> mempty+  toBuilder (Polygon mfr xys) = surround "polygon" $ f mfr xys+    where f Nothing xys'   = mconcatIntersperse g "," (NE.toList xys')+          f (Just fr) xys' = toBuilder fr <> "," <> mconcatIntersperse g "," (NE.toList xys')+          g (x, y) = toBuilder x <> " " <> toBuilder y++surround :: Builder -> Builder -> Builder+surround func x = func <> "(" <> x <> ")"
+ src/Hasmin/Types/BorderRadius.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE OverloadedStrings #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Hasmin.Types.BorderRadius+-- Copyright   : (c) 2017 Cristian Adrián Ontivero+-- License     : BSD3+-- Stability   : experimental+-- Portability : unknown+--+-----------------------------------------------------------------------------+module Hasmin.Types.BorderRadius+    ( BorderRadius(..)+    , isZeroBR+    ) where++import Data.List.NonEmpty (NonEmpty((:|)))+import qualified Data.List.NonEmpty as NE+import Data.Monoid ((<>))+import qualified Data.Text.Lazy.Builder as B++import Hasmin.Class+import Hasmin.Types.PercentageLength+import Hasmin.Utils+                                -- <length-percentage>{1,4} [ / <length-percentage>{1,4} ]?+data BorderRadius = BorderRadius (NonEmpty PercentageLength) [PercentageLength]+  deriving Show++instance Eq BorderRadius where+  BorderRadius xs1 ys1 == BorderRadius xs2 ys2 =+      minifyBorderRadius xs1 ys1 `eq` minifyBorderRadius xs2 ys2+    where (BorderRadius as1 bs1) `eq` (BorderRadius as2 bs2) = as1 == as2 && bs1 == bs2++instance Minifiable BorderRadius where+  minify (BorderRadius xs ys) =+      minifyBorderRadius <$> traverse minifyPL xs <*> traverse minifyPL ys++minifyBorderRadius :: NonEmpty PercentageLength -> [PercentageLength] -> BorderRadius+minifyBorderRadius as [] = BorderRadius (reduceTRBL as) []+minifyBorderRadius as bs+    | l1 == l2  = BorderRadius l1 []+    | otherwise = BorderRadius l1 (NE.toList l2)+  where l1 = reduceTRBL as+        l2 = reduceTRBL (NE.fromList bs)++instance ToText BorderRadius where+  toBuilder (BorderRadius (h:|ts) xs) = toBuilder h <> f ' ' ts <> f '/' xs+    where f _ [] = mempty+          f i ys = B.singleton i <> mconcatIntersperse toBuilder " " ys++isZeroBR :: BorderRadius -> Bool+isZeroBR (BorderRadius (pl:|[]) []) = isZero pl+isZeroBR _                          = False
src/Hasmin/Types/Color.hs view
@@ -31,14 +31,15 @@ import Data.Char (isHexDigit, digitToInt, intToDigit, toLower) import Data.Maybe (fromMaybe) import Data.Ratio ((%))-import Data.Text (pack, Text)-import Data.Text.Lazy.Builder (Builder, singleton, fromText)+import Data.Text (Text)+import Data.Text.Lazy.Builder (Builder, singleton)+import qualified Data.Text.Lazy.Builder as B import Data.Word (Word8) import qualified Data.Map.Strict as Map import qualified Data.Text as T -import Hasmin.Config import Hasmin.Class+import Hasmin.Config import Hasmin.Types.Numeric import Hasmin.Utils @@ -70,12 +71,8 @@   (Hex6 r1 g1 b1) == (Hex8 r2 g2 b2 a)     | a == "ff" = r1 == r2 && g1 == g2 && b1 == b2     | otherwise = False-  c1 == (Named s) = case Map.lookup (T.toLower s) colorMap of-                      Just a  -> a == c1-                      Nothing -> False-  (Named s) == c2 = case Map.lookup (T.toLower s) colorMap of-                      Just a  -> a == c2-                      Nothing -> False+  c1 == (Named s) = Map.lookup (T.toLower s) colorMap == Just c1+  (Named s) == c2 = c2 == Named s   a == b = toLongHex a == toLongHex b  instance Ord Color where@@ -128,14 +125,14 @@     where values = toBuilderWithCommas [toText h, toText s, toText l]   toBuilder (HSLA h s l a)    = "hsla(" <> values <> singleton ')'     where values = toBuilderWithCommas [toText h, toText s, toText l, toText a]-  toBuilder (Named a)         = fromText a+  toBuilder (Named a)         = B.fromText a   toBuilder (Hex3 r g b)      = singleton '#' <> singleton r <> singleton g <> singleton b   toBuilder (Hex4 r g b a)    = singleton '#' <> singleton r <> singleton g <> singleton b <> singleton a-  toBuilder (Hex6 r g b)      = fromText . pack $ mconcat ["#", r, g, b]-  toBuilder (Hex8 r g b a)    = fromText . pack $ mconcat ["#", r, g, b, a]+  toBuilder (Hex6 r g b)      = B.fromString $ mconcat ["#", r, g, b]+  toBuilder (Hex8 r g b a)    = B.fromString $ mconcat ["#", r, g, b, a]  toBuilderWithCommas :: [Text] -> Builder-toBuilderWithCommas = mconcatIntersperse fromText (singleton ',')+toBuilderWithCommas = mconcatIntersperse B.fromText (singleton ',')  -- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% --                              Smart constructors@@ -237,10 +234,9 @@ hexToWord8 = fromIntegral . foldl (\s c -> s*16 + digitToInt c) 0  toRGBAInt :: Color -> Color-toRGBAInt (Named s) = case Map.lookup (T.toLower s) colorMap of-                        Just a  -> toRGBAInt a-                        Nothing -> error e+toRGBAInt (Named s) = maybe (error e) toRGBAInt (Map.lookup t colorMap)   where e = T.unpack $ "Invalid color keyword (" <> s <> "). Can't convert to rgba"+        t = T.toLower s toRGBAInt (Hex3 r g b) = RGBAInt (f [r,r]) (f [g,g]) (f [b,b]) 1   where f = fromIntegral . hexToWord8 toRGBAInt (Hex6 r g b) = RGBAInt (hexToWord8 r) (hexToWord8 g) (hexToWord8 b) 1
src/Hasmin/Types/Declaration.hs view
@@ -22,6 +22,8 @@ import Data.Maybe (fromMaybe) import Data.Sequence (Seq, (|>)) import Data.List (find, delete, minimumBy, (\\))+import Data.List.NonEmpty (NonEmpty((:|)))+import qualified Data.List.NonEmpty as NE import Data.Text (Text) import Data.Text.Lazy.Builder (singleton, fromText) import qualified Data.Map.Strict as Map@@ -41,10 +43,10 @@  -- | A CSS <https://www.w3.org/TR/css-syntax-3/#declaration \<declaration\>>. data Declaration = Declaration-    { propertyName :: Text -- ^ Property name of the declaration.-    , valueList    :: Values  -- ^ Values used in the declaration.-    , isImportant  :: Bool  -- ^ Whether the declaration is \"!important\" (i.e. ends with it).-    , hasIEhack    :: Bool    -- ^ Whether the declaration ends with the \"\\9\" IE hack.+    { propertyName :: Text   -- ^ Property name of the declaration.+    , valueList    :: Values -- ^ Values used in the declaration.+    , isImportant  :: Bool   -- ^ Whether the declaration is \"!important\" (i.e. ends with it).+    , hasIEhack    :: Bool   -- ^ Whether the declaration ends with the \"\\9\" IE hack.     } deriving (Eq, Show) instance ToText Declaration where   toBuilder (Declaration p vs i h) = fromText p <> singleton ':'@@ -98,10 +100,10 @@   ,("right",                      nullPercentageToLength)   ,("bottom",                     nullPercentageToLength)   ,("left",                       nullPercentageToLength)-  ,("border-color",               pure . reduceTRBL)-  ,("border-width",               pure . reduceTRBL)-  ,("border-style",               pure . reduceTRBL)-  ,("padding",                    nullPercentageToLength >=> pure . reduceTRBL)+  ,("border-color",               pure . reduceTRBLDec)+  ,("border-width",               pure . reduceTRBLDec)+  ,("border-style",               pure . reduceTRBLDec)+  ,("padding",                    nullPercentageToLength >=> pure . reduceTRBLDec)   ,("padding-top",                nullPercentageToLength)   ,("padding-right",              nullPercentageToLength)   ,("padding-bottom",             nullPercentageToLength)@@ -110,7 +112,7 @@   ,("margin-right",               nullPercentageToLength)   ,("margin-bottom",              nullPercentageToLength)   ,("margin-left",                nullPercentageToLength)-  ,("margin",                     nullPercentageToLength >=> pure . reduceTRBL)+  ,("margin",                     nullPercentageToLength >=> pure . reduceTRBLDec)   ,("grid-row-gap",               nullPercentageToLength)   ,("grid-column-gap",            nullPercentageToLength)   ,("line-height",                nullPercentageToLength)@@ -288,7 +290,6 @@     ,("left",   LengthV NullLength)     ,("center", PercentageV (Percentage 50))] - -- | Minifies a declaration, based on the property's specific traits (i.e. if -- it inherits or not, and what its initial value is), and the chosen -- configurations.@@ -519,12 +520,12 @@               -> Maybe Declaration -- ^ If successful, the combination of both mergeIntoTRBL d1@(Declaration _ (Values v1 vs) i1 h1) d2@(Declaration p2 (Values v2 _) i2 h2)     | h1 || h2     = Nothing -- TODO handle ie hacks-    | i1 && not i2 = Just $ reduceTRBL d1 -- margin:6px !important;margin-top:0; --> margin:6px !important+    | i1 && not i2 = Just $ reduceTRBLDec d1 -- margin:6px !important;margin-top:0; --> margin:6px !important     | not i1 && i2 = Nothing -- margin:6px;margin-top:0!important; --> stays the same     | otherwise    = do           (_,index) <- find (\(x,_) -> T.isInfixOf x (T.toCaseFold p2)) indexTable           let mkDec ys  = d1 {valueList = mkValues $ replaceAt index v2 ys}-              retDec ys = let mergedDec = reduceTRBL (mkDec ys)+              retDec ys = let mergedDec = reduceTRBLDec (mkDec ys)                           in if textualLength mergedDec <= originalLength                                 then Just mergedDec                                 else Nothing@@ -541,22 +542,9 @@ -- E.g.: margin: 6px 6px 6px 6px;  --> margin: 6px; --       margin: 1px 0 2px 0;      --> margin: 1px 0 2px; -- can be used with "border-image-outset" too.-reduceTRBL :: Declaration -> Declaration-reduceTRBL d@(Declaration _ (Values v1 vs) _ _) =-    case v1:map snd vs of-      [t,r,b,l] -> reduce4 t r b l-      [t,r,b]   -> reduce3 t r b-      [t,r]     -> reduce2 t r-      _         -> d-  where reduce4 tv rv bv lv-            | lv == rv  = reduce3 tv rv bv-            | otherwise = d-        reduce3 tv rv bv-            | tv == bv  = reduce2 tv rv-            | otherwise = d { valueList = mkValues [tv, rv, bv] }-        reduce2 tv rv-            | tv == rv  = d { valueList = mkValues [tv] }-            | otherwise = d { valueList = mkValues [tv, rv] }+reduceTRBLDec :: Declaration -> Declaration+reduceTRBLDec d@(Declaration _ (Values v1 vs) _ _) =+    d { valueList = mkValues . NE.toList $ reduceTRBL (v1:|map snd vs) }  mapValues :: (Value -> Reader Config Value) -> Values -> Reader Config Values mapValues f (Values v1 vs) = do
src/Hasmin/Types/Dimension.hs view
@@ -7,9 +7,9 @@ -- Stability   : experimental -- Portability : unknown ----- CSS Dimension data types: \<length\> (distance), \<angle\>, \<duration\>,--- \<frequency\>, and \<resolution\>. Provides conversion of absolute--- dimensions into other equivalent dimensions.+-- CSS Dimension data types: \<length\>, \<angle\>, \<time\>, \<frequency\>,+-- and \<resolution\>. Provides conversion of absolute dimensions into other+-- equivalent dimensions. -- ----------------------------------------------------------------------------- module Hasmin.Types.Dimension@@ -17,8 +17,8 @@     , LengthUnit(..)     , Angle(..)     , AngleUnit(..)-    , Duration(..)-    , DurationUnit(..)+    , Time(..)+    , TimeUnit(..)     , Frequency(..)     , FrequencyUnit(..)     , Resolution(..)@@ -35,9 +35,10 @@ import Control.Monad.Reader (asks) import Data.Monoid ((<>)) import Data.Text.Lazy.Builder (singleton, fromText)+ import Hasmin.Class-import Hasmin.Types.Numeric import Hasmin.Config+import Hasmin.Types.Numeric import Hasmin.Utils  -- | The \<length\> CSS data type@@ -101,7 +102,7 @@   minify a@(Angle r u) = do       dimSettings <- asks dimensionSettings       pure $ case dimSettings of-               DimMinOn  -> minDim Angle r u [Turn, Grad, Rad, Deg]+               DimMinOn  -> minDim Angle r u [minBound..]                DimMinOff -> a   minify NullAngle = pure NullAngle @@ -109,21 +110,21 @@   toBuilder NullAngle   = singleton '0'   toBuilder (Angle r u) = toBuilder r <> toBuilder u --- | The \<duration\> CSS data type-data Duration = Duration Number DurationUnit+-- | The \<time\> CSS data type+data Time = Time Number TimeUnit   deriving (Show)-instance Eq Duration where-  (Duration r1 u1) == (Duration r2 u2)+instance Eq Time where+  (Time r1 u1) == (Time r2 u2)     | u1 == u2  = r1 == r2     | otherwise = toSeconds r1 u1 == toSeconds r2 u2-instance Minifiable Duration where-  minify d@(Duration r u) = do+instance Minifiable Time where+  minify d@(Time r u) = do       dimSettings <- asks dimensionSettings       pure $ case dimSettings of-                DimMinOn  -> minDim Duration r u [S, Ms]+                DimMinOn  -> minDim Time r u [minBound..]                 DimMinOff -> d-instance ToText Duration where-  toBuilder (Duration r u) = toBuilder r <> toBuilder u+instance ToText Time where+  toBuilder (Time r u) = toBuilder r <> toBuilder u  -- | The \<frequency\> CSS data type data Frequency = Frequency Number FrequencyUnit@@ -136,7 +137,7 @@   minify f@(Frequency r u) = do       dimSettings <- asks dimensionSettings       pure $ case dimSettings of-                DimMinOn  -> minDim Frequency r u [Khz, Hz]+                DimMinOn  -> minDim Frequency r u [minBound..]                 DimMinOff -> f instance ToText Frequency where   toBuilder (Frequency r u) = toBuilder r <> toBuilder u@@ -152,7 +153,7 @@   minify x@(Resolution r u) = do       dimSettings <- asks dimensionSettings       pure $ case dimSettings of-               DimMinOn  -> minDim Resolution r u [Dpcm, Dppx, Dpi]+               DimMinOn  -> minDim Resolution r u [minBound..]                DimMinOff -> x instance ToText Resolution where   toBuilder (Resolution r u) = toBuilder r <> toBuilder u@@ -172,9 +173,9 @@ class Unit a where   convertTo :: a -> Number -> a -> Number -data LengthUnit = IN | CM | MM | Q | PC | PT | PX            -- absolute-                  | EM | EX | CH | VH | VW | VMIN | VMAX | REM -- relative-  deriving (Show, Eq)+data LengthUnit = IN | CM | MM | PC | PT | PX | Q            -- absolute+                | EM | EX | CH | VH | VW | VMIN | VMAX | REM -- relative+  deriving (Show, Eq, Enum, Bounded) instance ToText LengthUnit where   toBuilder IN   = "in"   toBuilder CM   = "cm"@@ -201,31 +202,31 @@   convertTo PX = toPixels   convertTo _  = const -data AngleUnit = Deg | Grad | Rad | Turn-  deriving (Show, Eq)+data AngleUnit = Turn | Grad | Rad | Deg+  deriving (Show, Eq, Enum, Bounded) instance ToText AngleUnit where-  toBuilder Deg  = "deg"+  toBuilder Turn = "turn"   toBuilder Grad = "grad"   toBuilder Rad  = "rad"-  toBuilder Turn = "turn"+  toBuilder Deg  = "deg" instance Unit AngleUnit where-  convertTo Deg  = toDegrees+  convertTo Turn = toTurns   convertTo Grad = toGradians   convertTo Rad  = toRadians-  convertTo Turn = toTurns+  convertTo Deg  = toDegrees -data DurationUnit = S -- seconds-                  | Ms -- miliseconds-  deriving (Show, Eq)-instance ToText DurationUnit where+data TimeUnit = S -- seconds+              | Ms -- miliseconds+  deriving (Show, Eq, Enum, Bounded)+instance ToText TimeUnit where   toBuilder S  = "s"   toBuilder Ms = "ms"-instance Unit DurationUnit where+instance Unit TimeUnit where   convertTo S  = toSeconds   convertTo Ms = toMiliseconds  data FrequencyUnit = Hz | Khz-  deriving (Show, Eq)+  deriving (Show, Eq, Enum, Bounded) instance ToText FrequencyUnit where   toBuilder Hz  = "hz"   toBuilder Khz = "khz"@@ -233,8 +234,8 @@   convertTo Hz  = toHertz   convertTo Khz = toKilohertz -data ResolutionUnit = Dpi | Dpcm | Dppx-  deriving (Show, Eq)+data ResolutionUnit = Dpcm | Dppx | Dpi+  deriving (Show, Eq, Enum, Bounded) instance ToText ResolutionUnit where   toBuilder Dpi  = "dpi"   toBuilder Dpcm = "dpcm"@@ -334,11 +335,11 @@ toTurns d Rad  = d / (2 * rationalPi) toTurns d Turn = d -------------------------------------------------------------------------------toSeconds :: Number -> DurationUnit -> Number+toSeconds :: Number -> TimeUnit -> Number toSeconds d S     = d toSeconds d Ms = d / 1000 -toMiliseconds :: Number -> DurationUnit -> Number+toMiliseconds :: Number -> TimeUnit -> Number toMiliseconds d S     = d * 1000 toMiliseconds d Ms = d ------------------------------------------------------------------------------
src/Hasmin/Types/FilterFunction.hs view
@@ -17,22 +17,26 @@ import Data.Monoid ((<>)) import Data.Text.Lazy.Builder (singleton, Builder) -import Hasmin.Config import Hasmin.Class-import Hasmin.Types.Dimension+import Hasmin.Config import Hasmin.Types.Color+import Hasmin.Types.Dimension import Hasmin.Types.Numeric +-- The CSS spec calls it <number-percentage>, amount comes from the mozilla+-- docs.+type Amount = Either Number Percentage+ -- | CSS <https://drafts.fxtf.org/filter-effects/#typedef-filter-function \<filter-function\>> -- data type. data FilterFunction = Blur Length-                    | Brightness (Either Number Percentage)-                    | Contrast (Either Number Percentage)-                    | Grayscale (Either Number Percentage)-                    | Invert (Either Number Percentage)-                    | Opacity (Either Number Percentage)-                    | Saturate (Either Number Percentage)-                    | Sepia (Either Number Percentage)+                    | Brightness Amount+                    | Contrast Amount+                    | Grayscale Amount+                    | Invert Amount+                    | Opacity Amount+                    | Saturate Amount+                    | Sepia Amount                     | HueRotate Angle                     | DropShadow Length Length (Maybe Length) (Maybe Color)   deriving (Show)@@ -97,8 +101,7 @@               c2 <- traverse minify d               pure $ constr x y z c2 -minifyNumberPercentage :: Either Number Percentage-                       -> Reader Config (Either Number Percentage)+minifyNumberPercentage :: Amount -> Reader Config Amount minifyNumberPercentage x = do     conf <- ask     pure $ if shouldMinifyFilterFunctions conf@@ -112,8 +115,7 @@             | 0 < p && p < 10 = Right p             | otherwise       = Left $ toNumber (p / 100) -filterFunctionEquality :: Either Number Percentage-                       -> Either Number Percentage -> Bool+filterFunctionEquality :: Amount -> Amount -> Bool filterFunctionEquality (Left a) (Left b)   = toRational a == toRational b filterFunctionEquality (Right a) (Right b) = toRational a == toRational b filterFunctionEquality (Left a) (Right b)  = toRational a == toRational b/100
src/Hasmin/Types/Gradient.hs view
@@ -21,8 +21,9 @@ import Data.Text.Lazy.Builder (singleton)
 import Data.Maybe (catMaybes, fromJust, isNothing, isJust)
 import Data.Either (isLeft)
-import Hasmin.Config
+
 import Hasmin.Class
+import Hasmin.Config
 import Hasmin.Types.Color
 import Hasmin.Types.Dimension
 import Hasmin.Types.Numeric
src/Hasmin/Types/PercentageLength.hs view
@@ -16,10 +16,11 @@     ) where  import Control.Monad.Reader (Reader)-import Hasmin.Types.Dimension-import Hasmin.Types.Numeric+ import Hasmin.Class import Hasmin.Config+import Hasmin.Types.Dimension+import Hasmin.Types.Numeric  -- | CSS <length-percentage> data type, i.e.: [length | percentage] -- Though because of the name it would be more intuitive to define:@@ -28,9 +29,7 @@ -- makes no sense to minify a Percentage. type PercentageLength = Either Percentage Length --minifyPL :: PercentageLength-         -> Reader Config PercentageLength+minifyPL :: PercentageLength -> Reader Config PercentageLength minifyPL x@(Right _) = mapM minify x minifyPL x@(Left p)     | p == 0    = pure $ Right NullLength -- minifies 0% to 0
src/Hasmin/Types/Position.hs view
@@ -14,12 +14,13 @@     , minifyPosition     , p50     , l0+    , centerpos     ) where  import Data.Text (Text) import qualified Data.Text as T import Data.Text.Lazy.Builder (singleton, fromText)-import Data.Maybe (isJust)+import Data.Maybe (isJust, isNothing) import Hasmin.Class import Hasmin.Types.Dimension import Hasmin.Types.PercentageLength@@ -30,7 +31,7 @@                 | PosRight                 | PosTop                 | PosBottom-  deriving (Eq, Show)+  deriving (Show, Eq, Enum, Bounded) instance ToText PosKeyword where   toBuilder PosCenter = "center"   toBuilder PosTop    = "top"@@ -65,7 +66,9 @@ minifyPosition p@(Position (Just x) Nothing Nothing (Just y)) =     minifyPos2 x y   where mkPos2 i j = if isZero j-                        then Position Nothing i Nothing l0+                        then if i == p50+                                then Position (Just PosTop) Nothing Nothing Nothing+                                else Position Nothing i Nothing l0                         else Position Nothing i Nothing (Just j)         minifyPos2 PosLeft a   = mkPos2 l0 a         minifyPos2 PosRight a  = mkPos2 p100 a@@ -78,7 +81,7 @@                         else Position Nothing (Just i) Nothing j         minifyPos2 a PosTop    = mkPos2 a l0         minifyPos2 a PosBottom = mkPos2 a p100-        minifyPos2 a PosCenter = mkPos2 a p50+        minifyPos2 a PosCenter = mkPos2 a Nothing         minifyPos2 _ _         = p -- Two <lenght-percentage> minifyPosition p@(Position Nothing (Just x) Nothing (Just y)) = f x y@@ -148,7 +151,7 @@         minifyPos3 PosLeft b PosCenter           | isZero b     = Position Nothing l0 Nothing Nothing           | b == Left 50 = Position Nothing p50 Nothing Nothing-          | otherwise    = Position Nothing (Just b) Nothing p50+          | otherwise    = Position Nothing (Just b) Nothing Nothing         minifyPos3 PosRight b PosTop           | isZero b     = Position Nothing p100 Nothing l0           | otherwise    = Position (Just PosRight) (Just b) Nothing l0@@ -160,8 +163,18 @@           | otherwise    = Position (Just PosRight) (Just b) Nothing p50         minifyPos3 _ _ _ = p minifyPosition p@(Position (Just c) Nothing (Just a) (Just b)) = f $ minAxis a b-  where f (x, y)+  where isHorizontal i = i == PosLeft || i == PosRight+        f (x, y)           | c == PosLeft && x == Just PosTop && isJust y = minifyPosition $ Position Nothing l0 Nothing y+          | c == PosCenter && isHorizontal a = Position x y Nothing Nothing+          | c == PosTop && isNothing x =+              if y == p50+                 then Position (Just PosTop) Nothing Nothing Nothing+                 else Position x y Nothing l0+          | c == PosBottom && isNothing x =+              if y == p50+                 then Position (Just PosBottom) Nothing Nothing Nothing+                 else Position x y Nothing p100           | otherwise = if Just a == x && Just b == y                            then p                            else minifyPosition $ p {origin2 = x, offset2 = y }@@ -185,7 +198,7 @@         minifyPos4' PosLeft a PosBottom b             | isZero b  = minifyPosition $ Position Nothing (Just a) Nothing p100             | isZero a  = minifyPosition $ Position (Just PosLeft) Nothing (Just PosTop) (Just b)-            | otherwise = minifyPosition $ Position (Just PosLeft) (Just a) (Just PosBottom) (Just b)+            | otherwise = Position (Just PosLeft) (Just a) (Just PosBottom) (Just b)         minifyPos4' PosRight a PosBottom b             | isZero a && isZero b = Position Nothing p100 Nothing p100             | otherwise            = Position (Just PosRight) (Just a) (Just PosBottom) (Just b)@@ -200,12 +213,12 @@                     else (Just PosTop, Just x) minAxis PosLeft x =     case x of-      Left 50 -> (Just PosCenter, Nothing)+      Left 50 -> (Nothing, p50)       smth    -> if isZero smth-                    then (Just PosLeft, Nothing)+                    then (Nothing, l0)                     else (Just PosLeft, Just x) minAxis PosRight x-    | isZero x  = (Just PosRight, Nothing)+    | isZero x  = (Nothing, p100)     | otherwise = (Just PosRight, Just x) minAxis PosBottom x     | isZero x  = (Just PosBottom, Nothing)@@ -213,9 +226,10 @@ minAxis PosCenter x = (Just PosCenter, Just x)  instance Eq Position where-  x == y = let (Position a b c d) = minifyPosition x +  x == y = let (Position a b c d) = minifyPosition x                (Position e f g h) = minifyPosition y            in a == e && b == f && c == g && d == h+              || a == g && b == h && c == e && d == f  l0 :: Maybe PercentageLength l0 = Just $ Right NullLength@@ -225,3 +239,6 @@  p50 :: Maybe PercentageLength p50 = Just $ Left 50++centerpos :: Position+centerpos = Position (Just PosCenter) Nothing Nothing Nothing
src/Hasmin/Types/RepeatStyle.hs view
@@ -65,7 +65,7 @@                | RsSpace                | RsRound                | RsNoRepeat-  deriving (Eq, Show)+  deriving (Show, Eq, Enum, Bounded) instance ToText RSKeyword where   toBuilder RsRepeat   = "repeat"   toBuilder RsSpace    = "space"
src/Hasmin/Types/Shadow.hs view
@@ -22,12 +22,12 @@  -- | CSS <https://drafts.csswg.org/css-backgrounds-3/#typedef-shadow \<shadow\>> -- data type, used by the @box-shadow@ property.-data Shadow = Shadow { inset        :: Bool-                     , sOffsetX     :: Length-                     , sOffsetY     :: Length-                     , blurRadius   :: Maybe Length-                     , spreadRadius :: Maybe Length-                     , sColor       :: Maybe Color+data Shadow = Shadow { _inset        :: Bool+                     , _sOffsetX     :: Length+                     , _sOffsetY     :: Length+                     , _blurRadius   :: Maybe Length+                     , _spreadRadius :: Maybe Length+                     , _sColor       :: Maybe Color                      } deriving (Eq, Show)  instance ToText Shadow where
src/Hasmin/Types/String.hs view
@@ -18,23 +18,25 @@   , StringType(..)   ) where -import Control.Applicative (liftA2, (<|>)) import Control.Monad.Reader (ask, Reader)+import Control.Monad (mzero)+import Control.Applicative ((<|>), many) import Data.Attoparsec.Text (Parser, parse, IResult(Done, Partial, Fail), maybeResult, feed) import Data.Monoid ((<>)) import Data.Text (Text)-import Data.Foldable (foldl')-import Data.Bits ((.|.), shiftL)-import Data.Text.Lazy.Builder (Builder)-import qualified Data.Text.Lazy.Builder as B import qualified Data.Attoparsec.Text as A-import qualified Data.Char as C import qualified Data.Text as T import qualified Data.Text.Lazy as TL+import Data.Text.Lazy.Builder (Builder)+import qualified Data.Text.Lazy.Builder as B+import qualified Data.Char as C+import Data.Bits ((.|.), shiftL)+import Data.Foldable (foldl')  import Hasmin.Config-import Hasmin.Parser.Utils import Hasmin.Class+import Hasmin.Parser.Primitives+import Hasmin.Parser.Utils  -- | The <https://drafts.csswg.org/css-values-3/#strings \<string\>> data type. -- It represents a string, formed by Unicode characters, delimited by either@@ -80,13 +82,13 @@ unquoteStringType g x@(SingleQuotes s) = maybe (Right x) Left (g s)  removeQuotes :: StringType -> Either Text StringType-removeQuotes = unquoteStringType toIdent+removeQuotes = unquoteStringType (unquote ident)  unquoteUrl :: StringType -> Either Text StringType-unquoteUrl = unquoteStringType toUnquotedURL+unquoteUrl = unquoteStringType (unquote unquotedURL)  unquoteFontFamily :: StringType -> Either Text StringType-unquoteFontFamily = unquoteStringType toUnquotedFontFamily+unquoteFontFamily = unquoteStringType (unquote fontfamilyname)  unquote :: Parser Text -> Text -> Maybe Text unquote p s = case parse p s of@@ -96,15 +98,6 @@                 par@Partial{} -> maybeResult (feed par mempty)                 Fail{}        -> Nothing -toIdent :: Text -> Maybe Text-toIdent = unquote ident--toUnquotedURL :: Text -> Maybe Text-toUnquotedURL = unquote unquotedURL--toUnquotedFontFamily :: Text -> Maybe Text-toUnquotedFontFamily = unquote fontfamilyname- -- TODO can the Parser be avoided by a fold, or one of the provided library -- functions? Apart from being cleaner, doing so would simplify other functions. -- | Parse and convert any escaped unicode to its underlying Char.@@ -124,7 +117,7 @@     parseEscapedAndContinue :: Builder -> Parser Builder     parseEscapedAndContinue b = do         u8 <- utf8-        (b `mappend` u8 `mappend`) <$>  go+        ((b <> u8) <>) <$>  go      utf8 :: Parser Builder     utf8 = do@@ -145,6 +138,26 @@                 | otherwise   = (a `shiftL` 4) .|. fromIntegral (w - 55)               where w = C.ord c -    atMost :: Int -> Parser a -> Parser [a]-    atMost 0 _ = pure []-    atMost n p = A.option [] $ liftA2 (:) p (atMost (n-1) p)+fontfamilyname :: Parser Text+fontfamilyname = do+    i  <- ident+    is <- many (skipComments *> ident)+    if T.toLower i `elem` invalidNames+       then mzero+       else pure $ i <> foldMap (" "<>) is+  where invalidNames = ["serif", "sans-serif", "monospace", "cursive",+                        "fantasy", "inherit", "initial", "unset", "default"]++-- | Parse a URL without enclosing quotes.+-- See <https://drafts.csswg.org/css-syntax-3/#consume-a-url-token §4.3.6. Consume a url token>+unquotedURL :: Parser Text+unquotedURL = do+    t <- many (escape <|> (B.singleton <$> A.satisfy validChar))+    pure $ TL.toStrict (B.toLazyText (mconcat t))+  where validChar x = x /= '\"' && x /= '\'' && x /= '(' && x /= ')'+                   && x /= '\\' && notWhitespace x && notNonprintable x+        notWhitespace x = x /= '\n' &&  x /= '\t' && x /= ' '+        notNonprintable x = not (C.chr 0 <= x && x <= C.chr 8)+                         && x /= '\t'+                         && not ('\SO' <= x && x <= C.chr 31)+                         && x /= '\DEL'
src/Hasmin/Types/TimingFunction.hs view
@@ -69,7 +69,7 @@ -- <https://drafts.csswg.org/css-timing-1/#step-timing-functions step timing function>. data StepPosition = Start                   | End -- End is the default value-  deriving (Eq, Show)+  deriving (Show, Eq, Enum, Bounded) instance ToText StepPosition where   toBuilder Start = "start"   toBuilder End   = "end"
src/Hasmin/Types/Value.hs view
@@ -8,8 +8,8 @@ -- Portability : unknown -- ------------------------------------------------------------------------------module Hasmin.Types.Value (-      Value(..)+module Hasmin.Types.Value+    ( Value(..)     , Values(..)     , TextV(..)     , Separator(..)@@ -24,14 +24,16 @@ import Control.Monad.Reader (ask, Reader, mapReader) import Data.Monoid ((<>)) import Data.Maybe (isJust, catMaybes, isNothing)-import Data.Text (Text, toCaseFold)+import Data.Text (Text) import qualified Data.Text as T import Data.Text.Lazy.Builder (fromText, singleton, Builder) import Data.String (IsString) +import Hasmin.Class import Hasmin.Config import Hasmin.Types.BgSize-import Hasmin.Class+import Hasmin.Types.BasicShape+import Hasmin.Types.BorderRadius import Hasmin.Types.Color import Hasmin.Types.Dimension import Hasmin.Types.FilterFunction@@ -53,7 +55,7 @@            | PercentageV Percentage            | LengthV Length            | AngleV Angle-           | DurationV Duration+           | TimeV Time            | FrequencyV Frequency            | ResolutionV Resolution            | ColorV Color@@ -66,15 +68,30 @@            | ShadowText Length Length (Maybe Length) (Maybe Color) -- <shadow-t> type            | PositionV Position            | RepeatStyleV RepeatStyle+           | BorderRadiusV BorderRadius+           | BasicShapeV BasicShape            | BgSizeV BgSize-           --          <bg-image> || <position> [ / <bg-size> ]?  || <repeat-style>    || <attachment>    || <box>{1,2}-           | BgLayer (Maybe Value) (Maybe Position) (Maybe BgSize) (Maybe RepeatStyle) (Maybe TextV) (Maybe TextV) (Maybe TextV)+           -- <bg-image> || <position> [ / <bg-size> ]? || <repeat-style> || <attachment> || <box>{1,2}+           | BgLayer+              { _bgimage      :: Maybe Value+              , _bgposition   :: Maybe Position+              , _bgsize       :: Maybe BgSize+              , _bgrepeat     :: Maybe RepeatStyle+              , _bgattachment :: Maybe TextV+              , _bgbox1       :: Maybe TextV+              , _bgbox2       :: Maybe TextV+              }            --              <bg-image> || <position> [ / <bg-size> ]? || <repeat-style> || <attachment> || <box> || <box> || <'background-color'>            | FinalBgLayer (Maybe Value) (Maybe Position) (Maybe BgSize) (Maybe RepeatStyle) (Maybe TextV) (Maybe TextV) (Maybe TextV) (Maybe Color)            -- [ none | <single-transition-property> ] || <time> || <single-transition-timing-function> || <time>-           | SingleTransition (Maybe TextV) (Maybe Duration) (Maybe TimingFunction) (Maybe Duration)+           | SingleTransition+              { _transitionproperty :: Maybe TextV+              , _time1 :: Maybe Time+              , _transitiontimingfunction :: Maybe TimingFunction+              , _time2 :: Maybe Time+              }            --                         <time> || <timing-function> || <time> || <iteration-count> || <animation-direction> || <animation-fill-mode> || <animation-play-state> || [ none | <keyframes-name> ]-           | SingleAnimation (Maybe Duration) (Maybe TimingFunction) (Maybe Duration) (Maybe Value)  (Maybe TextV) (Maybe TextV) (Maybe TextV) (Maybe Value)+           | SingleAnimation (Maybe Time) (Maybe TimingFunction) (Maybe Time) (Maybe Value)  (Maybe TextV) (Maybe TextV) (Maybe TextV) (Maybe Value)            -- [ [ <'font-style'> || <font-variant-css21> || <'font-weight'> || <'font-stretch'> ]? <'font-size'> [ / <'line-height'> ]? <'font-family'> ] |            | FontV (Maybe TextV) (Maybe TextV) (Maybe Value) (Maybe TextV) Value (Maybe Value) [Value]            | StringV StringType@@ -91,7 +108,7 @@   deriving (Show, Ord, IsString)  instance Eq TextV where-  TextV t1 == TextV t2 = toCaseFold t1 == toCaseFold t2+  TextV t1 == TextV t2 = T.toLower t1 == T.toLower t2 instance ToText TextV where   toText = getText @@ -121,7 +138,7 @@   toBuilder (ColorV c)      = toBuilder c   toBuilder (LengthV d)     = toBuilder d   toBuilder (AngleV a)      = toBuilder a-  toBuilder (DurationV d)   = toBuilder d+  toBuilder (TimeV d)       = toBuilder d   toBuilder (FrequencyV f)  = toBuilder f   toBuilder (ResolutionV r) = toBuilder r   toBuilder (FilterV f)     = toBuilder f@@ -140,6 +157,8 @@     where funcValues = mconcatIntersperse toBuilder (singleton ',') [a, b, c, d]   toBuilder (PositionV p)   = toBuilder p   toBuilder (RepeatStyleV r) = toBuilder r+  toBuilder (BorderRadiusV b) = toBuilder b+  toBuilder (BasicShapeV b)   = toBuilder b   toBuilder (BgSizeV b)     = toBuilder b   toBuilder (BgLayer a b c d e f g) =       let sz  = maybe mempty (\x -> singleton '/' <> toBuilder x) c@@ -177,22 +196,24 @@        <> maybeToBuilder ml <> maybeToBuilder mc  instance Minifiable Value where-  minify (ColorV c)       = ColorV <$> minify c-  minify (LengthV d)      = LengthV <$> minify d-  minify (AngleV a)       = AngleV <$> minify a-  minify (DurationV d)    = DurationV <$> minify d-  minify (FrequencyV f)   = FrequencyV <$> minify f-  minify (ResolutionV r)  = ResolutionV <$> minify r-  minify (GradientV t g)  = GradientV t <$> minify g-  minify (FilterV f)      = FilterV <$> minify f-  minify (TransformV tf)  = TransformV <$> minify tf-  minify (TimingFuncV tf) = TimingFuncV <$> minify tf-  minify (StringV s)      = StringV <$> minify s-  minify (UrlV u)         = UrlV <$> minify u-  minify (Format x)       = Format <$> mapM minify x-  minify (PositionV p)    = PositionV <$> minify p-  minify (RepeatStyleV r) = RepeatStyleV <$> minify r-  minify (BgSizeV b)      = BgSizeV <$> minify b+  minify (ColorV c)        = ColorV <$> minify c+  minify (LengthV d)       = LengthV <$> minify d+  minify (AngleV a)        = AngleV <$> minify a+  minify (TimeV d)         = TimeV <$> minify d+  minify (FrequencyV f)    = FrequencyV <$> minify f+  minify (ResolutionV r)   = ResolutionV <$> minify r+  minify (GradientV t g)   = GradientV t <$> minify g+  minify (FilterV f)       = FilterV <$> minify f+  minify (TransformV tf)   = TransformV <$> minify tf+  minify (TimingFuncV tf)  = TimingFuncV <$> minify tf+  minify (StringV s)       = StringV <$> minify s+  minify (UrlV u)          = UrlV <$> minify u+  minify (Format x)        = Format <$> mapM minify x+  minify (PositionV p)     = PositionV <$> minify p+  minify (RepeatStyleV r)  = RepeatStyleV <$> minify r+  minify (BgSizeV b)       = BgSizeV <$> minify b+  minify (BasicShapeV b)    = BasicShapeV <$> minify b+  minify (BorderRadiusV b) = BorderRadiusV <$> minify b   minify (ShadowV s)      = ShadowV <$> minify s   minify (ShadowText l1 l2 ml mc) = minifyPseudoShadow ShadowText l1 l2 ml mc   minify (BgLayer img pos sz rst att b1 b2) = do@@ -220,11 +241,11 @@       let p = if prop == Just "all"                  then Nothing                  else prop -- TODO lowercase here-      (tDuration, tDelay) <- handleTime tdur tdel-      tfunc               <- handleTimingFunction tf-      pure $ if isNothing p && isNothing tDuration && isNothing tDelay && isNothing tfunc-                then SingleTransition p (Just $ Duration 0 S) tfunc tDelay-                else SingleTransition p tDuration tfunc tDelay+      (tTime, tDelay) <- handleTime tdur tdel+      tfunc           <- handleTimingFunction tf+      pure $ if isNothing p && isNothing tTime && isNothing tDelay && isNothing tfunc+                then SingleTransition p (Just $ Time 0 S) tfunc tDelay+                else SingleTransition p tTime tfunc tDelay   minify (SingleAnimation t1 tf t2 ic ad af ap kf) = do       (tdur, tdel) <- handleTime t1 t2       tfunc        <- handleTimingFunction tf@@ -396,33 +417,32 @@     | otherwise = Just <$> minify tfunc  -- Used for SingleAnimation and SingleTransition minification.-handleTime :: Maybe Duration -> Maybe Duration -> Reader Config (Maybe Duration, Maybe Duration)-handleTime (Just t) Nothing = if t == Duration 0 S-                                 then pure (Nothing, Nothing)-                                 else do newT <- minify t-                                         pure (Just newT, Nothing)+handleTime :: Maybe Time -> Maybe Time -> Reader Config (Maybe Time, Maybe Time)+handleTime (Just t) Nothing+    | t == Time 0 S = pure (Nothing, Nothing)+    | otherwise     = mzip (Just <$> minify t) (pure Nothing) handleTime (Just t1) (Just t2)-    | t1 == Duration 0 S = if t2 == t1-                              then pure (Nothing, Nothing)-                              else do newT2 <- minify t2-                                      newT1 <- minify t1-                                      pure (Just newT1, Just newT2)+    | t1 == Time 0 S = if t2 == t1+                          then pure (Nothing, Nothing)+                          else do newT2 <- minify t2+                                  newT1 <- minify t1+                                  pure (Just newT1, Just newT2)     | otherwise = do newT1 <- minify t1-                     if t2 == Duration 0 S+                     if t2 == Time 0 S                         then pure (Just t1, Nothing)                         else do newT2 <- minify t2                                 pure (Just newT1, Just newT2) handleTime _ _ = pure (Nothing, Nothing)  removeIfEqualTo :: Text -> Maybe TextV -> Maybe TextV-removeIfEqualTo _ Nothing  = Nothing+removeIfEqualTo _ Nothing = Nothing removeIfEqualTo s (Just x)     | x == TextV s = Nothing     | otherwise    = Just x  -- Unquotes font family names when possible optimizeFontFamily :: Value -> Reader Config Value-optimizeFontFamily (Other t) = mkOther <$> lowercaseText (getText t)+optimizeFontFamily (Other t)   = mkOther <$> lowercaseText (getText t) optimizeFontFamily (StringV s) = do     conf <- ask     ffamily <- mapString lowercaseText s
src/Hasmin/Utils.hs view
@@ -17,10 +17,13 @@     , restrict     , textualLength     , replaceAt+    , mzip+    , reduceTRBL     ) where  import Data.Monoid ((<>)) import qualified Data.Text as T+import Data.List.NonEmpty (NonEmpty((:|)))  import Hasmin.Class @@ -58,3 +61,24 @@  eps :: Rational eps = 0.000001 -- See https://bug-30341-attachments.webkit.org/attachment.cgi?id=45276++-- Why does Attoparsec have no instance of MonadZip? :«+mzip :: Applicative f => f a -> f b -> f (a, b)+mzip f g = (,) <$> f <*> g++reduceTRBL :: Eq a => NonEmpty a -> NonEmpty a+reduceTRBL xs =+    case xs of+      t:|[r,b,l] -> reduce4 t r b l+      t:|[r,b]   -> reduce3 t r b+      t:|[r]     -> reduce2 t r+      _          -> xs+  where reduce4 tv rv bv lv+            | lv == rv  = reduce3 tv rv bv+            | otherwise = xs+        reduce3 tv rv bv+            | tv == bv  = reduce2 tv rv+            | otherwise = tv:|[rv, bv]+        reduce2 tv rv+            | tv == rv  = tv:|[]+            | otherwise = tv:|[rv]
tests/Hasmin/TestUtils.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS -Wno-orphans #-}  module Hasmin.TestUtils     ( module Hasmin.TestUtils@@ -8,7 +9,8 @@     ) where  import Test.Hspec-import Test.QuickCheck+import Test.QuickCheck hiding (NonZero)+import Test.QuickCheck.Instances() import Test.Hspec.Attoparsec (parseSatisfies, (~>))  import Control.Applicative (liftA2, liftA3)@@ -28,6 +30,8 @@ import Hasmin.Types.Position import Hasmin.Types.TimingFunction import Hasmin.Types.RepeatStyle+import Hasmin.Types.BasicShape+import Hasmin.Types.BorderRadius import Hasmin.Utils  @@ -51,40 +55,33 @@   it (unpack textToParse) $     (toText <$> (textToParse ~> parser)) `parseSatisfies` (== expectedResult) -mkGen :: [a] -> Gen a-mkGen xs = (xs !!) <$> choose (0, length xs - 1)+chooseConstructor :: (Enum a, Bounded a) => Gen a+chooseConstructor = oneof $ fmap pure [minBound..]  newtype Declarations = Declarations [Declaration] instance ToText Declarations where   toText (Declarations ds) = mconcatIntersperse toText (singleton ';') ds  instance Arbitrary Length where-  arbitrary = liftA2 Length arbitrary distanceUnit-    -- TODO keep it DRY, avoid repeating constructor list here (i.e. SPOF)-    where distanceUnit = mkGen [IN, CM, MM, Q, PC, PT, PX, EM, EX,-                                CH, VH, VW, VMIN, VMAX, REM]+  arbitrary = liftA2 Length arbitrary chooseConstructor  instance Arbitrary Angle where-  arbitrary = liftA2 Angle arbitrary angleUnit-    where angleUnit = mkGen [Deg, Grad, Rad, Turn]+  arbitrary = liftA2 Angle arbitrary chooseConstructor -instance Arbitrary Duration where-  arbitrary = liftA2 Duration arbitrary durationUnit-    where durationUnit = mkGen [S, Ms]+instance Arbitrary Time where+  arbitrary = liftA2 Time arbitrary chooseConstructor  instance Arbitrary Frequency where-  arbitrary = liftA2 Frequency arbitrary frequencyUnit-    where frequencyUnit = mkGen [Hz, Khz]+  arbitrary = liftA2 Frequency arbitrary chooseConstructor  instance Arbitrary Resolution where-  arbitrary = liftA2 Resolution arbitrary resolutionUnit-    where resolutionUnit = mkGen [Dpi, Dpcm, Dppx]+  arbitrary = liftA2 Resolution arbitrary chooseConstructor  instance Arbitrary Number where   arbitrary = toNumber <$> (arbitrary :: Gen Rational)  instance Arbitrary PosKeyword where-  arbitrary = mkGen [PosCenter, PosLeft, PosRight, PosTop, PosBottom]+  arbitrary = chooseConstructor  instance Arbitrary Percentage where   arbitrary = fmap Percentage (arbitrary :: Gen Rational)@@ -112,7 +109,7 @@   arbitrary = pure Auto  instance Arbitrary StepPosition where-  arbitrary = oneof [pure Start, pure End]+  arbitrary = chooseConstructor  instance Arbitrary TimingFunction where   arbitrary = oneof [ liftM4 CubicBezier arbitrary arbitrary arbitrary arbitrary@@ -152,15 +149,47 @@                         ]  instance Arbitrary RSKeyword where-  arbitrary = mkGen [RsRepeat, RsSpace, RsRound, RsNoRepeat]+  arbitrary = oneof $ fmap pure [minBound..] +instance Arbitrary BasicShape where+  arbitrary = oneof+      [liftA2 Inset arbitrary arbitrary+      ,liftA2 Circle arbitrary arbitrary+      ,liftA2 Ellipse arbitrary arbitrary+      ,liftA2 Polygon arbitrary arbitrary+      ]++instance Arbitrary FillRule where+  arbitrary = oneof [pure NonZero, pure EvenOdd]++instance Arbitrary a => Arbitrary (AtMost2 a) where+  arbitrary = oneof+      [pure None+      ,One <$> arbitrary+      ,liftA2 Two arbitrary arbitrary+      ]++instance Arbitrary ShapeRadius where+  arbitrary = oneof+      [SRLength <$> arbitrary+      ,SRPercentage <$> arbitrary+      ,pure SRClosestSide+      ,pure SRFarthestSide+      ]++instance Arbitrary Position where+  arbitrary = Position <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary++instance Arbitrary BorderRadius where+  arbitrary = BorderRadius <$> arbitrary <*> arbitrary+ -- | Generates color keywords uniformly distributed colorKeyword :: Gen Text-colorKeyword = mkGen $ fmap fst keywordColors+colorKeyword = oneof $ fmap (pure . fst) keywordColors  -- | Generates a hexadecimal character uniformly distributed hexChar :: Gen Char-hexChar = mkGen hexadecimals+hexChar = oneof $ fmap pure hexadecimals  hexString :: Gen String hexString = liftA2 (\x y -> [x,y]) hexChar hexChar
+ tests/Hasmin/Types/BasicShapeSpec.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE OverloadedStrings #-}++module Hasmin.Types.BasicShapeSpec where++import Data.Text (Text)+import Data.Foldable (traverse_)+import Data.Attoparsec.Text (Parser)+import Test.Hspec.QuickCheck (modifyMaxSuccess)++import Hasmin.Parser.Value+import Hasmin.Types.Value+import Hasmin.Types.BasicShape+import Hasmin.TestUtils++basicShapeTests :: Spec+basicShapeTests =+  describe "<basic-shape> tests" $ do+    traverse_ (matchSpec f) basicShapeTestsInfo+    modifyMaxSuccess (const 10000) . it "Minified <basic-shape> maintains semantical equivalence" $+      property (prop_minificationEq :: BasicShape -> Bool)+  where f :: Parser Value+        f = minifyWithTestConfig <$> value++basicShapeTestsInfo :: [(Text, Text)]+basicShapeTestsInfo =+  [("inset(1px 2px 1px 2px)",              "inset(1px 2px)")+  ,("inset(1px 1px round 4px / 4px)",      "inset(1px round 4px)")+  ,("inset(1px 1px round 0px 0% / 0 0 0)", "inset(1px)")+  ,("ellipse(12px 12px at left bottom)",   "ellipse(9pt 9pt at 0 100%)")+  ,("ellipse(12px 12px at center)",        "ellipse(9pt 9pt)")+  ,("ellipse(12px closest-side)",          "ellipse(9pt)")+  ,("ellipse(farthest-side closest-side)", "ellipse(farthest-side)")+  ,("ellipse(closest-side closest-side)",  "ellipse()")+  ,("polygon(nonzero, 12px 12px)",         "polygon(9pt 9pt)")+  ,("polygon(evenodd, 12px 12px)",         "polygon(evenodd,9pt 9pt)")+  ,("circle(closest-side at center)",      "circle()")+  ,("circle(closest-side at left bottom)", "circle(at 0 100%)")+  ,("circle(12px 12px at 45%)",            "circle(9pt 9pt at 45%)")+  ]++spec :: Spec+spec = basicShapeTests++main :: IO ()+main = hspec spec
tests/Hasmin/Types/DimensionSpec.hs view
@@ -13,7 +13,7 @@       it "Minified <angle>s are equivalent to the original ones" $         property (prop_minificationEq :: Angle -> Bool)       it "Minified <time>s are equivalent to the original ones" $-        property (prop_minificationEq :: Duration -> Bool)+        property (prop_minificationEq :: Time -> Bool)       it "Minified <frequency>s are equivalent to the original ones" $         property (prop_minificationEq :: Frequency -> Bool)       it "Minified <resolution>s are equivalent to the original ones" $@@ -65,7 +65,7 @@       it "1in != 1em" $         Length 1 IN `shouldNotBe` Length 1 EM     -- describe "<time> conversions" $ do-      -- it "" $ Duration 1 S `shouldBe` Duration 1000 Ms+      -- it "" $ Time 1 S `shouldBe` Time 1000 Ms     -- describe "<frequency> conversions" $ do       -- it "" $ Frequency 1 Khz `shouldBe` Frequency 1000 Hz     -- describe "<resolution> conversions" $ do
tests/Hasmin/Types/PositionSpec.hs view
@@ -3,17 +3,19 @@ module Hasmin.Types.PositionSpec where  import Data.Text (Text)+import Test.Hspec.QuickCheck (modifyMaxSuccess)  import Hasmin.Parser.Value import Hasmin.TestUtils +import Hasmin.Types.Position+ positionMinificationTests :: Spec positionMinificationTests =-    describe "<position> minification" $-        mapM_ (matchSpec f) positionMinificationTestsInfo-      -- it "Minifies <position> properly" $-      -- it "Minified <position> maintains semantical equivalence" $ do-        -- property (prop_minificationEq :: Position -> Bool)+    describe "<position> minification" $ do+      mapM_ (matchSpec f) positionMinificationTestsInfo+      modifyMaxSuccess (const 200000) . it "Minified <position> maintains semantical equivalence" $+        property (prop_minificationEq :: Position -> Bool)   where f = minifyWithTestConfig <$> position  positionMinificationTestsInfo :: [(Text, Text)]@@ -60,6 +62,7 @@   ,("left 50% top 0%", "top")   ,("left 50% top",    "top")   ,("center top",      "top")+  ,("center 0%",       "top")    ,("100% 0%",         "100% 0")   ,("right top",       "100% 0")@@ -70,6 +73,7 @@   ,("top 0% right",    "100% 0")   ,("top right 0%",    "100% 0")   ,("top right",       "100% 0")+  ,("right 0%",        "100% 0")    ,("100%",             "100%")   ,("100% 50%",         "100%")@@ -130,6 +134,7 @@   ,("20px bottom",         "20px 100%")   ,("right 20px",          "100% 20px")   ,("top 30px right 0",    "100% 30px")+  -- ,("left -1% bottom -1%", "99% 99%") -- TODO   ]  spec :: Spec
tests/Hasmin/Types/RepeatStyleSpec.hs view
@@ -3,7 +3,6 @@ module Hasmin.Types.RepeatStyleSpec where  import Data.Text (Text)-import Control.Applicative (liftA2) import Hasmin.Parser.Value import Hasmin.Types.RepeatStyle import Hasmin.TestUtils@@ -11,7 +10,7 @@ repeatStyleTests :: Spec repeatStyleTests =     describe "<repeat-style> minification tests" $ do-      it "Minified <repeat-style> maintains semantic equivalence" $ +      it "Minified <repeat-style> maintains semantic equivalence" $         property (prop_minificationEq :: RepeatStyle -> Bool)       mapM_ (matchSpec f) repeatStyleTestsInfo   where f = minifyWithTestConfig <$> repeatStyle