foscam-filename (empty) → 0.0.1
raw patch · 15 files changed
+1108/−0 lines, 15 filesdep +QuickCheckdep +basedep +bifunctorsbuild-type:Customsetup-changed
Dependencies added: QuickCheck, base, bifunctors, digit, directory, doctest, filepath, lens, parsec, parsers, semigroupoids, semigroups, template-haskell
Files
- LICENSE +27/−0
- Setup.lhs +44/−0
- changelog +4/−0
- foscam-filename.cabal +84/−0
- src/Data/Foscam/File.hs +16/−0
- src/Data/Foscam/File/Alias.hs +118/−0
- src/Data/Foscam/File/AliasCharacter.hs +75/−0
- src/Data/Foscam/File/Date.hs +99/−0
- src/Data/Foscam/File/DeviceId.hs +112/−0
- src/Data/Foscam/File/DeviceIdCharacter.hs +74/−0
- src/Data/Foscam/File/Filename.hs +129/−0
- src/Data/Foscam/File/ImageId.hs +116/−0
- src/Data/Foscam/File/Internal.hs +40/−0
- src/Data/Foscam/File/Time.hs +85/−0
- test/doctests.hs +85/−0
+ LICENSE view
@@ -0,0 +1,27 @@+Copyright 2015 Tony Morris++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.+3. Neither the name of the author nor the names of his contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+SUCH DAMAGE.
+ Setup.lhs view
@@ -0,0 +1,44 @@+#!/usr/bin/env runhaskell+\begin{code}+{-# OPTIONS_GHC -Wall #-}+module Main (main) where++import Data.List ( nub )+import Data.Version ( showVersion )+import Distribution.Package ( PackageName(PackageName), PackageId, InstalledPackageId, packageVersion, packageName )+import Distribution.PackageDescription ( PackageDescription(), TestSuite(..) )+import Distribution.Simple ( defaultMainWithHooks, UserHooks(..), simpleUserHooks )+import Distribution.Simple.Utils ( rewriteFile, createDirectoryIfMissingVerbose )+import Distribution.Simple.BuildPaths ( autogenModulesDir )+import Distribution.Simple.Setup ( BuildFlags(buildVerbosity), fromFlag )+import Distribution.Simple.LocalBuildInfo ( withLibLBI, withTestLBI, LocalBuildInfo(), ComponentLocalBuildInfo(componentPackageDeps) )+import Distribution.Verbosity ( Verbosity )+import System.FilePath ( (</>) )++main :: IO ()+main = defaultMainWithHooks simpleUserHooks+ { buildHook = \pkg lbi hooks flags -> do+ generateBuildModule (fromFlag (buildVerbosity flags)) pkg lbi+ buildHook simpleUserHooks pkg lbi hooks flags+ }++generateBuildModule :: Verbosity -> PackageDescription -> LocalBuildInfo -> IO ()+generateBuildModule verbosity pkg lbi = do+ let dir = autogenModulesDir lbi+ createDirectoryIfMissingVerbose verbosity True dir+ withLibLBI pkg lbi $ \_ libcfg -> do+ withTestLBI pkg lbi $ \suite suitecfg -> do+ rewriteFile (dir </> "Build_" ++ testName suite ++ ".hs") $ unlines+ [ "module Build_" ++ testName suite ++ " where"+ , "deps :: [String]"+ , "deps = " ++ (show $ formatdeps (testDeps libcfg suitecfg))+ ]+ where+ formatdeps = map (formatone . snd)+ formatone p = case packageName p of+ PackageName n -> n ++ "-" ++ showVersion (packageVersion p)++testDeps :: ComponentLocalBuildInfo -> ComponentLocalBuildInfo -> [(InstalledPackageId, PackageId)]+testDeps xs ys = nub $ componentPackageDeps xs ++ componentPackageDeps ys++\end{code}
+ changelog view
@@ -0,0 +1,4 @@+0.0.1++* Initial version.+
+ foscam-filename.cabal view
@@ -0,0 +1,84 @@+name: foscam-filename+version: 0.0.1+license: BSD3+license-file: LICENSE+author: Tony Morris <ʇǝu˙sıɹɹoɯʇ@ןןǝʞsɐɥ> <dibblego>+maintainer: Tony Morris <ʇǝu˙sıɹɹoɯʇ@ןןǝʞsɐɥ> <dibblego>+copyright: Copyright (C) 2015 Tony Morris+synopsis: Foscam File format+category: Data+description: + Foscam File format++homepage: https://github.com/tonymorris/foscam-filename+bug-reports: https://github.com/tonymorris/foscam-filename+cabal-version: >= 1.10+build-type: Custom+extra-source-files: changelog++source-repository head+ type: git+ location: git@github.com:tonymorris/foscam-filename.git++flag small_base+ description: Choose the new, split-up base package.++library+ default-language:+ Haskell2010++ build-depends:+ base >= 4 && < 5+ , semigroups >= 0.17 && < 0.19+ , semigroupoids >= 5.0 && < 5.1+ , bifunctors >= 5 && < 6.0+ , lens >= 4.0 && < 5+ , parsers >= 0.12 && < 0.13+ , digit >= 0.1.1 && < 0.2++ ghc-options:+ -Wall++ default-extensions:+ NoImplicitPrelude++ hs-source-dirs:+ src++ exposed-modules:+ Data.Foscam.File+ Data.Foscam.File.Alias+ Data.Foscam.File.AliasCharacter+ Data.Foscam.File.Date+ Data.Foscam.File.DeviceId+ Data.Foscam.File.DeviceIdCharacter+ Data.Foscam.File.Internal+ Data.Foscam.File.Filename+ Data.Foscam.File.ImageId+ Data.Foscam.File.Time++test-suite doctests+ type:+ exitcode-stdio-1.0++ main-is:+ doctests.hs++ default-language:+ Haskell2010++ build-depends:+ base >= 4 && < 5+ , doctest >= 0.9.7 && < 0.11+ , filepath >= 1.3 && < 1.5+ , directory >= 1.1 && < 1.3+ , QuickCheck >= 2.0 && < 3.0+ , template-haskell >= 2.8 && < 3.0+ , parsec >= 3.1 && < 4.0++ ghc-options:+ -Wall+ -threaded++ hs-source-dirs:+ test
+ src/Data/Foscam/File.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}++module Data.Foscam.File(+ module F+) where++import Data.Foscam.File.Alias as F+import Data.Foscam.File.AliasCharacter as F+import Data.Foscam.File.Date as F +import Data.Foscam.File.DeviceIdCharacter as F+import Data.Foscam.File.DeviceId as F+import Data.Foscam.File.ImageId as F+import Data.Foscam.File.Filename as F+import Data.Foscam.File.Time as F
+ src/Data/Foscam/File/Alias.hs view
@@ -0,0 +1,118 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}++module Data.Foscam.File.Alias(+ Alias(..)+, AsAlias(..)+, AsAliasHead(..)+, AsAliasTail(..)+, alias+) where++import Control.Applicative(Applicative((<*>)), (<$>))+import Control.Category(Category(id))+import Control.Lens(Optic', Choice, Cons(_Cons), prism', lens, (^?), (#))+import Control.Monad(Monad)+import Data.Eq(Eq)+import Data.Foscam.File.AliasCharacter(AliasCharacter, AsAliasCharacter(_AliasCharacter), aliasCharacter)+import Data.Functor(Functor)+import Data.Maybe(Maybe(Nothing, Just))+import Data.Ord(Ord)+import Data.String(String)+import Data.Traversable(traverse)+import Prelude(Show)+import Text.Parser.Char(CharParsing)+import Text.Parser.Combinators(many, (<?>), try)++-- $setup+-- >>> import Text.Parsec++data Alias =+ Alias+ AliasCharacter+ [AliasCharacter]+ deriving (Eq, Ord, Show)++class AsAlias p f s where+ _Alias ::+ Optic' p f s Alias++instance AsAlias p f Alias where+ _Alias =+ id++instance (Choice p, Applicative f) => AsAlias p f String where+ _Alias =+ prism'+ (\(Alias h t) -> (_AliasCharacter #) <$> (h:t))+ (\s -> case s of + [] -> + Nothing+ h:t ->+ let ch = (^? _AliasCharacter)+ in Alias <$> ch h <*> traverse ch t)++instance Cons Alias Alias AliasCharacter AliasCharacter where+ _Cons = + prism'+ (\(h, Alias h' t) -> Alias h (h':t))+ (\(Alias h t) -> case t of + [] ->+ Nothing+ u:v ->+ Just (h, Alias u v))++class AsAliasHead p f s where+ _AliasHead ::+ Optic' p f s AliasCharacter++instance AsAliasHead p f AliasCharacter where+ _AliasHead =+ id++instance (p ~ (->), Functor f) => AsAliasHead p f Alias where+ _AliasHead =+ lens+ (\(Alias h _) -> h)+ (\(Alias _ t) h -> Alias h t)++class AsAliasTail p f s where+ _AliasTail ::+ Optic' p f s [AliasCharacter]++instance AsAliasTail p f [AliasCharacter] where+ _AliasTail =+ id++instance (p ~ (->), Functor f) => AsAliasTail p f Alias where+ _AliasTail =+ lens+ (\(Alias _ t) -> t)+ (\(Alias h _) t -> Alias h t)++-- |+--+-- >>> parse alias "test" "abcdef"+-- Right (Alias (AliasCharacter 'a') [AliasCharacter 'b',AliasCharacter 'c',AliasCharacter 'd',AliasCharacter 'e',AliasCharacter 'f'])+-- +-- >>> parse alias "test" "abc123"+-- Right (Alias (AliasCharacter 'a') [AliasCharacter 'b',AliasCharacter 'c',AliasCharacter '1',AliasCharacter '2',AliasCharacter '3'])+-- +-- >>> parse alias "test" "abc*123"+-- Right (Alias (AliasCharacter 'a') [AliasCharacter 'b',AliasCharacter 'c'])+-- +-- >>> parse alias "test" "abc*"+-- Right (Alias (AliasCharacter 'a') [AliasCharacter 'b',AliasCharacter 'c'])+-- +-- >>> parse alias "test" ""+-- Left "test" (line 1, column 1):+-- unexpected end of input+-- expecting alias+alias ::+ (Monad f, CharParsing f) =>+ f Alias+alias =+ let a = try aliasCharacter+ in Alias <$> a <*> many a <?> "alias"
+ src/Data/Foscam/File/AliasCharacter.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}++module Data.Foscam.File.AliasCharacter (+ AliasCharacter+, AsAliasCharacter(..)+, aliasCharacter+) where++import Control.Applicative(Applicative(pure), (<$>))+import Control.Category(Category((.), id))+import Control.Lens(Optic', Choice, prism')+import Control.Monad(Monad(fail))+import Data.Char(Char)+import Data.Eq(Eq)+import Data.Foscam.File.Internal(boolj, charP)+import Data.List((++), notElem)+import Data.Ord(Ord)+import Prelude(Show)+import Text.Parser.Char(CharParsing)+import Text.Parser.Combinators((<?>))++-- $setup+-- >>> import Text.Parsec++newtype AliasCharacter = + AliasCharacter Char+ deriving (Eq, Ord, Show)++class AsAliasCharacter p f s where+ _AliasCharacter ::+ Optic' p f s AliasCharacter++instance AsAliasCharacter p f AliasCharacter where+ _AliasCharacter =+ id++instance (Choice p, Applicative f) => AsAliasCharacter p f Char where+ _AliasCharacter =+ prism'+ (\(AliasCharacter c) -> c)+ ((AliasCharacter <$>) . boolj (`notElem` ['/', ':', '*', '?', '"', '<', '>', '(', ')']))++-- todo: _1 to _12 ?? ++-- |+--+-- >>> parse aliasCharacter "test" "2062"+-- Right (AliasCharacter '2')+-- +-- >>> parse aliasCharacter "test" "2"+-- Right (AliasCharacter '2')+-- +-- >>> parse aliasCharacter "test" "abc"+-- Right (AliasCharacter 'a')+-- +-- >>> parse aliasCharacter "test" "*"+-- Left "test" (line 1, column 2):+-- not an alias character: *+-- +-- >>> parse aliasCharacter "test" "<"+-- Left "test" (line 1, column 2):+-- not an alias character: <+-- +-- >>> parse aliasCharacter "test" ""+-- Left "test" (line 1, column 1):+-- unexpected end of input+-- expecting alias character+aliasCharacter ::+ (Monad f, CharParsing f) =>+ f AliasCharacter+aliasCharacter =+ charP (fail . ("not an alias character: " ++) . pure) _AliasCharacter <?> "alias character"+
+ src/Data/Foscam/File/Date.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}++module Data.Foscam.File.Date(+ Date(..)+, AsDate(..)+, date+) where++import Control.Applicative(Applicative((<*>)), (<$>))+import Control.Category(Category(id))+import Control.Lens(Optic', Choice, prism', (^?), (#))+import Control.Monad(Monad)+import Data.Digit(Digit, digitC)+import Data.Eq(Eq)+import Data.Foscam.File.Internal(digitCharacter)+import Data.Maybe(Maybe(Nothing))+import Data.Ord(Ord)+import Data.String(String)+import Text.Parser.Char(CharParsing)+import Text.Parser.Combinators((<?>))+import Prelude(Show)++-- $setup+-- >>> import Text.Parsec++data Date =+ Date+ Digit+ Digit+ Digit+ Digit+ Digit+ Digit+ Digit+ Digit+ deriving (Eq, Ord, Show)+ +class AsDate p f s where+ _Date ::+ Optic' p f s Date++instance AsDate p f Date where+ _Date =+ id++instance (Choice p, Applicative f) => AsDate p f String where+ _Date =+ prism'+ (\(Date d01 d02 d03 d04 d05 d06 d07 d08) -> (digitC #) <$> [d01, d02, d03, d04, d05, d06, d07, d08])+ (\s -> case s of+ [c01, c02, c03, c04, c05, c06, c07, c08] ->+ let f = (^? digitC)+ in Date <$>+ f c01 <*>+ f c02 <*>+ f c03 <*>+ f c04 <*>+ f c05 <*>+ f c06 <*>+ f c07 <*>+ f c08+ _ ->+ Nothing)++-- |+-- +-- >>> parse date "test" "20140508"+-- Right (Date 2 0 1 4 0 5 0 8)+-- +-- >>> parse date "test" "20140508abc"+-- Right (Date 2 0 1 4 0 5 0 8)+-- +-- >>> parse date "test" "201405"+-- Left "test" (line 1, column 7):+-- unexpected end of input+-- expecting digit+-- +-- >>> parse date "test" "201405a9"+-- Left "test" (line 1, column 8):+-- not a digit: a+-- +-- >>> parse date "test" ""+-- Left "test" (line 1, column 1):+-- unexpected end of input+-- expecting date+date ::+ (Monad f, CharParsing f) =>+ f Date+date =+ Date <$>+ digitCharacter <*>+ digitCharacter <*>+ digitCharacter <*>+ digitCharacter <*>+ digitCharacter <*>+ digitCharacter <*>+ digitCharacter <*>+ digitCharacter <?> "date"
+ src/Data/Foscam/File/DeviceId.hs view
@@ -0,0 +1,112 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}++module Data.Foscam.File.DeviceId(+ DeviceId(..)+, AsDeviceId(..)+, deviceId+) where++import Control.Applicative(Applicative((<*>)), (<$>))+import Control.Category(id)+import Control.Lens(Optic', Choice, prism', (^?), ( # ))+import Control.Monad(Monad)+import Data.Eq(Eq)+import Data.Foscam.File.DeviceIdCharacter+import Data.Functor(fmap)+import Data.Maybe(Maybe(Nothing))+import Data.Ord(Ord)+import Data.String(String)+import Text.Parser.Char(CharParsing)+import Text.Parser.Combinators((<?>))+import Prelude(Show)+++-- $setup+-- >>> import Text.Parsec++data DeviceId =+ DeviceId+ DeviceIdCharacter+ DeviceIdCharacter+ DeviceIdCharacter+ DeviceIdCharacter+ DeviceIdCharacter+ DeviceIdCharacter+ DeviceIdCharacter+ DeviceIdCharacter+ DeviceIdCharacter+ DeviceIdCharacter+ DeviceIdCharacter+ DeviceIdCharacter+ deriving (Eq, Ord, Show)++class AsDeviceId p f s where+ _DeviceId ::+ Optic' p f s DeviceId++instance AsDeviceId p f DeviceId where+ _DeviceId =+ id++instance (Choice p, Applicative f) => AsDeviceId p f String where+ _DeviceId =+ prism'+ (\(DeviceId d01 d02 d03 d04 d05 d06 d07 d08 d09 d10 d11 d12) -> fmap (_DeviceIdCharacter #) [d01, d02, d03, d04, d05, d06, d07, d08, d09, d10, d11, d12])+ (\s -> case s of+ [c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11, c12] ->+ let f = (^? _DeviceIdCharacter)+ in DeviceId <$>+ f c01 <*>+ f c02 <*>+ f c03 <*>+ f c04 <*>+ f c05 <*>+ f c06 <*>+ f c07 <*>+ f c08 <*>+ f c09 <*>+ f c10 <*>+ f c11 <*>+ f c12+ _ ->+ Nothing)++-- |+--+-- >>> parse deviceId "test" "AB0934233DEF"+-- Right (DeviceId (DeviceIdCharacter 'A') (DeviceIdCharacter 'B') (DeviceIdCharacter '0') (DeviceIdCharacter '9') (DeviceIdCharacter '3') (DeviceIdCharacter '4') (DeviceIdCharacter '2') (DeviceIdCharacter '3') (DeviceIdCharacter '3') (DeviceIdCharacter 'D') (DeviceIdCharacter 'E') (DeviceIdCharacter 'F'))+-- +-- >>> parse deviceId "test" "AB0934233DEFabc"+-- Right (DeviceId (DeviceIdCharacter 'A') (DeviceIdCharacter 'B') (DeviceIdCharacter '0') (DeviceIdCharacter '9') (DeviceIdCharacter '3') (DeviceIdCharacter '4') (DeviceIdCharacter '2') (DeviceIdCharacter '3') (DeviceIdCharacter '3') (DeviceIdCharacter 'D') (DeviceIdCharacter 'E') (DeviceIdCharacter 'F'))+-- +-- >>> parse deviceId "test" "AB0934233DX"+-- Left "test" (line 1, column 12):+-- not a device ID character: X+-- +-- >>> parse deviceId "test" "AB0934233DEf"+-- Left "test" (line 1, column 13):+-- not a device ID character: f+-- +-- >>> parse deviceId "test" ""+-- Left "test" (line 1, column 1):+-- unexpected end of input+-- expecting device ID+deviceId ::+ (Monad f, CharParsing f) =>+ f DeviceId+deviceId =+ DeviceId <$>+ deviceIdCharacter <*>+ deviceIdCharacter <*>+ deviceIdCharacter <*>+ deviceIdCharacter <*>+ deviceIdCharacter <*>+ deviceIdCharacter <*>+ deviceIdCharacter <*>+ deviceIdCharacter <*>+ deviceIdCharacter <*>+ deviceIdCharacter <*>+ deviceIdCharacter <*>+ deviceIdCharacter <?> "device ID"
+ src/Data/Foscam/File/DeviceIdCharacter.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}++module Data.Foscam.File.DeviceIdCharacter(+ DeviceIdCharacter+, AsDeviceIdCharacter(..)+, deviceIdCharacter+) where++import Control.Applicative(Applicative(pure))+import Control.Category(id, (.))+import Control.Lens(Optic', Choice, prism')+import Control.Monad(Monad(fail))+import Data.Char(Char)+import Data.Eq(Eq)+import Data.Functor(fmap)+import Data.Foscam.File.Internal(boolj, charP)+import Data.List(elem, (++))+import Data.Ord(Ord)+import Text.Parser.Char(CharParsing)+import Text.Parser.Combinators((<?>))+import Prelude(Show)+++-- $setup+-- >>> import Text.Parsec++newtype DeviceIdCharacter = + DeviceIdCharacter Char+ deriving (Eq, Ord, Show)++class AsDeviceIdCharacter p f s where+ _DeviceIdCharacter ::+ Optic' p f s DeviceIdCharacter++instance AsDeviceIdCharacter p f DeviceIdCharacter where+ _DeviceIdCharacter =+ id++instance (Choice p, Applicative f) => AsDeviceIdCharacter p f Char where+ _DeviceIdCharacter =+ prism'+ (\(DeviceIdCharacter c) -> c)+ (fmap DeviceIdCharacter . boolj (`elem` (['A'..'F'] ++ ['0'..'9'])))++-- |+--+-- >>> parse deviceIdCharacter "test" "A"+-- Right (DeviceIdCharacter 'A')+-- +-- >>> parse deviceIdCharacter "test" "0"+-- Right (DeviceIdCharacter '0')+-- +-- >>> parse deviceIdCharacter "test" "0abc"+-- Right (DeviceIdCharacter '0')+-- +-- >>> parse deviceIdCharacter "test" "a"+-- Left "test" (line 1, column 2):+-- not a device ID character: a+-- +-- >>> parse deviceIdCharacter "test" "G"+-- Left "test" (line 1, column 2):+-- not a device ID character: G+--+-- >>> parse deviceIdCharacter "test" ""+-- Left "test" (line 1, column 1):+-- unexpected end of input+-- expecting device ID character +deviceIdCharacter ::+ (Monad f, CharParsing f) =>+ f DeviceIdCharacter+deviceIdCharacter =+ charP (fail . ("not a device ID character: " ++) . pure) _DeviceIdCharacter <?> "device ID character"
+ src/Data/Foscam/File/Filename.hs view
@@ -0,0 +1,129 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}++module Data.Foscam.File.Filename(+ Filename(..)+, AsFilename(..)+, filename+) where++import Control.Applicative(Applicative((<*>)), (<*), (<$>))+import Control.Category(id)+import Control.Lens(Optic', lens)+import Control.Monad(Monad)+import Data.Digit(Digit)+import Data.Eq(Eq)+import Data.Foscam.File.Internal(digitCharacter)+import Data.Foscam.File.Alias(AsAlias(_Alias), Alias, alias)+import Data.Foscam.File.Date(AsDate(_Date), Date, date)+import Data.Foscam.File.DeviceId(AsDeviceId(_DeviceId), DeviceId, deviceId)+import Data.Foscam.File.ImageId(AsImageId(_ImageId), ImageId, imageId)+import Data.Foscam.File.Time(AsTime(_Time), Time, time)+import Data.Functor(Functor)+import Data.Ord(Ord)+import Text.Parser.Char(CharParsing, char, string)+import Text.Parser.Combinators((<?>))+import Prelude(Show)++-- $setup+-- >>> import Text.Parsec++data Filename =+ Filename+ DeviceId+ Alias+ Digit+ Date+ Time+ ImageId+ deriving (Eq, Ord, Show)++class AsFilename p f s where+ _Filename ::+ Optic' p f s Filename++instance AsFilename p f Filename where+ _Filename =+ id++instance (p ~ (->), Functor f) => AsDeviceId p f Filename where+ _DeviceId =+ lens+ (\(Filename i _ _ _ _ _) -> i)+ (\(Filename _ a x d t m) i -> Filename i a x d t m)++instance (p ~ (->), Functor f) => AsAlias p f Filename where+ _Alias =+ lens+ (\(Filename _ a _ _ _ _) -> a)+ (\(Filename i _ x d t m) a -> Filename i a x d t m)++instance (p ~ (->), Functor f) => AsDate p f Filename where+ _Date =+ lens+ (\(Filename _ _ _ d _ _) -> d)+ (\(Filename i a x _ t m) d -> Filename i a x d t m)++instance (p ~ (->), Functor f) => AsTime p f Filename where+ _Time =+ lens+ (\(Filename _ _ _ _ t _) -> t)+ (\(Filename i a x d _ m) t -> Filename i a x d t m)++instance (p ~ (->), Functor f) => AsImageId p f Filename where+ _ImageId =+ lens+ (\(Filename _ _ _ _ _ m) -> m)+ (\(Filename i a x d t _) m -> Filename i a x d t m)++-- |+--+-- >>> parse Filename "test" "00626E44C831(house)_1_20150209134121_2629.jpg"+-- Right (Filename (DeviceId (DeviceIdCharacter '0') (DeviceIdCharacter '0') (DeviceIdCharacter '6') (DeviceIdCharacter '2') (DeviceIdCharacter '6') (DeviceIdCharacter 'E') (DeviceIdCharacter '4') (DeviceIdCharacter '4') (DeviceIdCharacter 'C') (DeviceIdCharacter '8') (DeviceIdCharacter '3') (DeviceIdCharacter '1')) (Alias (AliasCharacter 'h') [AliasCharacter 'o',AliasCharacter 'u',AliasCharacter 's',AliasCharacter 'e']) 1 (Date 2 0 1 5 0 2 0 9) (Time 1 3 4 1 2 1) (ImageId 2 [6,2,9]))+--+-- >>> parse Filename "test" "00626E44C829(garage)_1_20140313234556_2660.jpg"+-- Right (Filename (DeviceId (DeviceIdCharacter '0') (DeviceIdCharacter '0') (DeviceIdCharacter '6') (DeviceIdCharacter '2') (DeviceIdCharacter '6') (DeviceIdCharacter 'E') (DeviceIdCharacter '4') (DeviceIdCharacter '4') (DeviceIdCharacter 'C') (DeviceIdCharacter '8') (DeviceIdCharacter '2') (DeviceIdCharacter '9')) (Alias (AliasCharacter 'g') [AliasCharacter 'a',AliasCharacter 'r',AliasCharacter 'a',AliasCharacter 'g',AliasCharacter 'e']) 1 (Date 2 0 1 4 0 3 1 3) (Time 2 3 4 5 5 6) (ImageId 2 [6,6,0]))+--+-- >>> parse Filename "test" "00626E44C82x(garage)_1_20140313234556_2660.jpg"+-- Left "test" (line 1, column 13):+-- not a device ID character: x+--+-- >>> parse Filename "test" "00626E44C829(gara*ge)_1_20140313234556_2660.jpg"+-- Left "test" (line 1, column 19):+-- not an alias character: *+--+-- >>> parse Filename "test" "00626E44C829(garage) 1_20140313234556_2660.jpg"+-- Left "test" (line 1, column 20):+-- unexpected " "+-- expecting ")_"+--+-- >>> parse Filename "test" "00626E44C829(garage)_x_20140313234556_2660.jpg"+-- Left "test" (line 1, column 23):+-- not a digit: x+--+-- >>> parse Filename "test" "00626E44C829(garage)_1 20140313234556_2660.jpg"+-- Left "test" (line 1, column 23):+-- unexpected " "+-- expecting "_"+--+-- >>> parse Filename "test" "00626E44C829(garage)_1_x0140313234556_2660.jpg"+-- Left "test" (line 1, column 25):+-- not a digit: x+filename ::+ (Monad f, CharParsing f) =>+ f Filename+filename =+ Filename <$>+ deviceId <*+ char '(' <*>+ alias <*+ string ")_" <*>+ digitCharacter <*+ char '_' <*>+ date <*>+ time <*+ char '_' <*>+ imageId <*+ string ".jpg" <?> "file"
+ src/Data/Foscam/File/ImageId.hs view
@@ -0,0 +1,116 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}++module Data.Foscam.File.ImageId(+ ImageId(..)+, AsImageId(..)+, imageId+) where++import Control.Applicative(Applicative((<*>)), (<$>))+import Control.Category(id)+import Control.Lens(Cons(_Cons), Optic', Choice, prism', lens, (^?), ( # ))+import Control.Monad(Monad)+import Data.Digit(Digit, digitC)+import Data.Eq(Eq)+import Data.Foscam.File.Internal(digitCharacter)+import Data.Functor(Functor)+import Data.Maybe(Maybe(Nothing, Just))+import Data.Ord(Ord)+import Data.String(String)+import Data.Traversable(traverse)+import Text.Parser.Char(CharParsing)+import Text.Parser.Combinators((<?>), try, many)+import Prelude(Show)++-- $setup+-- >>> import Text.Parsec++data ImageId =+ ImageId+ Digit+ [Digit]+ deriving (Eq, Ord, Show)++class AsImageId p f s where+ _ImageId ::+ Optic' p f s ImageId++instance AsImageId p f ImageId where+ _ImageId =+ id++instance (Choice p, Applicative f) => AsImageId p f String where+ _ImageId =+ prism'+ (\(ImageId h t) -> (digitC #) <$> (h:t))+ (\s -> case s of + [] -> + Nothing+ h:t ->+ let ch = (^? digitC)+ in ImageId <$> ch h <*> traverse ch t)++instance Cons ImageId ImageId Digit Digit where+ _Cons = + prism'+ (\(h, ImageId h' t) -> ImageId h (h':t))+ (\(ImageId h t) -> case t of + [] ->+ Nothing+ u:v ->+ Just (h, ImageId u v))++class AsImageIdHead p f s where+ _ImageIdHead ::+ Optic' p f s Digit++instance AsImageIdHead p f Digit where+ _ImageIdHead =+ id++instance (p ~ (->), Functor f) => AsImageIdHead p f ImageId where+ _ImageIdHead =+ lens+ (\(ImageId h _) -> h)+ (\(ImageId _ t) h -> ImageId h t)++class AsImageIdTail p f s where+ _ImageIdTail ::+ Optic' p f s [Digit]++instance AsImageIdTail p f [Digit] where+ _ImageIdTail =+ id++instance (p ~ (->), Functor f) => AsImageIdTail p f ImageId where+ _ImageIdTail =+ lens+ (\(ImageId _ t) -> t)+ (\(ImageId h _) t -> ImageId h t)++-- |+--+-- >>> parse imageId "test" "2062"+-- Right (ImageId 2 [0,6,2])+--+-- >>> parse imageId "test" "2"+-- Right (ImageId 2 [])+--+-- >>> parse imageId "test" "a"+-- Left "test" (line 1, column 2):+-- expecting image ID+-- not a digit: a+--+-- >>> parse imageId "test" ""+-- Left "test" (line 1, column 1):+-- unexpected end of input+-- expecting image ID+imageId ::+ (Monad f, CharParsing f) =>+ f ImageId+imageId =+ let i = try digitCharacter+ in ImageId <$> i <*> many i <?> "image ID"
+ src/Data/Foscam/File/Internal.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE NoImplicitPrelude #-}++module Data.Foscam.File.Internal(+ charP+, digitCharacter+, boolj+) where++import Control.Applicative(pure)+import Control.Category((.))+import Control.Monad(Monad((>>=), fail))+import Data.Bool(bool, Bool)+import Data.Char(Char)+import Data.Digit(Digit, digitC)+import Data.Maybe(Maybe(Nothing, Just), maybe)+import Data.Monoid(First, (<>))+import Control.Lens(Getting, (^?))+import Text.Parser.Char(CharParsing, anyChar)+import Text.Parser.Combinators((<?>))++charP :: + (Monad f, CharParsing f) =>+ (Char -> f a)+ -> Getting (First a) Char a+ -> f a+charP fl p =+ anyChar >>= \c -> maybe (fl c) pure (c ^? p) ++digitCharacter ::+ (Monad f, CharParsing f) =>+ f Digit+digitCharacter =+ charP (fail . ("not a digit: " <>) . pure) digitC <?> "digit"++boolj ::+ (a -> Bool)+ -> a+ -> Maybe a+boolj p x = + bool Nothing (Just x) (p x)
+ src/Data/Foscam/File/Time.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}++module Data.Foscam.File.Time(+ Time(..)+, AsTime(..)+, time+) where++import Control.Applicative(Applicative((<*>)), (<$>))+import Control.Category(id)+import Control.Lens(Optic', Choice, prism', (^?), ( # ))+import Control.Monad(Monad)+import Data.Digit(Digit, digitC)+import Data.Eq(Eq)+import Data.Foscam.File.Internal(digitCharacter)+import Data.Maybe(Maybe(Nothing))+import Data.Ord(Ord)+import Data.String(String)+import Text.Parser.Char(CharParsing)+import Text.Parser.Combinators((<?>))+import Prelude(Show)++-- $setup+-- >>> import Text.Parsec++data Time =+ Time+ Digit+ Digit+ Digit+ Digit+ Digit+ Digit+ deriving (Eq, Ord, Show)++class AsTime p f s where+ _Time ::+ Optic' p f s Time++instance AsTime p f Time where+ _Time =+ id++instance (Choice p, Applicative f) => AsTime p f String where+ _Time =+ prism'+ (\(Time d01 d02 d03 d04 d05 d06) -> (digitC #) <$> [d01, d02, d03, d04, d05, d06])+ (\s -> case s of+ [c01, c02, c03, c04, c05, c06] ->+ let f = (^? digitC)+ in Time <$>+ f c01 <*>+ f c02 <*>+ f c03 <*>+ f c04 <*>+ f c05 <*>+ f c06+ _ ->+ Nothing)++-- |+--+-- >>> parse time "test" "134122"+-- Right (Time 1 3 4 1 2 2)+--+-- >>> parse time "test" "134122abc"+-- Right (Time 1 3 4 1 2 2)+--+-- >>> parse time "test" "1341"+-- Left "test" (line 1, column 5):+-- unexpected end of input+-- expecting digit+time ::+ (Monad f, CharParsing f) =>+ f Time+time =+ Time <$>+ digitCharacter <*>+ digitCharacter <*>+ digitCharacter <*>+ digitCharacter <*>+ digitCharacter <*>+ digitCharacter <?> "time"
+ test/doctests.hs view
@@ -0,0 +1,85 @@+module Main where++import Control.Applicative+import Prelude+import Build_doctests (deps)+import Control.Monad+import Data.List+import Data.Monoid+import System.Directory+import System.FilePath+import System.IO+import Test.DocTest++main ::+ IO ()+main =+ getSources >>= \sources ->+ forM_ (preferredOrderFirst sources) $ \source -> do+ hPutStrLn stderr $ "Testing " <> source+ doctest $+ "-isrc"+ : "-idist/build/autogen"+ : "-optP-include"+ : "-optPdist/build/autogen/cabal_macros.h"+ : "-hide-all-packages"+ : map ("-package="++) deps ++ [source]++sourceDirectories ::+ [FilePath]+sourceDirectories =+ [+ "src"+ ]++preferredOrderFirst :: [FilePath] -> [FilePath]+preferredOrderFirst sources =+ filter (`elem` sources ) preferredOrder+ <> filter (`notElem` preferredOrder) sources++-- If you find the tests are running slowly.+-- Comment out the Modules you have completed+-- in the list below.+preferredOrder :: [String]+preferredOrder = map (\f -> "src/Course" </> f <.> "hs") [+ "List"+ , "Functor"+ , "Apply"+ , "Applicative"+ , "Bind"+ , "Monad"+ , "FileIO"+ , "State"+ , "StateT"+ , "Extend"+ , "Comonad"+ , "Compose"+ , "Traversable"+ , "ListZipper"+ , "Parser"+ , "MoreParser"+ , "JsonParser"+ , "Interactive"+ , "Anagrams"+ , "FastAnagrams"+ , "Cheque"+ ]++isSourceFile ::+ FilePath+ -> Bool+isSourceFile p =+ and [takeFileName p /= "Setup.hs", isSuffixOf ".hs" p]++getSources :: IO [FilePath]+getSources =+ liftM (filter isSourceFile . concat) (mapM go sourceDirectories)+ where+ go dir = do+ (dirs, files) <- getFilesAndDirectories dir+ (files ++) . concat <$> mapM go dirs++getFilesAndDirectories :: FilePath -> IO ([FilePath], [FilePath])+getFilesAndDirectories dir = do+ c <- map (dir </>) . filter (`notElem` ["..", "."]) <$> getDirectoryContents dir+ (,) <$> filterM doesDirectoryExist c <*> filterM doesFileExist c