From 33b78f1df2c55e8bdc6d2b51bdbe7666a8ad9fbb Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Tue, 3 Jun 2025 04:05:53 +0200 Subject: [PATCH] add: Rosetta --- REUSE.toml | 8 +- data/.gitignore | 3 + data/styles/rosetta.css | 128 ++++++ flake.nix | 2 +- src/Prelude.hs | 301 ++++++++++++-- src/Worksheets/Writing/Latin.hs | 49 --- src/Worksheets/Writing/Rosetta.hs | 387 ++++++++++++++++++ style/worksheet.css | 44 -- tests/Spec.hs | 6 +- tests/Worksheets/Writing/LatinSpec.hs | 57 --- .../FarOverTheMistyMountainsCold/full.txt | 129 ++++++ .../FarOverTheMistyMountainsCold/part1.txt | 4 + .../LatinSpec/frozen/in-summer/en/part1.html | 19 - .../LatinSpec/frozen/in-summer/en/part2.html | 19 - .../LatinSpec/frozen/in-summer/en/part2.txt | 3 +- .../LatinSpec/frozen/in-summer/en/part3.html | 15 - .../LatinSpec/frozen/in-summer/en/part4.html | 15 - .../LatinSpec/frozen/let-it-go/en/part1.html | 21 - .../LatinSpec/frozen/let-it-go/en/part2.html | 21 - .../LatinSpec/frozen/let-it-go/fr/part1.html | 19 - .../LatinSpec/frozen/let-it-go/fr/part2.html | 13 - .../LatinSpec/frozen/let-it-go/fr/part3.html | 19 - .../LatinSpec/frozen/let-it-go/fr/part4.html | 15 - .../LatinSpec/frozen/let-it-go/fr/part5.html | 9 - .../LatinSpec/frozen/let-it-go/fr/part6.html | 13 - .../LatinSpec/frozen/let-it-go/fr/part7.html | 19 - worksheets.cabal | 12 +- 27 files changed, 934 insertions(+), 416 deletions(-) create mode 100644 data/.gitignore create mode 100644 data/styles/rosetta.css delete mode 100644 src/Worksheets/Writing/Latin.hs create mode 100644 src/Worksheets/Writing/Rosetta.hs delete mode 100644 style/worksheet.css delete mode 100644 tests/Worksheets/Writing/LatinSpec.hs create mode 100644 tests/Worksheets/Writing/LatinSpec/TheHobbit/FarOverTheMistyMountainsCold/full.txt create mode 100644 tests/Worksheets/Writing/LatinSpec/TheHobbit/FarOverTheMistyMountainsCold/part1.txt delete mode 100644 tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part1.html delete mode 100644 tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part2.html delete mode 100644 tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part3.html delete mode 100644 tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part4.html delete mode 100644 tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/en/part1.html delete mode 100644 tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/en/part2.html delete mode 100644 tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part1.html delete mode 100644 tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part2.html delete mode 100644 tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part3.html delete mode 100644 tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part4.html delete mode 100644 tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part5.html delete mode 100644 tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part6.html delete mode 100644 tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part7.html diff --git a/REUSE.toml b/REUSE.toml index 3263052..4009efd 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -4,7 +4,7 @@ SPDX-PackageSupplier = "Julien Moutinho " SPDX-PackageDownloadLocation = "https://git.sourcephile.fr/haskell/worksheets" [[annotations]] -path = ["**.nix", "**.lock", "cabal.project", "**.cabal", "**.md", "**.toml", ".chglog/**", ".envrc", "fourmolu.yaml", ".gitignore", ".hlint.yaml", "Makefile"] +path = [".ghci", "cabal.project*", "**.nix", "**.lock", "cabal.project", "**.cabal", "**.md", "**.toml", ".chglog/**", ".envrc", "fourmolu.yaml", ".gitignore", ".hlint.yaml", "Makefile"] precedence = "aggregate" SPDX-FileCopyrightText = "Julien Moutinho " SPDX-License-Identifier = "CC0-1.0" @@ -14,3 +14,9 @@ path = ["src/**", "style/**", "tests/**", "app/**"] precedence = "aggregate" SPDX-FileCopyrightText = "Julien Moutinho " SPDX-License-Identifier = "AGPL-3.0-or-later" + +[[annotations]] +path = ["data/**"] +precedence = "aggregate" +SPDX-FileCopyrightText = "???" +SPDX-License-Identifier = "CC0-1.0" diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 0000000..7aa981d --- /dev/null +++ b/data/.gitignore @@ -0,0 +1,3 @@ +*.avif +*.jpg +*.webp diff --git a/data/styles/rosetta.css b/data/styles/rosetta.css new file mode 100644 index 0000000..ab54140 --- /dev/null +++ b/data/styles/rosetta.css @@ -0,0 +1,128 @@ +body { + line-height:1.4; + margin-top:0; + margin-left:0; + margin-right:0; + //width:650px; + width:100%; + display: grid; + grid-template-columns: 1 / -1; + justify-items: center; + justify-content:center; +} + +.rosetta { + margin-top:1ex; + margin-bottom:1ex; + margin-left:1ex; + margin-right:1ex; + display:flex; + flex-direction: column; + flex-wrap: wrap; + justify-content:space-evenly; + align-items:flex-start; + align-content:space-around; +} +.rosetta-row { + display: grid; + break-inside:avoid; + grid-template-columns: 6cm 11cm; + box-shadow:0 0 0 1px #AAA; +} +.rosetta-word:last-child { + margin-bottom:0; + margin-right:0; +} +.rosetta-cell { +} +.rosetta-cell-picture { + width: fit-content; + place-content:center; + //block-size:fit-content; + //box-sizing: fit-content; + //width:auto; + //max-height:100%; + //background-color:white; +} +.rosetta-cell-picture img { + display:block; + border:0; + place-content:center; + vertical-align:center; + width: 100%; + object-fit: contain; + object-position: center center; +} +.rosetta-cell.rosetta-cell-words { + +} + +.writing-words { + margin-top:1ex; + margin-bottom:1ex; + margin-left:1ex; + margin-right:1ex; + display:flex; + flex-direction: row; + flex-wrap: wrap; + justify-content:space-evenly; + align-items:flex-start; + align-content:space-around; +} +.writing-words-word { + margin-left: 10px; + margin-right: 10px; + margin-bottom: 1ex; + margin-top: 1ex; +} +.writing-words-row { + white-space:preserve-spaces; + //width:100%; + font-family: monospace; + font-variant: small-caps; + display: grid; + gap: 0px; + //justify-items: center; + justify-self: center; + //background-color:grey; + place-content: center; +} +.writing-words-cell { + display: grid; + place-content: center; + position: relative; + box-shadow:0 0 0 1px #AAA; + background-color:white; + min-height:1cm; + font-size: 20pt; + padding-left:0; + padding-right:0; + margin-left:0; + margin-right:0; +} +.writing-words-cell-space { + box-shadow:0 0 0 0 red !important; + border-left:1px solid #AAA; +} + +.writing-words-row.writing-words-row-input { + color:#ddd; +} +.writing-words-row.writing-words-LangueMandarin .writing-words-cell { + font-size:30pt; +} +.writing-words-row.writing-words-LangueMandarinPinyin .writing-words-cell { + color:black; + font-size:12pt; + font-variant:normal; +} + +@media all { + @page { + size: 210mm 297mm; + margin-top: 1cm; + margin-bottom: 1cm; + margin-left: 2cm; + margin-right: 2cm; + } +} diff --git a/flake.nix b/flake.nix index 31d64e5..9c0c63a 100644 --- a/flake.nix +++ b/flake.nix @@ -20,7 +20,7 @@ #./Readme.md ./LICENSES (fileFilter (file: lib.any file.hasExt [ "hs" ]) ./src) - (fileFilter (file: lib.any file.hasExt [ "css" ]) ./style) + (fileFilter (file: lib.any file.hasExt [ "css" ]) ./data/styles) (fileFilter (file: lib.any file.hasExt [ "hs" "html" "txt" ]) ./tests) #(fileFilter (file: lib.any file.hasExt [ "hs" ]) ./app) ]; diff --git a/src/Prelude.hs b/src/Prelude.hs index 03329f8..a99e280 100644 --- a/src/Prelude.hs +++ b/src/Prelude.hs @@ -1,45 +1,268 @@ +{-# LANGUAGE FieldSelectors #-} {-# LANGUAGE PackageImports #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE NoMonomorphismRestriction #-} +{-# OPTIONS_GHC -Wno-orphans #-} module Prelude ( - -- module BasePrelude - -- module Prelude, - Bounded (..), - Double, - Enum, - Fractional (..), - Integral (..), - Num (..), - Rational, - Real, - error, - FilePath, - fromIntegral, - IsString (..), - String, - toRational, + traceString, + traceShow, + traceShowId, + traceShowM, + IsList (..), + pattern (:=), + forMap, + setSingle, + setInsert, + setSize, + mapSize, + mapLookup, + DropPrefix (..), + ($), + (<$>), + ($>), + (<&>), + (.), + (&), + (&&), + (||), + (>>>), + (++), + id, + const, + on, + fold, + foldr, + foldMap, + foldMapM, + Foldable, + forM, + forM_, + null, + any, + all, + or, + and, + not, + flip, + first, + second, + void, + Bool (..), + otherwise, + Enum (..), + Eq (..), + Ord (..), + Show (..), + Either (..), + either, + Generic, + IsString, + fromString, + fromRational, + HasCallStack, IO, + FilePath, Char, + -- Hashable (..), + ShortText, + Set, + String, + Natural, + Int, + Integer, + Map, + Maybe (..), + fromMaybe, + maybe, + maybeToList, + isJust, + isNothing, + Functor (..), + Down (..), + Sum (..), + Identity (..), + Applicative (..), + Semigroup (..), + Monoid (..), Monad (..), - forM, - forM_, - (++), - (>>>), - module Data.Bool, - module Data.Eq, - module Data.Function, - module Data.Functor, - module Data.Ord, - module Data.Semigroup, -) -where - -import Control.Arrow -import Control.Monad -import Data.Bool -import Data.Eq -import Data.Function -import Data.Functor -import Data.Ord -import Data.Semigroup -import Data.String -import "base" Prelude as BasePrelude + unless, + when, + Symbol, + KnownSymbol (..), + KnownNat (..), + Real (..), + Num (..), + natVal, + symbolVal, + pShow, + pShowNoColor, + IsLabel (..), + Proxy (..), + Last (..), + -- Surgery, + -- Surgeries, + -- ProductSurgery, + -- ProductSurgeries, + -- Surgery' (..), + -- Generically (..), + -- GenericProduct (..), + -- Derecordify, + -- OnField, + -- OnFields, + -- CopyRep, + -- type (%~), + Typeable, + fst, + snd, + maximum, + minimum, + Max (..), + Min (..), +) where + +import Control.Applicative (Applicative (..)) +import Control.Arrow (first, second, (>>>)) +import Control.Monad (Monad (..), forM, forM_, unless, void, when) +import Data.Bool (Bool (..), not, otherwise, (&&), (||)) +import Data.Char (Char) +import Data.Either (Either (..), either) +import Data.Eq (Eq (..)) +import Data.Foldable (Foldable, all, and, any, fold, foldMap, foldr, maximum, minimum, null, or) +import Data.Function (const, flip, id, on, ($), (&), (.)) +import Data.Functor (Functor (..), ($>), (<$), (<$>), (<&>)) +import Data.Functor.Identity (Identity (..)) +import Data.List ((++)) +import Data.Map.Strict (Map) +import Data.Map.Strict qualified as Map +import Data.Maybe (Maybe (..), fromMaybe, isJust, isNothing, maybe, maybeToList) +import Data.Monoid (Ap (..), Last (..), Monoid (..)) +import Data.Ord (Down (..), Ord (..)) +import Data.Proxy (Proxy (..)) +import Data.Semigroup (Max (..), Min (..), Semigroup (..), Sum (..)) +import Data.Set (Set) +import Data.Set qualified as Set +import Data.String (IsString, String, fromString) +import Data.Text qualified as Text +import Data.Text.Short (ShortText) +import Data.Text.Short qualified as ShortText +import Data.Tuple (fst, snd) +import Debug.Pretty.Simple (pTrace, pTraceShow, pTraceShowId, pTraceShowM) +import GHC.Generics (Generic) +import GHC.IsList (IsList (..), toList) +import GHC.OverloadedLabels (IsLabel (..)) +import GHC.Stack (HasCallStack) +import GHC.TypeLits (KnownNat (..), KnownSymbol (..), Symbol, natVal, symbolVal) +import System.IO (FilePath, IO) + +-- import Generic.Data.Microsurgery ( +-- CopyRep, +-- Derecordify, +-- GenericProduct (..), +-- Generically (..), +-- OnField, +-- OnFields, +-- ProductSurgeries, +-- ProductSurgery, +-- Surgeries, +-- Surgery, +-- Surgery' (..), +-- type (%~), +-- ) +import Numeric.Natural (Natural) + +-- import Optics.Core +import Text.Pretty.Simple (pShow, pShowNoColor) +import Text.Show (Show (..)) +import Type.Reflection (Typeable) +import "base" Prelude (Enum (..), Int, Integer, Num (..), Real (..), fromRational) + +traceString = pTrace +traceShow = pTraceShow +traceShowId = pTraceShowId +traceShowM = pTraceShowM + +pattern (:=) :: a -> b -> (a, b) +pattern (:=) x y = (x, y) +infixr 0 := + +class Assoc a b c where + (~>) :: a -> b -> c +instance Assoc a b (a, b) where + (~>) = (,) + +(<&) :: Functor f => a -> f b -> f a +(&>) :: Functor f => f b -> a -> f a +(<&) = flip ($>) +(&>) = flip (<$) +infixl 4 <& +infixl 4 &> + +-- l <>~ n = over l (<> n) +-- {-# INLINE (<>~) #-} + +instance IsList a => IsList (Last a) where + type Item (Last a) = Item a + fromList [] = Last Nothing + fromList xs = Last (Just (fromList xs)) + toList (Last Nothing) = [] + toList (Last (Just x)) = toList x + +-- | Like `Last` but `mempty` is `[]` instead not `Nothing`. +-- Useful for deriving: +-- +-- @ +-- deriving Semigroup via (ProductSurgery (OnFields Lasts) Foo) +-- @ +newtype Lasts a = Lasts a + deriving (Eq, Ord, Show, Generic) + +instance Semigroup (Lasts [a]) where + Lasts [] <> x = x + x <> Lasts [] = x + _x <> y = y +instance (Monoid a, Semigroup (Lasts a)) => Monoid (Lasts a) where + mempty = Lasts mempty + +newtype Newest a = Newest {unNewest :: a} + deriving (Eq, Ord, Generic) + deriving newtype (Show) +instance Semigroup (Newest a) where + _x <> y = y + +newtype MapUnion k a = MapUnion {unMapUnion :: Map.Map k a} + deriving (Eq, Ord, Generic, Functor) + deriving newtype (Show) + +-- CorrectionWarning: `Monoid` is not derived correctly via `Generically`, +-- it does not reuses `(<>)`. +-- See https://github.com/haskell/core-libraries-committee/issues/324 +-- deriving (Monoid) via (Generically (MapUnion k a)) +instance (Ord k, Semigroup a) => Semigroup (MapUnion k a) where + MapUnion x <> MapUnion y = MapUnion (Map.unionWith (<>) x y) +instance (Ord k, Semigroup a) => Monoid (MapUnion k a) where + mempty = MapUnion mempty + +forMap :: (Foldable t, Monoid m) => t a -> (a -> m) -> m +forMap = flip foldMap + +class DropPrefix a where + dropPrefix :: a -> a -> a +instance DropPrefix Text.Text where + dropPrefix p t = t & Text.stripPrefix p & fromMaybe t +instance DropPrefix ShortText where + dropPrefix p t = t & ShortText.stripPrefix p & fromMaybe t + +setSingle = Set.singleton +{-# INLINE setSingle #-} +setInsert = Set.insert +{-# INLINE setInsert #-} +setSize = Set.size +{-# INLINE setSize #-} +mapSize = Map.size +{-# INLINE mapSize #-} +mapLookup = Map.lookup +{-# INLINE mapLookup #-} + +foldMapM :: (Applicative m, Foldable t, Monoid b) => (a -> m b) -> t a -> m b +foldMapM f = getAp <$> foldMap (Ap . f) diff --git a/src/Worksheets/Writing/Latin.hs b/src/Worksheets/Writing/Latin.hs deleted file mode 100644 index fd3b67c..0000000 --- a/src/Worksheets/Writing/Latin.hs +++ /dev/null @@ -1,49 +0,0 @@ -module Worksheets.Writing.Latin where - -import Data.ByteString.Builder (Builder) -import Data.Text qualified as Text -import Data.Text.IO.Utf8 qualified as Text -import Paths_worksheets qualified as Self -import System.FilePath.Posix qualified as File -import Text.Blaze -import Text.Blaze.Html5 qualified as H -import Text.Blaze.Html5.Attributes qualified as HA -import Text.Blaze.Renderer.Utf8 qualified as Blaze -import Prelude - --- import Debug.Pretty.Simple (pTraceShowM) - -worksheet :: File.FilePath -> IO Builder -worksheet inputFilePath = do - inputText <- Text.readFile inputFilePath - let inputLines = Text.lines inputText - -- FIXME: this absolute path is not reproducible out of my system - cssPath <- Self.getDataFileName "worksheet.css" <&> File.normalise - return $ Blaze.renderMarkupBuilder do - H.docTypeHtml do - H.head do - H.title $ fromString $ File.takeBaseName inputFilePath - H.link - ! HA.rel "stylesheet" - ! HA.type_ "text/css" - ! HA.href (toValue cssPath) - H.body do - "\n" - H.div ! HA.class_ "worksheet" $ do - forM_ inputLines \inputLine -> do - H.div ! HA.class_ "row" $ do - forM_ (Text.unpack inputLine) \inputChar -> do - let cellSpace - | inputChar == ' ' = " cell-space" - | otherwise = "" - H.div ! HA.class_ ("cell" <> cellSpace) $ do - fromString [inputChar] - "\n" - H.div ! HA.class_ "row" $ do - forM_ (Text.unpack inputLine) \inputChar -> do - let cellSpace - | inputChar == ' ' = " cell-space" - | otherwise = "" - H.div ! HA.class_ ("cell" <> cellSpace) $ do - fromString " " - "\n" diff --git a/src/Worksheets/Writing/Rosetta.hs b/src/Worksheets/Writing/Rosetta.hs new file mode 100644 index 0000000..a36e5f8 --- /dev/null +++ b/src/Worksheets/Writing/Rosetta.hs @@ -0,0 +1,387 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE PackageImports #-} +{-# OPTIONS_GHC -Wno-unused-imports #-} + +module Worksheets.Writing.Rosetta where + +import Control.Applicative (Alternative (..), Applicative (..)) +import Control.Monad (when) +import Control.Monad.Trans.State qualified as MT +import Data.ByteString (ByteString) +import Data.ByteString qualified as ByteString +import Data.ByteString.Builder (Builder) +import Data.ByteString.Short qualified as ShortByteString +import Data.Char qualified as Char +import Data.Csv ((.!)) +import Data.Csv qualified as CSV +import Data.Csv.Incremental qualified as CSV.Incremental +import Data.Foldable (Foldable (..)) +import Data.Int (Int) +import Data.List qualified as List +import Data.Map.Strict qualified as Map +import Data.Maybe (Maybe (..)) +import Data.Monoid (Ap (..), Monoid (..)) +import Data.Text (Text) +import Data.Text qualified as Text +import Data.Text.Encoding qualified as Text +import Data.Text.Lazy qualified as Text.Lazy +import Data.Text.Short (ShortText) +import Data.Text.Short qualified as ShortText +import Debug.Pretty.Simple (pTraceShow, pTraceShowId, pTraceShowM) +import GHC.Generics (Generic) +import Paths_worksheets qualified as Self +import System.Exit qualified as Sys +import System.FilePath qualified as Sys +import System.FilePath.Posix (()) +import System.FilePath.Posix qualified as File +import System.IO qualified as Sys +import Text.Blaze +import Text.Blaze.Html5 qualified as H +import Text.Blaze.Html5.Attributes qualified as HA +import Text.Blaze.Renderer.Utf8 qualified as Blaze +import Text.Show (Show (..)) +import Utils.Blaze +import Prelude +import "base" Prelude (error, fromIntegral) + +newtype ChineseDict = ChineseDict (Map ShortText [ChineseDictEntry]) + deriving (Show) +instance Semigroup ChineseDict where + ChineseDict x <> ChineseDict y = + ChineseDict (Map.unionWithKey merge x y) + where + merge _k !xV !yV = xV <> yV + +-- xV +-- & traceString (List.unlines $ +-- [ "Semigroup ChineseDict: key collision: " <> ShortText.unpack k <> " ("<>show k<>")" +-- , xV & pShowNoColor & Text.Lazy.unpack +-- , yV & pShowNoColor & Text.Lazy.unpack +-- ]) +instance Monoid ChineseDict where + mempty = ChineseDict mempty +data ChineseDictEntry = ChineseDictEntry + { chinese :: !ShortText + , pinyins :: ![ShortText] + , english :: ![ShortText] + , hskLevel :: !(Maybe HskLevel) + } + deriving (Generic, Show) + +data HskLevel + = HskLevel301 + | HskLevel302 + | HskLevel303 + | HskLevel304 + | HskLevel305 + | HskLevel306 + deriving (Eq, Ord, Enum, Show) + +-- instance CSV.FromRecord ChineseDictEntry +-- instance CSV.ToRecord ChineseDictEntry +-- instance CSV.FromNamedRecord ChineseDictEntry +-- instance CSV.ToNamedRecord ChineseDictEntry +-- instance CSV.DefaultOrdered ChineseDictEntry + +feed :: (ByteString -> r) -> Sys.Handle -> Sys.IO r +feed k csvFile = do + Sys.hIsEOF csvFile >>= \case + True -> return $ k "" + False -> k <$> ByteString.hGetSome csvFile 4096 + +readHSK :: HskLevel -> IO ChineseDict +readHSK hskLevel = do + Sys.withFile ("data/langs/mandarin/hsk_csv/hsk" <> show hskIndex Sys.<.> "csv") Sys.ReadMode \hskHandle -> do + loop hskHandle mempty $ + CSV.Incremental.decodeWithP parser decodeOpts CSV.NoHeader + where + hskIndex = hskLevel & fromEnum & (+ 1) + decodeOpts = CSV.defaultDecodeOptions + parser :: CSV.Record -> CSV.Parser ChineseDictEntry + parser v + | length v == 3 = do + chinese <- v .! 0 + pinyins <- v .! 1 <&> pure + english <- v .! 2 <&> pure + pure ChineseDictEntry{hskLevel = Just hskLevel, ..} + | otherwise = empty + check = + either (\x -> Sys.print x >> return mempty) \e@ChineseDictEntry{chinese} -> + return $ ChineseDict $ Map.singleton chinese [e] + loop hskHandle !acc = \case + CSV.Incremental.Fail _ errMsg -> do + Sys.putStrLn errMsg + Sys.exitFailure + CSV.Incremental.Many rs k -> do + ok <- rs & foldMapM check + t <- feed k hskHandle + loop hskHandle (acc <> ok) t + CSV.Incremental.Done rs -> do + ok <- rs & foldMapM check + return (acc <> ok) + +readCEDICT :: IO ChineseDict +readCEDICT = do + Sys.withFile ("data/langs/mandarin/cedict/cedict_ts.u8") Sys.ReadMode \cedictHandle -> do + let skipHeader = do + isEOF <- cedictHandle & Sys.hIsEOF + if isEOF + then return () + else do + lineBS <- ByteString.hGetLine cedictHandle + let lineST = lineBS & ShortText.fromByteString & fromMaybe (error "invalid UTF-8") + let begin = lineST & ShortText.take 1 + when (begin == "#") do + skipHeader + skipHeader + let loop !acc = do + isEOF <- cedictHandle & Sys.hIsEOF + if isEOF + then return acc + else do + line <- ByteString.hGetLine cedictHandle + let decodeUtf8 = ShortText.fromByteString >>> fromMaybe (error "invalid UTF-8") + -- DescriptionNote: each line is formatted as: #(.+) (.+) \[(.+)] /(.*)/#' + let skipChar c = void $ MT.state $ ByteString.span (== fromIntegral (fromEnum c)) + let skipPrefix p = + MT.modify' $ + ByteString.stripPrefix p + >>> fromMaybe (error $ "skipPrefix fail to match: " <> show p) + let breakOnChar c = ByteString.break (== fromIntegral (fromEnum c)) + let breakOnSpace = breakOnChar ' ' + let skipSuffix p = + MT.modify' $ + ByteString.stripSuffix p + >>> fromMaybe + ( error $ + "skipSuffix: mismatch: " + <> show p + <> "\n on line: " + <> ShortText.unpack (decodeUtf8 line) + <> "\n escaped: " + <> show (ShortText.unpack (decodeUtf8 line)) + ) + let (dict, leftover) = (`MT.runState` line) do + chineseTrad <- MT.state $ breakOnSpace >>> first decodeUtf8 + skipChar ' ' + chineseSimpl <- MT.state $ breakOnSpace >>> first decodeUtf8 + skipChar ' ' + skipPrefix "[" + pinyins <- + MT.state $ + breakOnChar ']' + >>> first (\s -> s & ByteString.split (fromIntegral (fromEnum ' ')) <&> decodeUtf8) + skipPrefix "] /" + -- CorrectnessNote: some lines do not end with \r + -- hence make it optional. + MT.modify' \s -> s & ByteString.stripSuffix "\r" & fromMaybe s + skipSuffix "/" + english <- MT.gets \s -> s & ByteString.split (fromIntegral (fromEnum '/')) <&> decodeUtf8 + MT.put mempty + let chinese = chineseSimpl + return $ + ChineseDict $ + Map.singleton chinese $ + pure $ + ChineseDictEntry + { chinese + , pinyins + , english + , hskLevel = Nothing + } + if not (ByteString.null leftover) + then error $ "parserLine: leftover: " <> show leftover + else loop (acc <> dict) + loop mempty + +readChineseDict :: IO ChineseDict +readChineseDict = do + 0 & toEnum & enumFrom & foldMapM readHSK + +{- +where + manualDict = ChineseDict $ Map.fromListWith (<>) [ (chinese, e) | e@ChineseDictEntry{chinese} <- manualEntries ] + manualEntries = + [ ChineseDictEntry + { chinese = "心" + , pinyins = ["xīn"] + , english = ["heart"] + , hskLevel = Nothing + } + , ChineseDictEntry + { chinese = "果" + , pinyins = ["guǒ"] + , english = ["fruit"] + , hskLevel = Nothing + } + , ChineseDictEntry + { chinese = "冰" + , pinyins = ["bīng"] + , english = ["ice"] + , hskLevel = Nothing + } + , ChineseDictEntry + { chinese = "淇" + , pinyins = ["qí"] + , english = [] + , hskLevel = Nothing + } + , ChineseDictEntry + { chinese = "淋" + , pinyins = ["lín"] + , english = [] + , hskLevel = Nothing + } + , ChineseDictEntry + { chinese = "天" + , pinyins = ["tiān"] + , english = ["sky"] + , hskLevel = Nothing + } + , ChineseDictEntry + { chinese = "空" + , pinyins = ["kōng"] + , english = ["empty"] + , hskLevel = Nothing + } + , ChineseDictEntry + { chinese = "中" + , pinyins = ["zhōng"] + , english = ["middle"] + , hskLevel = Nothing + } + , ChineseDictEntry + { chinese = "架" + , pinyins = ["jià"] + , english = ["shelf"] + , hskLevel = Nothing + } + , ChineseDictEntry + { chinese = "飞" + , pinyins = ["fēi"] + , english = ["fly"] + , hskLevel = Nothing + } + , ChineseDictEntry + { chinese = "机" + , pinyins = ["jī"] + , english = ["machine"] + , hskLevel = Nothing + } + ] +-} + +{- +readHSK :: Sys.IO [HSK] +readHSK = do + Sys.withFile "data/langs/mandarin/hsk.csv" Sys.ReadMode \csvFile -> do + let loopH !_ (CSV.Incremental.FailH _ errMsg) = do Sys.putStrLn errMsg; Sys.exitFailure + loopH acc (CSV.Incremental.PartialH k) = feed k csvFile >>= loopH acc + loopH _acc (CSV.Incremental.DoneH !h p) = p + p <- loopH [] CSV.Incremental.decodeByName + let loop !_ (CSV.Incremental.Fail _ errMsg) = do Sys.putStrLn errMsg; Sys.exitFailure + loop acc (CSV.Incremental.Many rs k) = feed k csvFile >>= loop (acc <> rs) + loop acc (CSV.Incremental.Done rs) = loop (acc <> rs) + loop [] p +-} + +data Langue + = LangueFrançais + | LangueAnglais + | LangueMandarin + | LangueMandarinPinyin + | LanguePhonetic + deriving (Eq, Ord, Show) + +data Rosetta = Rosetta + { rosettaPictures :: [File.FilePath] + , rosettaEntries :: [RosettaEntry] + } + deriving (Eq, Ord, Show) + +data RosettaEntry = RosettaEntry + { rosettaEntryGlyphsText :: ShortText + , rosettaEntryGlyphsLangue :: Langue + } + deriving (Eq, Ord, Show) + +rosetta :: ChineseDict -> Text -> Rosetta -> IO Builder +rosetta (ChineseDict chineseDict) title Rosetta{..} = do + -- FIXME: this absolute path is not reproducible out of my system + dataPath <- Self.getDataDir <&> File.normalise + return $ Blaze.renderMarkupBuilder do + H.docTypeHtml do + H.head do + H.title $ title & H.toHtml + H.link + ! HA.rel "stylesheet" + ! HA.type_ "text/css" + ! HA.href (toValue $ dataPath "styles/rosetta.css") + H.body do + "\n" + H.div ! classes ["rosetta"] $ do + forM_ (List.zip rosettaPictures rosettaEntries) \(rosettaPicture, RosettaEntry{..}) -> do + -- pTraceShowM (rosettaPicture, RosettaEntry{..}) + "\n" + H.div ! classes ["rosetta-row", "rosetta-row-" <> show rosettaEntryGlyphsLangue] $ do + H.div ! classes ["rosetta-cell", "rosetta-cell-picture"] $ do + H.img ! HA.src ("file://" <> dataPath "images" "thumbnails" rosettaPicture & toValue) + H.div ! classes ["rosetta-cell", "rosetta-cell-words"] $ do + let atomWidth = case rosettaEntryGlyphsLangue of + LangueMandarin -> "1.5cm" + _ -> "1cm" + let cellSpace = \case + ' ' -> "writing-words-cell-space" + _ -> "" + H.div ! classes ["writing-words"] $ do + forM_ (rosettaEntryGlyphsText & ShortText.split (== ' ')) \writingWord -> do + let atomLength = writingWord & ShortText.length & show + H.div + ! classes + [ "writing-words-word" + ] + $ do + when (rosettaEntryGlyphsLangue == LangueMandarin) do + H.div + ! classes + [ "writing-words-row" + , "writing-words-" <> show LangueMandarinPinyin + ] + ! HA.style ("grid-template-columns: repeat(" <> atomLength <> ", " <> atomWidth <> ");" & toValue) + $ do + forM_ (writingWord & ShortText.unpack) \writingChar -> do + let ChineseDictEntry{pinyins} = + chineseDict + & Map.lookup (ShortText.singleton writingChar) + & fromMaybe (error $ "chineseDict missing: " <> [writingChar]) + & List.head -- FIXME + H.div ! classes ["writing-words-cell", cellSpace writingChar] $ do + forM_ pinyins H.toHtml + H.div + ! classes + [ "writing-words-row" + , "writing-words-" <> show rosettaEntryGlyphsLangue + , "writing-words-row-model" + ] + ! HA.style ("grid-template-columns: repeat(" <> atomLength <> ", " <> atomWidth <> ");" & toValue) + $ do + forM_ (writingWord & ShortText.unpack) \writingChar -> do + H.div ! classes ["writing-words-cell", cellSpace writingChar] $ do + fromString [writingChar] + "\n" + H.div + ! classes + [ "writing-words-row" + , "writing-words-" <> show rosettaEntryGlyphsLangue + , "writing-words-row-input" + ] + ! HA.style ("grid-template-columns: repeat(" <> atomLength <> ", " <> atomWidth <> ");" & toValue) + $ do + forM_ (writingWord & ShortText.unpack) \writingChar -> do + H.div ! classes ["writing-words-cell", cellSpace writingChar] $ do + case rosettaEntryGlyphsLangue of + LangueMandarin -> fromString [writingChar] + _ -> " " + "\n" + "\n" diff --git a/style/worksheet.css b/style/worksheet.css deleted file mode 100644 index f5bf762..0000000 --- a/style/worksheet.css +++ /dev/null @@ -1,44 +0,0 @@ -body { - line-height:1.4; - margin:2ex auto; - //width:650px; -} -.worksheet { - white-space:preserve-spaces; - width:100%; - margin-left: 1ex; - margin-right: 1ex; - margin-bottom: 1ex; - margin-top: 1ex; - font-family: sans; - font-variant: small-caps; - display: grid; - gap: 0px; - //justify-items: center; - justify-self: center; - font-size: 14pt; - //background-color:grey; -} -.row { - display: grid; - grid-template-columns: repeat(1000, 1.5em); -} -.worksheet > div:nth-child(2n) { - margin-bottom:.5em; -} -.cell { - display: grid; - place-content: center; - position: relative; - box-shadow:0 0 0 1px #AAA; - background-color:white; - min-height:1.5em; - padding-left:0; - padding-right:0; - margin-left:0; - margin-right:0; -} -.cell-space { - box-shadow:0 0 0 0 red !important; - border-left:1px solid #AAA; -} diff --git a/tests/Spec.hs b/tests/Spec.hs index 80da72f..9a3b14a 100644 --- a/tests/Spec.hs +++ b/tests/Spec.hs @@ -3,7 +3,7 @@ import Test.Syd import Prelude qualified -import Worksheets.Writing.LatinSpec qualified +import Worksheets.Writing.RosettaSpec qualified main :: Prelude.IO () main = sydTest spec @@ -11,5 +11,5 @@ main = sydTest spec spec = do describe "Worksheets" do describe "Writing" do - describe "LatinSpec" do - Worksheets.Writing.LatinSpec.spec + describe "RosettaSpec" do + Worksheets.Writing.RosettaSpec.spec diff --git a/tests/Worksheets/Writing/LatinSpec.hs b/tests/Worksheets/Writing/LatinSpec.hs deleted file mode 100644 index a825453..0000000 --- a/tests/Worksheets/Writing/LatinSpec.hs +++ /dev/null @@ -1,57 +0,0 @@ -module Worksheets.Writing.LatinSpec where - -import Data.Function (($), (&), (.)) -import Data.Functor ((<&>)) -import Data.GenValidity.Map () -import Data.GenValidity.Sequence () -import Data.GenValidity.Set () -import Data.GenValidity.Text () -import Data.List qualified as List -import Data.Text qualified as Text -import Data.Validity.Map () -import Data.Validity.Set () -import Data.Validity.Text () -import GHC.Stack (HasCallStack) -import System.FilePath (joinPath, pathSeparator, (<.>), ()) -import System.FilePath qualified as Sys -import Test.Syd - -import Worksheets.Writing.Latin qualified - -runPart :: Sys.FilePath -> TestDefM outers () () -runPart part = do - descrPath <- getTestDescriptionPath - let dirPath = - List.reverse descrPath - <&> Text.unpack - . Text.replace - (Text.pack ".") - (Text.singleton pathSeparator) - & joinPath - let inpPath = "tests" dirPath part <.> "txt" - let outPath = "tests" dirPath part <.> "html" - it part do - goldenByteStringBuilderFile outPath $ - Worksheets.Writing.Latin.worksheet inpPath - -spec :: HasCallStack => Spec -spec = do - describe "frozen" do - describe "let-it-go" do - describe "en" do - runPart "part1" - runPart "part2" - describe "fr" do - runPart "part1" - runPart "part2" - runPart "part3" - runPart "part4" - runPart "part5" - runPart "part6" - runPart "part7" - describe "in-summer" do - describe "en" do - runPart "part1" - runPart "part2" - runPart "part3" - runPart "part4" diff --git a/tests/Worksheets/Writing/LatinSpec/TheHobbit/FarOverTheMistyMountainsCold/full.txt b/tests/Worksheets/Writing/LatinSpec/TheHobbit/FarOverTheMistyMountainsCold/full.txt new file mode 100644 index 0000000..794de5b --- /dev/null +++ b/tests/Worksheets/Writing/LatinSpec/TheHobbit/FarOverTheMistyMountainsCold/full.txt @@ -0,0 +1,129 @@ +Far over the Misty Mountains cold +To dungeons deep and caverns old +We must away, ere break of day +To seek our pale enchanted gold + +The dwarves of yore made mighty spells +While hammers fell like ringing bells +In places deep, where dark things sleep +In hollow halls beneath the fells + +For ancient king and elvish lord +There many a gleaming golden hoard +They shaped and wrought, and light they caught +To hide in gems on hilt of sword + +On silver necklaces they strung +The flowering stars, on crowns they hung +The dragon-fire, on twisted wire +They meshed the light of moon and sun + +Far over the Misty Mountains cold +To dungeons deep and caverns old +We must away, ere break of day +To claim our long-forgotten gold + +Goblets they carved there for themselves +And harps of gold, where no man delves +There lay they long, and many a song +Was sung unheard by men or elves +The pines were roaring on the heights +The wind was moaning in the night +The fire was red, it flaming spread +The trees like torches blazed with light + +The bells were ringing in the dale +And men looked up with faces pale +The dragon's ire, more fierce than fire +Laid low their towers and houses frail + +The mountain smoked beneath the moon +The dwarves, they heard the tramp of doom +They fled the hall to dying fall +Beneath his feet, beneath the moon + +Far over the Misty Mountains grim +To dungeons deep and caverns dim +We must away, ere break of day +To win our harps and gold from him! + +The wind was on the withered heath +But in the forest stirred no leaf +There shadows lay be night or day +And dark things silent crept beneath + +The wind came down from mountains cold +And like a tide it roared and rolled +The branches groaned, the forest moaned +And leaves were laid upon the mould +The wind went on from West to East; +All movement in the forest ceased +But shrill and harsh across the marsh +Its whistling voices were released + +The grasses hissed, their tassels bent +The reeds were rattling--on it went +O'er shaken pool under heavens cool +Where racing clouds were torn and rent + +It passed the Lonely Mountain bare +And swept above the dragon's lair +There black and dark lay boulders stark +And flying smoke was in the air + +It left the world and took its flight +Over the wide seas of the night +The moon set sail upon the gale +And stars were fanned to leaping light + +Under the Mountain dark and tall +The King has come unto his hall! +His foe is dead, the Worm of Dread +And ever so his foes shall fall! + +The sword is sharp, the spear is long +The arrow swift, the Gate is strong +The heart is bold that looks on gold +The dwarves no more shall suffer wrong +The dwarves of yore made mighty spells +While hammers fell like ringing bells +In places deep, where dark things sleep +In hollow halls beneath the fells + +On silver necklaces they strung +The light of stars, on crowns they hung +The dragon-fire, from twisted wire +The melody of harps they wrung + +The mountain throne once more is freed! +O! Wandering folk, the summons heed! +Come haste! Come haste! Across the waste! +The king of friend and kin has need + +Now call we over the mountains cold +'Come back unto the caverns old! +Here at the gates the king awaits +His hands are rich with gems and gold + +The king has come unto his hall +Under the Mountain dark and tall +The Wyrm of Dread is slain and dead +And ever so our foes shall fall + +Farewell we call to hearth and hall! +Though wind may blow and rain may fall +We must away, ere break of day +Far over the wood and mountain tall + +To Rivendell, where Elves yet dwell +In glades beneath the misty fell +Through moor and waste we ride in haste +And whither then we cannot tell + +With foes ahead, behind us dread +Beneath the sky shall be our bed +Until at last our toil be passed +Our journey done, our errand sped + +We must away! We must away! +We ride before the break of day! diff --git a/tests/Worksheets/Writing/LatinSpec/TheHobbit/FarOverTheMistyMountainsCold/part1.txt b/tests/Worksheets/Writing/LatinSpec/TheHobbit/FarOverTheMistyMountainsCold/part1.txt new file mode 100644 index 0000000..88132e0 --- /dev/null +++ b/tests/Worksheets/Writing/LatinSpec/TheHobbit/FarOverTheMistyMountainsCold/part1.txt @@ -0,0 +1,4 @@ +Far over the Misty Mountains cold +To dungeons deep and caverns old +We must away, ere break of day +To seek our pale enchanted gold diff --git a/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part1.html b/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part1.html deleted file mode 100644 index 84e0baa..0000000 --- a/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part1.html +++ /dev/null @@ -1,19 +0,0 @@ - -part1 -
B
e
e
s
'
l
l
b
u
z
z
,
-
-
k
i
d
s
'
l
l
b
l
o
w
d
a
n
d
e
l
i
o
n
f
u
z
z
-
-
A
n
d
I
'
l
l
b
e
d
o
i
n
g
w
h
a
t
e
v
e
r
s
n
o
w
d
o
e
s
-
-
I
n
s
u
m
m
e
r
-
-
A
d
r
i
n
k
i
n
m
y
h
a
n
d
,
m
y
s
n
o
w
-
-
u
p
a
g
a
i
n
s
t
t
h
e
b
u
r
n
i
n
g
s
a
n
d
-
-
P
r
o
b
a
b
l
y
g
e
t
t
i
n
g
g
o
r
g
e
o
u
s
l
y
t
a
n
n
e
d
-
-
I
n
s
u
m
m
e
r
-
-
\ No newline at end of file diff --git a/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part2.html b/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part2.html deleted file mode 100644 index 180b9f9..0000000 --- a/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part2.html +++ /dev/null @@ -1,19 +0,0 @@ - -part2 -
I
'
l
l
f
i
n
a
l
l
y
s
e
e
a
s
u
m
m
e
r
b
r
e
e
z
e
-
-
b
l
o
w
a
w
a
y
a
w
i
n
t
e
r
s
t
o
r
m
-
-
A
n
d
f
i
n
d
o
u
t
w
h
a
t
h
a
p
p
e
n
s
-
-
t
o
s
o
l
i
d
w
a
t
e
r
w
h
e
n
i
t
g
e
t
s
w
a
r
m
-
-
A
n
d
I
c
a
n
'
t
w
a
i
t
t
o
s
e
e
-
-
w
h
a
t
m
y
b
u
d
d
i
e
s
a
l
l
t
h
i
n
k
o
f
m
e
-
-
J
u
s
t
i
m
a
g
i
n
e
h
o
w
m
u
c
h
c
o
o
l
e
r
I
'
l
l
b
e
-
-
I
n
s
u
m
m
e
r
-
-
\ No newline at end of file diff --git a/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part2.txt b/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part2.txt index eb8be58..e8f2479 100644 --- a/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part2.txt +++ b/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part2.txt @@ -4,5 +4,6 @@ And find out what happens to solid water when it gets warm And I can't wait to see what my buddies all think of me -Just imagine how much cooler I'll be +Just imagine how much cooler +I'll be In summer diff --git a/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part3.html b/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part3.html deleted file mode 100644 index eb674df..0000000 --- a/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part3.html +++ /dev/null @@ -1,15 +0,0 @@ - -part3 -
D
a
d
a
,
d
a
d
o
o
,
a
h
,
b
a
h
,
b
a
h
,
b
a
h
,
b
a
h
,
b
a
h
,
b
o
o
-
-
T
h
e
h
o
t
a
n
d
t
h
e
c
o
l
d
a
r
e
b
o
t
h
s
o
i
n
t
e
n
s
e
-
-
P
u
t
t
h
e
m
t
o
g
e
t
h
e
r
,
i
t
j
u
s
t
m
a
k
e
s
s
e
n
s
e
-
-
R
a
t
d
a
d
a
t
,
d
a
d
a
d
a
d
o
o
-
-
W
i
n
t
e
r
'
s
a
g
o
o
d
t
i
m
e
t
o
s
t
a
y
i
n
a
n
d
c
u
d
d
l
e
-
-
B
u
t
p
u
t
m
e
i
n
s
u
m
m
e
r
a
n
d
I
'
l
l
b
e
a
h
a
p
p
y
s
n
o
w
m
a
n
-
-
\ No newline at end of file diff --git a/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part4.html b/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part4.html deleted file mode 100644 index e08987e..0000000 --- a/tests/Worksheets/Writing/LatinSpec/frozen/in-summer/en/part4.html +++ /dev/null @@ -1,15 +0,0 @@ - -part4 -
W
h
e
n
l
i
f
e
g
e
t
s
r
o
u
g
h
I
l
i
k
e
t
o
h
o
l
d
o
n
t
o
m
y
d
r
e
a
m
s
-
-
O
f
r
e
l
a
x
i
n
g
i
n
t
h
e
s
u
m
m
e
r
s
u
n
,
j
u
s
t
l
e
t
t
i
n
g
o
f
f
s
t
e
a
m
-
-
O
h
,
t
h
e
s
k
y
w
i
l
l
b
e
b
l
u
e
,
a
n
d
y
o
u
g
u
y
s
'
l
l
b
e
t
h
e
r
e
t
o
o
-
-
W
h
e
n
I
f
i
n
a
l
l
y
d
o
w
h
a
t
f
r
o
z
e
n
t
h
i
n
g
s
d
o
-
-
I
n
s
u
m
m
e
r
-
-
I
n
s
u
m
m
e
r
!
-
-
\ No newline at end of file diff --git a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/en/part1.html b/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/en/part1.html deleted file mode 100644 index 5c6b768..0000000 --- a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/en/part1.html +++ /dev/null @@ -1,21 +0,0 @@ - -part1 -
T
h
e
s
n
o
w
g
l
o
w
s
w
h
i
t
e
-
-
o
n
t
h
e
m
o
u
n
t
a
i
n
t
o
n
i
g
h
t
-
-
N
o
t
a
f
o
o
t
p
r
i
n
t
t
o
b
e
s
e
e
n
-
-
A
k
i
n
g
d
o
m
o
f
i
s
o
l
a
t
i
o
n
,
-
-
a
n
d
i
t
l
o
o
k
s
l
i
k
e
I
'
m
t
h
e
Q
u
e
e
n
-
-
T
h
e
w
i
n
d
i
s
h
o
w
l
i
n
g
-
-
l
i
k
e
t
h
i
s
s
w
i
r
l
i
n
g
s
t
o
r
m
i
n
s
i
d
e
-
-
C
o
u
l
d
n
'
t
k
e
e
p
i
t
i
n
,
-
-
H
e
a
v
e
n
k
n
o
w
s
I
t
r
i
e
d
-
-
\ No newline at end of file diff --git a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/en/part2.html b/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/en/part2.html deleted file mode 100644 index 038e835..0000000 --- a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/en/part2.html +++ /dev/null @@ -1,21 +0,0 @@ - -part2 -
L
e
t
i
t
g
o
!
L
e
t
i
t
g
o
!
-
-
C
a
n
'
t
h
o
l
d
i
t
b
a
c
k
a
n
y
m
o
r
e
!
-
-
L
e
t
i
t
g
o
!
L
e
t
i
t
g
o
!
-
-
T
u
r
n
a
w
a
y
a
n
d
s
l
a
m
t
h
e
d
o
o
r
!
-
-
I
d
o
n
'
t
c
a
r
e
-
-
w
h
a
t
t
h
e
y
'
r
e
g
o
i
n
g
t
o
s
a
y
!
-
-
L
e
t
t
h
e
s
t
o
r
m
r
a
g
e
o
n
-
-
T
h
e
c
o
l
d
n
e
v
e
r
b
o
t
h
e
r
e
d
m
e
-
-
a
n
y
w
a
y
-
-
\ No newline at end of file diff --git a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part1.html b/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part1.html deleted file mode 100644 index b6f6644..0000000 --- a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part1.html +++ /dev/null @@ -1,19 +0,0 @@ - -part1 -
L
’
h
i
v
e
r
s
’
i
n
s
t
a
l
l
e
d
o
u
c
e
m
e
n
t
d
a
n
s
l
a
n
u
i
t
,
-
-
L
a
n
e
i
g
e
e
s
t
r
e
i
n
e
à
s
o
n
t
o
u
r
-
-
U
n
r
o
y
a
u
m
e
d
e
s
o
l
i
t
u
d
e
:
-
-
m
a
p
l
a
c
e
e
s
t
l
à
p
o
u
r
t
o
u
j
o
u
r
s
-
-
L
e
v
e
n
t
q
u
i
h
u
r
l
e
e
n
m
o
i
-
-
n
e
p
e
n
s
e
p
l
u
s
à
d
e
m
a
i
n
-
-
I
l
e
s
t
b
i
e
n
t
r
o
p
f
o
r
t
,
-
-
j
’
a
i
l
u
t
t
é
e
n
v
a
i
n
-
-
\ No newline at end of file diff --git a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part2.html b/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part2.html deleted file mode 100644 index 71ff112..0000000 --- a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part2.html +++ /dev/null @@ -1,13 +0,0 @@ - -part2 -
C
a
c
h
e
t
e
s
p
o
u
v
o
i
r
s
,
n
’
e
n
p
a
r
l
e
p
a
s
,
-
-
F
a
i
s
a
t
t
e
n
t
i
o
n
,
l
e
s
e
c
r
e
t
s
u
r
v
i
v
r
a
-
-
P
a
s
d
’
é
t
a
t
s
d
’
â
m
e
,
-
-
p
a
s
d
e
t
o
u
r
m
e
n
t
s
,
-
-
d
e
s
e
n
t
i
m
e
n
t
s
-
-
\ No newline at end of file diff --git a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part3.html b/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part3.html deleted file mode 100644 index eb78690..0000000 --- a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part3.html +++ /dev/null @@ -1,19 +0,0 @@ - -part3 -
L
i
b
é
r
é
e
!
D
é
l
i
v
r
é
e
!
-
-
J
e
n
e
m
e
n
t
i
r
a
i
p
l
u
s
j
a
m
a
i
s
-
-
L
i
b
é
r
é
e
!
D
é
l
i
v
r
é
e
!
-
-
C
’
e
s
t
d
é
c
i
d
é
,
j
e
m
’
e
n
v
a
i
s
-
-
J
’
a
i
l
a
i
s
s
é
m
o
n
e
n
f
a
n
c
e
e
n
é
t
é
-
-
P
e
r
d
u
e
d
a
n
s
l
’
h
i
v
e
r
-
-
L
e
f
r
o
i
d
e
s
t
p
o
u
r
m
o
i
-
-
L
e
p
r
i
x
d
e
l
a
l
i
b
e
r
t
é
-
-
\ No newline at end of file diff --git a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part4.html b/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part4.html deleted file mode 100644 index bbf850e..0000000 --- a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part4.html +++ /dev/null @@ -1,15 +0,0 @@ - -part4 -
Q
u
a
n
d
o
n
p
r
e
n
d
d
e
l
a
h
a
u
t
e
u
r
,
t
o
u
t
s
e
m
b
l
e
i
n
s
i
g
n
i
f
i
a
n
t
-
-
L
a
t
r
i
s
t
e
s
s
e
,
l
’
a
n
g
o
i
s
s
e
e
t
l
a
p
e
u
r
-
-
m
’
o
n
t
q
u
i
t
t
é
d
e
p
u
i
s
l
o
n
g
t
e
m
p
s
-
-
J
e
v
e
u
x
v
o
i
r
c
e
q
u
e
j
e
p
e
u
x
f
a
i
r
e
-
-
D
e
c
e
t
t
e
m
a
g
i
e
p
l
e
i
n
e
d
e
m
y
s
t
è
r
e
-
-
L
e
b
i
e
n
,
l
e
m
a
l
j
e
d
i
s
t
a
n
t
p
i
s
,
t
a
n
t
p
i
s
-
-
\ No newline at end of file diff --git a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part5.html b/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part5.html deleted file mode 100644 index c12e22b..0000000 --- a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part5.html +++ /dev/null @@ -1,9 +0,0 @@ - -part5 -
L
i
b
é
r
é
e
,
d
é
l
i
v
r
é
e
,
l
e
s
é
t
o
i
l
e
s
m
e
t
e
n
d
e
n
t
l
e
s
b
r
a
s
.
-
-
L
i
b
é
r
é
e
,
d
é
l
i
v
r
é
e
,
n
o
n
j
e
n
e
p
l
e
u
r
e
p
a
s
.
-
-
M
e
v
o
i
l
à
,
o
u
i
j
e
s
u
i
s
l
à
.
P
e
r
d
u
e
d
a
n
s
l
’
h
i
v
e
r
.
-
-
\ No newline at end of file diff --git a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part6.html b/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part6.html deleted file mode 100644 index 510a692..0000000 --- a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part6.html +++ /dev/null @@ -1,13 +0,0 @@ - -part6 -
M
o
n
p
o
u
v
o
i
r
v
i
e
n
t
d
u
c
i
e
l
e
t
e
n
v
a
h
i
t
l
’
e
s
p
a
c
e
-
-
M
o
n
â
m
e
s
’
e
x
p
r
i
m
e
e
n
d
e
s
s
i
n
a
n
t
-
-
e
t
s
c
u
l
p
t
a
n
t
d
a
n
s
l
a
g
l
a
c
e
-
-
E
t
m
e
s
p
e
n
s
é
e
s
s
o
n
t
d
e
s
f
l
e
u
r
s
d
e
c
r
i
s
t
a
l
g
e
l
é
e
s
-
-
J
e
n
e
r
e
v
i
e
n
d
r
a
i
p
a
s
,
l
e
p
a
s
s
é
e
s
t
p
a
s
s
é
-
-
\ No newline at end of file diff --git a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part7.html b/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part7.html deleted file mode 100644 index b22e449..0000000 --- a/tests/Worksheets/Writing/LatinSpec/frozen/let-it-go/fr/part7.html +++ /dev/null @@ -1,19 +0,0 @@ - -part7 -
L
i
b
é
r
é
e
,
d
é
l
i
v
r
é
e
,
-
-
d
é
s
o
r
m
a
i
s
p
l
u
s
r
i
e
n
n
e
m
’
a
r
r
ê
t
e
-
-
L
i
b
é
r
é
e
,
d
é
l
i
v
r
é
e
,
-
-
p
l
u
s
d
e
p
r
i
n
c
e
s
s
e
p
a
r
f
a
i
t
e
-
-
J
e
s
u
i
s
l
à
c
o
m
m
e
j
e
l
’
a
i
r
ê
v
é
.
-
-
P
e
r
d
u
e
d
a
n
s
l
’
h
i
v
e
r
,
-
-
L
e
f
r
o
i
d
e
s
t
p
o
u
r
m
o
i
-
-
l
e
p
r
i
x
d
e
l
a
l
i
b
e
r
t
é
-
-
\ No newline at end of file diff --git a/worksheets.cabal b/worksheets.cabal index f5883ea..37b0dbe 100644 --- a/worksheets.cabal +++ b/worksheets.cabal @@ -13,7 +13,7 @@ copyright: Julien Moutinho -- PVP: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change -version: 0.0.0.20250503 +version: 0.0.0.20250603 stability: experimental category: Worksheets synopsis: Worksheets @@ -22,7 +22,7 @@ build-type: Simple tested-with: GHC ==9.6.6 extra-doc-files: extra-tmp-files: -data-dir: style +data-dir: data source-repository head type: git @@ -78,19 +78,23 @@ common library-deps , blaze-html , blaze-markup , bytestring + , cassava + , containers , filepath , pretty-simple , text >=2.1 + , text-short , transformers library import: haskell, library-deps hs-source-dirs: src autogen-modules: Paths_worksheets - exposed-modules: Worksheets.Writing.Latin + exposed-modules: Worksheets.Writing.Rosetta other-modules: Paths_worksheets Prelude + Utils.Blaze build-depends: base >=4.10 && <5 @@ -107,7 +111,7 @@ test-suite worksheets-tests autogen-modules: Paths_worksheets other-modules: Paths_worksheets - Worksheets.Writing.LatinSpec + Worksheets.Writing.RosettaSpec build-depends: , filepath -- 2.47.2