packages feed

filepather-0.8.0.0: test/Gens.hs

{-# OPTIONS_GHC -Wall #-}

-- | Generators shared by the property tests.
module Gens (
  genInt,
  genString,
  genPath,
  genIntList,
  genFnInt,
  pathKey,
  genFilePatherList,
  genFilePatherListFn,
  genFilePatherMaybe,
  toFilePather,
  toFilePatherFn,

  -- * Data types
  genName,
  genStep,
  genTrail,
  genRepetition,
  genReproducesInput,
  genRepeatsStep,
  genCycle,
) where

import Control.Lens (review)
import Data.Char (ord)
import Hedgehog (Gen)
import Hedgehog.Function (Fn, apply, fn)
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import System.FilePather

genInt :: Gen Int
genInt =
  Gen.int (Range.linear (-100) 100)

genString :: Gen String
genString =
  Gen.string (Range.linear 0 30) Gen.unicode

{- | An 'Int' that is determined by a file path, so that a generated function of
'Int' is a function of file paths. hedgehog-fn cannot generate functions of
'String', as it has no instance for 'Char'.
-}
pathKey :: FilePath -> Int
pathKey =
  foldr (\c k -> (k * 31 + ord c) `mod` 1000003) 7

-- | A file path, such as @src/a/report.fodt@.
genPath :: Gen FilePath
genPath =
  let part = Gen.string (Range.linear 1 6) Gen.alphaNum
   in (\ds f e -> concatMap (<> "/") ds <> f <> "." <> e)
        <$> Gen.list (Range.linear 0 3) part
        <*> part
        <*> Gen.element ["fodt", "md", "pdf", "png", "txt"]

genIntList :: Gen [Int]
genIntList =
  Gen.list (Range.linear 0 4) genInt

genFnInt :: Gen (Fn Int Int)
genFnInt =
  fn genInt

{- | A function from file paths to lists of 'Int', to make a 'FilePather'
with 'toFilePather'.
-}
genFilePatherList :: Gen (Fn Int [Int])
genFilePatherList =
  fn genIntList

{- | A function from file paths to lists of functions, to make a
'FilePather' with 'toFilePatherFn'.
-}
genFilePatherListFn :: Gen (Fn Int [Fn Int Int])
genFilePatherListFn =
  fn (Gen.list (Range.linear 0 3) genFnInt)

-- | A function from file paths to 'Maybe' 'Int'.
genFilePatherMaybe :: Gen (Fn Int (Maybe Int))
genFilePatherMaybe =
  fn (Gen.maybe genInt)

toFilePather :: Fn Int (f a) -> FilePather f a
toFilePather f =
  FilePather (apply f . pathKey)

toFilePatherFn :: (Functor f) => Fn Int (f (Fn Int Int)) -> FilePather f (Int -> Int)
toFilePatherFn f =
  FilePather (fmap apply . apply f . pathKey)

-- | The name of a step.
genName :: Gen String
genName =
  Gen.element ["prepare", "pdf page 1", "libreoffice pdf", "image png", "book"]

{- | A 'Step': a product, so its constructor applied to a generator of each
field, in the shape of its manifest.
-}
genStep :: Gen Step
genStep =
  Step <$> genName <*> genPath

genTrail :: Gen Trail
genTrail =
  Trail <$> Gen.list (Range.linear 0 3) genStep

{- | A 'Repetition': a sum, so one alternative for each constructor, through
its name.
-}
genRepetition :: Gen Repetition
genRepetition =
  Gen.choice [pure (review once ()), pure (review repeatable ())]

genReproducesInput :: Gen ReproducesInput
genReproducesInput =
  ReproducesInput <$> genPath <*> genStep <*> genTrail

genRepeatsStep :: Gen RepeatsStep
genRepeatsStep =
  RepeatsStep <$> genStep <*> genTrail

genCycle :: Gen Cycle
genCycle =
  Gen.choice [review reproducesInput <$> genReproducesInput, review repeatsStep <$> genRepeatsStep]