Cleanup fuzzy pseudonym handling
This commit is contained in:
parent
52e99bae53
commit
52467723b8
@ -414,9 +414,11 @@ NotificationTriggerCorrectionsAssigned: Mir wurden Abgaben zur Korrektur zugetei
|
|||||||
CorrCreate: Abgaben erstellen
|
CorrCreate: Abgaben erstellen
|
||||||
UnknownPseudonymWord pseudonymWord@Text: Unbekanntes Pseudonym-Wort "#{pseudonymWord}"
|
UnknownPseudonymWord pseudonymWord@Text: Unbekanntes Pseudonym-Wort "#{pseudonymWord}"
|
||||||
InvalidPseudonym pseudonym@Text: Invalides Pseudonym "#{pseudonym}"
|
InvalidPseudonym pseudonym@Text: Invalides Pseudonym "#{pseudonym}"
|
||||||
|
InvalidPseudonymSubmissionIgnored oPseudonyms@Text iPseudonym@Text: Abgabe mit Pseudonymen „#{oPseudonyms}“ wurde ignoriert, da „#{iPseudonym}“ nicht automatisiert zu einem validen Pseudonym korrigiert werden konnte.
|
||||||
|
PseudonymAutocorrections: Korrekturvorschläge:
|
||||||
UnknownPseudonym pseudonym@Text: Unbekanntes Pseudonym "#{pseudonym}"
|
UnknownPseudonym pseudonym@Text: Unbekanntes Pseudonym "#{pseudonym}"
|
||||||
CorrectionPseudonyms: Abgaben-Pseudonyme
|
CorrectionPseudonyms: Abgaben-Pseudonyme
|
||||||
CorrectionPseudonymsTip: Eine Abgabe pro Zeile, bei Gruppenabgaben mehrere Pseudonyme (komma-separiert) innerhalb einer Zeile
|
CorrectionPseudonymsTip: Eine Abgabe pro Zeile, bei Gruppenabgaben mehrere Pseudonyme (komma-separiert) innerhalb einer Zeile. Kleine Schreibfehler werden u.U. automatisch korrigiert.
|
||||||
PseudonymSheet: Übungsblatt
|
PseudonymSheet: Übungsblatt
|
||||||
CorrectionPseudonymSheet termDesc@Text csh@CourseShorthand shn@SheetName: #{termDesc} » #{csh} » #{shn}
|
CorrectionPseudonymSheet termDesc@Text csh@CourseShorthand shn@SheetName: #{termDesc} » #{csh} » #{shn}
|
||||||
SheetGroupTooLarge sheetGroupDesc@Text: Abgabegruppe zu groß: #{sheetGroupDesc}
|
SheetGroupTooLarge sheetGroupDesc@Text: Abgabegruppe zu groß: #{sheetGroupDesc}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ module Data.CaseInsensitive.Instances
|
|||||||
(
|
(
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import ClassyPrelude.Yesod
|
import ClassyPrelude.Yesod hiding (lift)
|
||||||
|
|
||||||
import Data.CaseInsensitive (CI)
|
import Data.CaseInsensitive (CI)
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
@ -16,6 +16,8 @@ import Text.Blaze (ToMarkup(..))
|
|||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text.Encoding as Text
|
import qualified Data.Text.Encoding as Text
|
||||||
|
|
||||||
|
import Language.Haskell.TH.Syntax (Lift(..))
|
||||||
|
|
||||||
|
|
||||||
instance PersistField (CI Text) where
|
instance PersistField (CI Text) where
|
||||||
toPersistValue ciText = PersistDbSpecific . Text.encodeUtf8 $ CI.original ciText
|
toPersistValue ciText = PersistDbSpecific . Text.encodeUtf8 $ CI.original ciText
|
||||||
@ -51,3 +53,6 @@ instance ToWidget site a => ToWidget site (CI a) where
|
|||||||
|
|
||||||
instance RenderMessage site a => RenderMessage site (CI a) where
|
instance RenderMessage site a => RenderMessage site (CI a) where
|
||||||
renderMessage f ls msg = renderMessage f ls $ CI.original msg
|
renderMessage f ls msg = renderMessage f ls $ CI.original msg
|
||||||
|
|
||||||
|
instance Lift t => Lift (CI t) where
|
||||||
|
lift (CI.original -> orig) = [e|CI.mk $(lift orig)|]
|
||||||
|
|||||||
@ -1309,7 +1309,7 @@ pageActions (CorrectionsR) =
|
|||||||
, menuItemLabel = MsgMenuCorrectionsCreate
|
, menuItemLabel = MsgMenuCorrectionsCreate
|
||||||
, menuItemIcon = Nothing
|
, menuItemIcon = Nothing
|
||||||
, menuItemRoute = SomeRoute CorrectionsCreateR
|
, menuItemRoute = SomeRoute CorrectionsCreateR
|
||||||
, menuItemModal = True
|
, menuItemModal = False
|
||||||
, menuItemAccessCallback' = runDB $ do
|
, menuItemAccessCallback' = runDB $ do
|
||||||
uid <- liftHandlerT requireAuthId
|
uid <- liftHandlerT requireAuthId
|
||||||
[E.Value sheetCount] <- E.select . E.from $ \(course `E.InnerJoin` sheet) -> do
|
[E.Value sheetCount] <- E.select . E.from $ \(course `E.InnerJoin` sheet) -> do
|
||||||
@ -1349,7 +1349,7 @@ pageActions (CorrectionsGradeR) =
|
|||||||
, menuItemLabel = MsgMenuCorrectionsCreate
|
, menuItemLabel = MsgMenuCorrectionsCreate
|
||||||
, menuItemIcon = Nothing
|
, menuItemIcon = Nothing
|
||||||
, menuItemRoute = SomeRoute CorrectionsCreateR
|
, menuItemRoute = SomeRoute CorrectionsCreateR
|
||||||
, menuItemModal = True
|
, menuItemModal = False
|
||||||
, menuItemAccessCallback' = runDB $ do
|
, menuItemAccessCallback' = runDB $ do
|
||||||
uid <- liftHandlerT requireAuthId
|
uid <- liftHandlerT requireAuthId
|
||||||
[E.Value sheetCount] <- E.select . E.from $ \(course `E.InnerJoin` sheet) -> do
|
[E.Value sheetCount] <- E.select . E.from $ \(course `E.InnerJoin` sheet) -> do
|
||||||
|
|||||||
@ -124,7 +124,7 @@ colSubmittors = sortable Nothing (i18nCell MsgSubmissionUsers) $ \DBRow{ dbrOutp
|
|||||||
protoCell = listCell (Map.toList users) $ \(userId, (User{..}, mPseudo)) ->
|
protoCell = listCell (Map.toList users) $ \(userId, (User{..}, mPseudo)) ->
|
||||||
anchorCellM (link <$> encrypt userId) $ case mPseudo of
|
anchorCellM (link <$> encrypt userId) $ case mPseudo of
|
||||||
Nothing -> nameWidget userDisplayName userSurname
|
Nothing -> nameWidget userDisplayName userSurname
|
||||||
Just p -> [whamlet|^{nameWidget userDisplayName userSurname} (#{review pseudonymText p})|]
|
Just p -> [whamlet|^{nameWidget userDisplayName userSurname} (#{review _PseudonymText p})|]
|
||||||
in protoCell & cellAttrs <>~ [("class", "list--inline list--comma-separated")]
|
in protoCell & cellAttrs <>~ [("class", "list--inline list--comma-separated")]
|
||||||
|
|
||||||
colSMatrikel :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
colSMatrikel :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
||||||
@ -154,7 +154,7 @@ colRated = sortable (Just "ratingtime") (i18nCell MsgRatingTime) $ \DBRow{ dbrOu
|
|||||||
colPseudonyms :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
colPseudonyms :: IsDBTable m a => Colonnade _ CorrectionTableData (DBCell m a)
|
||||||
colPseudonyms = sortable Nothing (i18nCell MsgPseudonyms) $ \DBRow{ dbrOutput=(_, _, _, _, users) } -> let
|
colPseudonyms = sortable Nothing (i18nCell MsgPseudonyms) $ \DBRow{ dbrOutput=(_, _, _, _, users) } -> let
|
||||||
lCell = listCell (catMaybes $ snd . snd <$> Map.toList users) $ \pseudo ->
|
lCell = listCell (catMaybes $ snd . snd <$> Map.toList users) $ \pseudo ->
|
||||||
cell [whamlet|#{review pseudonymText pseudo}|]
|
cell [whamlet|#{review _PseudonymText pseudo}|]
|
||||||
in lCell & cellAttrs <>~ [("class", "list--inline list--comma-separated")]
|
in lCell & cellAttrs <>~ [("class", "list--inline list--comma-separated")]
|
||||||
|
|
||||||
colRatedField :: Colonnade _ CorrectionTableData (DBCell _ (FormResult (DBFormResult CorrectionTableData SubmissionId (Bool, a, b))))
|
colRatedField :: Colonnade _ CorrectionTableData (DBCell _ (FormResult (DBFormResult CorrectionTableData SubmissionId (Bool, a, b))))
|
||||||
@ -631,17 +631,19 @@ postCorrectionsCreateR = do
|
|||||||
}
|
}
|
||||||
((pseudonymRes, pseudonymWidget), pseudonymEncoding) <- runFormPost . renderAForm FormStandard $ (,)
|
((pseudonymRes, pseudonymWidget), pseudonymEncoding) <- runFormPost . renderAForm FormStandard $ (,)
|
||||||
<$> areq (selectField sheetOptions) (fslI MsgPseudonymSheet) Nothing
|
<$> areq (selectField sheetOptions) (fslI MsgPseudonymSheet) Nothing
|
||||||
<*> areq (checkMMap textToList textFromList textareaField) (fslpI MsgCorrectionPseudonyms "Pseudonyme" & setTooltip MsgCorrectionPseudonymsTip) Nothing
|
<*> (textToList <$> areq textareaField (fslpI MsgCorrectionPseudonyms "Pseudonyme" & setTooltip MsgCorrectionPseudonymsTip) Nothing)
|
||||||
<* submitButton
|
<* submitButton
|
||||||
|
|
||||||
case pseudonymRes of
|
case pseudonymRes of
|
||||||
FormMissing -> return ()
|
FormMissing -> return ()
|
||||||
FormFailure errs -> forM_ errs $ addMessage Error . toHtml
|
FormFailure errs -> forM_ errs $ addMessage Error . toHtml
|
||||||
FormSuccess (sid, pss) -> do
|
FormSuccess (sid, (pss, invalids)) -> do
|
||||||
|
forM_ (Map.toList invalids) $ \((oPseudonyms, iPseudonym), alts) -> $(addMessageFile Warning "templates/messages/ignoredInvalidPseudonym.hamlet")
|
||||||
|
|
||||||
runDB $ do
|
runDB $ do
|
||||||
Sheet{..} <- get404 sid
|
Sheet{..} <- get404 sid
|
||||||
(sps, unknown) <- fmap partitionEithers' . forM pss . mapM $ \p -> maybe (Left p) Right <$> getBy (UniqueSheetPseudonym sid p)
|
(sps, unknown) <- fmap partitionEithers' . forM pss . mapM $ \p -> maybe (Left p) Right <$> getBy (UniqueSheetPseudonym sid p)
|
||||||
forM_ unknown $ addMessageI Error . MsgUnknownPseudonym . review pseudonymText
|
forM_ unknown $ addMessageI Error . MsgUnknownPseudonym . review _PseudonymText
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
let
|
let
|
||||||
sps' :: [[SheetPseudonym]]
|
sps' :: [[SheetPseudonym]]
|
||||||
@ -671,7 +673,7 @@ postCorrectionsCreateR = do
|
|||||||
let sps'' = filter (not . null) $ filter (\spGroup -> not . flip any spGroup $ \SheetPseudonym{sheetPseudonymUser} -> sheetPseudonymUser `elem` map (submissionUserUser . entityVal) existingSubUsers) sps'
|
let sps'' = filter (not . null) $ filter (\spGroup -> not . flip any spGroup $ \SheetPseudonym{sheetPseudonymUser} -> sheetPseudonymUser `elem` map (submissionUserUser . entityVal) existingSubUsers) sps'
|
||||||
forM_ sps'' $ \spGroup
|
forM_ sps'' $ \spGroup
|
||||||
-> let
|
-> let
|
||||||
sheetGroupDesc = Text.intercalate ", " $ map (review pseudonymText . sheetPseudonymPseudonym) spGroup
|
sheetGroupDesc = Text.intercalate ", " $ map (review _PseudonymText . sheetPseudonymPseudonym) spGroup
|
||||||
in case sheetGrouping of
|
in case sheetGrouping of
|
||||||
Arbitrary maxSize
|
Arbitrary maxSize
|
||||||
| genericLength sps > maxSize
|
| genericLength sps > maxSize
|
||||||
@ -727,17 +729,13 @@ postCorrectionsCreateR = do
|
|||||||
partitionEithers' :: [[Either a b]] -> ([[b]], [a])
|
partitionEithers' :: [[Either a b]] -> ([[b]], [a])
|
||||||
partitionEithers' = runWriter . mapM (WriterT . Identity . swap . partitionEithers)
|
partitionEithers' = runWriter . mapM (WriterT . Identity . swap . partitionEithers)
|
||||||
|
|
||||||
textToList :: Textarea -> Handler (Either UniWorXMessage [[Pseudonym]])
|
textToList :: Textarea -> ([[Pseudonym]], Map (Text, Text) [Pseudonym])
|
||||||
textToList (map (map Text.strip . Text.splitOn ",") . filter (not . Text.null) . Text.lines . unTextarea -> ws)
|
textToList (map (map Text.strip . Text.splitOn ",") . filter (not . Text.null) . map Text.strip . Text.lines . unTextarea -> ws)
|
||||||
= let
|
= runWriter . fmap (mapMaybe sequence) $ mapM (\ws' -> mapM (toPseudonym ws') ws') ws
|
||||||
invalid :: [Text]
|
where
|
||||||
valid :: [[Pseudonym]]
|
toPseudonym w' w
|
||||||
(valid, invalid) = partitionEithers' $ map (map $ \w -> maybe (Left w) Right $ w ^? pseudonymText) ws
|
| Just res <- w ^? _PseudonymText = return $ Just res
|
||||||
in case invalid of
|
| otherwise = Nothing <$ tell (Map.singleton (Text.intercalate ", " w', w) $ w ^.. pseudonymFragments . _PseudonymWords)
|
||||||
(i:_) -> return . Left $ MsgInvalidPseudonym i
|
|
||||||
[] -> return $ Right valid
|
|
||||||
textFromList :: [[Pseudonym]] -> Textarea
|
|
||||||
textFromList = Textarea . Text.unlines . map (Text.intercalate ", " . map (review pseudonymText))
|
|
||||||
|
|
||||||
getCorrectionsGradeR, postCorrectionsGradeR :: Handler Html
|
getCorrectionsGradeR, postCorrectionsGradeR :: Handler Html
|
||||||
getCorrectionsGradeR = postCorrectionsGradeR
|
getCorrectionsGradeR = postCorrectionsGradeR
|
||||||
|
|||||||
@ -46,7 +46,6 @@ import Data.Monoid (Sum(..), Any(..))
|
|||||||
-- import Control.Lens
|
-- import Control.Lens
|
||||||
import Utils.Lens
|
import Utils.Lens
|
||||||
|
|
||||||
import qualified Data.Text as Text
|
|
||||||
--import qualified Data.Aeson as Aeson
|
--import qualified Data.Aeson as Aeson
|
||||||
|
|
||||||
import Control.Monad.Random.Class (MonadRandom(..))
|
import Control.Monad.Random.Class (MonadRandom(..))
|
||||||
@ -318,7 +317,7 @@ getSShowR tid ssh csh shn = do
|
|||||||
mPseudonym <- runMaybeT $ do
|
mPseudonym <- runMaybeT $ do
|
||||||
uid <- MaybeT maybeAuthId
|
uid <- MaybeT maybeAuthId
|
||||||
Entity _ SheetPseudonym{sheetPseudonymPseudonym} <- MaybeT . runDB . getBy $ UniqueSheetPseudonymUser sid uid
|
Entity _ SheetPseudonym{sheetPseudonymPseudonym} <- MaybeT . runDB . getBy $ UniqueSheetPseudonymUser sid uid
|
||||||
return . Text.unwords . map CI.original $ review pseudonymWords sheetPseudonymPseudonym
|
return $ review _PseudonymText sheetPseudonymPseudonym
|
||||||
(generateWidget, generateEnctype) <- generateFormPost $ \csrf ->
|
(generateWidget, generateEnctype) <- generateFormPost $ \csrf ->
|
||||||
over _2 ((toWidget csrf <>) . fvInput) <$> mreq (buttonField BtnGenerate) "" Nothing
|
over _2 ((toWidget csrf <>) . fvInput) <$> mreq (buttonField BtnGenerate) "" Nothing
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
@ -348,9 +347,8 @@ postSPseudonymR tid ssh csh shn = do
|
|||||||
Right (Just ps) -> return ps
|
Right (Just ps) -> return ps
|
||||||
Left ps -> return ps
|
Left ps -> return ps
|
||||||
ps <- genPseudonym
|
ps <- genPseudonym
|
||||||
let ps' = Text.unwords . map CI.original $ review pseudonymWords ps
|
|
||||||
selectRep $ do
|
selectRep $ do
|
||||||
provideRep $ return ps'
|
provideRep . return $ review _PseudonymText ps
|
||||||
provideJson ps
|
provideJson ps
|
||||||
provideRep (redirect $ CSheetR tid ssh csh shn SShowR :#: ("pseudonym" :: Text) :: Handler Html)
|
provideRep (redirect $ CSheetR tid ssh csh shn SShowR :#: ("pseudonym" :: Text) :: Handler Html)
|
||||||
|
|
||||||
|
|||||||
@ -607,25 +607,25 @@ instance FromJSON Pseudonym where
|
|||||||
-> return $ fromIntegral w
|
-> return $ fromIntegral w
|
||||||
| otherwise
|
| otherwise
|
||||||
-> fail "Pseudonym out auf range"
|
-> fail "Pseudonym out auf range"
|
||||||
parseJSON (Aeson.String (map CI.mk . Text.words -> ws))
|
parseJSON (Aeson.String t)
|
||||||
= case preview pseudonymWords ws of
|
= case t ^? _PseudonymText of
|
||||||
Just p -> return p
|
Just p -> return p
|
||||||
Nothing -> fail "Could not parse pseudonym"
|
Nothing -> fail "Could not parse pseudonym"
|
||||||
parseJSON v = flip (Aeson.withArray "Pseudonym") v $ \ws -> do
|
parseJSON v = flip (Aeson.withArray "Pseudonym") v $ \ws -> do
|
||||||
ws' <- toList . map CI.mk <$> mapM parseJSON ws
|
ws' <- toList . map CI.mk <$> mapM parseJSON ws
|
||||||
case preview pseudonymWords ws' of
|
case ws' ^? _PseudonymWords of
|
||||||
Just p -> return p
|
Just p -> return p
|
||||||
Nothing -> fail "Could not parse pseudonym words"
|
Nothing -> fail "Could not parse pseudonym words"
|
||||||
|
|
||||||
instance ToJSON Pseudonym where
|
instance ToJSON Pseudonym where
|
||||||
toJSON = toJSON . (review pseudonymWords :: Pseudonym -> [PseudonymWord])
|
toJSON = toJSON . (review _PseudonymWords :: Pseudonym -> [PseudonymWord])
|
||||||
|
|
||||||
pseudonymWordlist :: [PseudonymWord]
|
pseudonymWordlist :: [PseudonymWord]
|
||||||
pseudonymCharacters :: Set Char
|
pseudonymCharacters :: Set (CI Char)
|
||||||
(pseudonymWordlist, pseudonymCharacters) = $(wordlist "config/wordlist.txt")
|
(pseudonymWordlist, pseudonymCharacters) = $(wordlist "config/wordlist.txt")
|
||||||
|
|
||||||
pseudonymWords :: Prism' [PseudonymWord] Pseudonym
|
_PseudonymWords :: Prism' [PseudonymWord] Pseudonym
|
||||||
pseudonymWords = prism' pToWords pFromWords
|
_PseudonymWords = prism' pToWords pFromWords
|
||||||
where
|
where
|
||||||
pFromWords :: [PseudonymWord] -> Maybe Pseudonym
|
pFromWords :: [PseudonymWord] -> Maybe Pseudonym
|
||||||
pFromWords [w1, w2]
|
pFromWords [w1, w2]
|
||||||
@ -644,32 +644,30 @@ pseudonymWords = prism' pToWords pFromWords
|
|||||||
maxWord :: Num a => a
|
maxWord :: Num a => a
|
||||||
maxWord = 0b111111111111
|
maxWord = 0b111111111111
|
||||||
|
|
||||||
pseudonymText :: Prism' Text Pseudonym
|
_PseudonymText :: Prism' Text Pseudonym
|
||||||
pseudonymText = prism' tToWords tFromWords . pseudonymWords
|
_PseudonymText = prism' tToWords tFromWords . _PseudonymWords
|
||||||
where
|
where
|
||||||
tFromWords :: Text -> Maybe [PseudonymWord]
|
tFromWords :: Text -> Maybe [PseudonymWord]
|
||||||
tFromWords = mapM (disambiguate . CI.mk) . filter (not . null) . Text.split (\c -> not $ Set.member c pseudonymCharacters)
|
tFromWords input
|
||||||
|
| [result] <- input ^.. pseudonymFragments
|
||||||
disambiguate :: CI Text -> Maybe PseudonymWord
|
= Just result
|
||||||
disambiguate inp
|
| otherwise
|
||||||
| [choice] <- inp ^.. pseudonymWord = Just choice
|
= Nothing
|
||||||
| otherwise = Nothing
|
|
||||||
|
|
||||||
tToWords :: [PseudonymWord] -> Text
|
tToWords :: [PseudonymWord] -> Text
|
||||||
tToWords = Text.unwords . map CI.original
|
tToWords = Text.unwords . map CI.original
|
||||||
|
|
||||||
pseudonymWord :: Fold (CI Text) PseudonymWord
|
pseudonymWords :: Fold Text PseudonymWord
|
||||||
pseudonymWord = folding disambiguate
|
pseudonymWords = folding
|
||||||
|
$ \(CI.mk -> input) -> map (view _2) . unsafeHead . groupBy ((==) `on` view _1) . sortBy (comparing $ view _1) . filter ((<= distanceCutoff) . view _1) $ map (distance input &&& id) pseudonymWordlist
|
||||||
where
|
where
|
||||||
disambiguate :: CI Text -> [PseudonymWord]
|
distance = damerauLevenshtein `on` CI.foldedCase
|
||||||
disambiguate inp
|
-- | Arbitrary cutoff point, for reference: ispell cuts off at 1
|
||||||
| [other] <- filter (== inp) pseudonymWordlist = [other]
|
distanceCutoff = 2
|
||||||
| otherwise = do
|
|
||||||
other <- pseudonymWordlist
|
|
||||||
let distance = (damerauLevenshtein `on` CI.foldedCase) inp other
|
|
||||||
guard $ distance <= 2 -- Smallest distance in vocabulary is just 1, be slightly more generous
|
|
||||||
return other
|
|
||||||
|
|
||||||
|
pseudonymFragments :: Fold Text [PseudonymWord]
|
||||||
|
pseudonymFragments = folding
|
||||||
|
$ mapM (toListOf pseudonymWords) . (\l -> guard (length l == 2) *> l) . filter (not . null) . Text.split (\(CI.mk -> c) -> not $ Set.member c pseudonymCharacters)
|
||||||
|
|
||||||
|
|
||||||
data AuthTag
|
data AuthTag
|
||||||
|
|||||||
@ -12,17 +12,16 @@ import qualified Data.Text.IO as Text
|
|||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
|
import Data.CaseInsensitive.Instances ()
|
||||||
import qualified Data.Char as Char
|
|
||||||
|
|
||||||
wordlist :: FilePath -> ExpQ
|
wordlist :: FilePath -> ExpQ
|
||||||
wordlist file = do
|
wordlist file = do
|
||||||
qAddDependentFile file
|
qAddDependentFile file
|
||||||
wordlist' <- runIO $ filter ((||) <$> not . isComment <*> isWord) . Text.lines <$> Text.readFile file
|
wordlist' <- runIO $ filter ((||) <$> not . isComment <*> isWord) . Text.lines <$> Text.readFile file
|
||||||
let usedChars = Set.unions $ map (Set.fromList . (>>= (\c -> [Char.toUpper c, Char.toLower c])) . Text.unpack) wordlist'
|
let usedChars = Set.unions $ map (Set.fromList . map CI.mk . Text.unpack) wordlist'
|
||||||
tupE
|
tupE
|
||||||
[ listE $ map (\(Text.unpack -> word) -> [e|CI.mk $ Text.pack $(lift word)|]) wordlist'
|
[ listE $ map (\(Text.unpack -> word) -> [e|CI.mk $ Text.pack $(lift word)|]) wordlist'
|
||||||
, [e|Set.fromList $(lift $ Set.toList usedChars)|]
|
, [e|Set.fromDistinctAscList $(lift $ Set.toAscList usedChars)|]
|
||||||
]
|
]
|
||||||
|
|
||||||
isWord :: Text -> Bool
|
isWord :: Text -> Bool
|
||||||
|
|||||||
7
templates/messages/ignoredInvalidPseudonym.hamlet
Normal file
7
templates/messages/ignoredInvalidPseudonym.hamlet
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<p>_{MsgInvalidPseudonymSubmissionIgnored oPseudonyms iPseudonym}
|
||||||
|
$if not (null alts)
|
||||||
|
<div>
|
||||||
|
<p>_{MsgPseudonymAutocorrections}
|
||||||
|
<ul>
|
||||||
|
$forall v <- alts
|
||||||
|
<li>#{review _PseudonymText v}
|
||||||
@ -3,4 +3,4 @@ _{MsgSheetDuplicatePseudonym}
|
|||||||
<ul>
|
<ul>
|
||||||
$forall p <- duplicate
|
$forall p <- duplicate
|
||||||
<li .pseudonym>
|
<li .pseudonym>
|
||||||
#{review pseudonymText p}
|
#{review _PseudonymText p}
|
||||||
|
|||||||
@ -6,4 +6,4 @@ _{MsgSheetCreateExisting}
|
|||||||
<dd>
|
<dd>
|
||||||
<ul>
|
<ul>
|
||||||
$forall p <- pseudos
|
$forall p <- pseudos
|
||||||
<li .pseudonym>#{review pseudonymText p}
|
<li .pseudonym>#{review _PseudonymText p}
|
||||||
|
|||||||
@ -38,9 +38,9 @@ spec = do
|
|||||||
it "has sufficient vocabulary" $
|
it "has sufficient vocabulary" $
|
||||||
(length pseudonymWordlist ^ 2) `shouldBe` (succ $ fromIntegral (maxBound - minBound :: Pseudonym))
|
(length pseudonymWordlist ^ 2) `shouldBe` (succ $ fromIntegral (maxBound - minBound :: Pseudonym))
|
||||||
it "has compatible encoding/decoding to/from Text" . property $
|
it "has compatible encoding/decoding to/from Text" . property $
|
||||||
\pseudonym -> preview pseudonymText (review pseudonymText pseudonym) == Just pseudonym
|
\pseudonym -> preview _PseudonymText (review _PseudonymText pseudonym) == Just pseudonym
|
||||||
it "encodes to Text injectively" . property $
|
it "encodes to Text injectively" . property $
|
||||||
\p1 p2 -> p1 /= p2 ==> ((/=) `on` review pseudonymText) p1 p2
|
\p1 p2 -> p1 /= p2 ==> ((/=) `on` review _PseudonymText) p1 p2
|
||||||
|
|
||||||
termExample :: (TermIdentifier, Text) -> Expectation
|
termExample :: (TermIdentifier, Text) -> Expectation
|
||||||
termExample (term, encoded) = example $ do
|
termExample (term, encoded) = example $ do
|
||||||
|
|||||||
Reference in New Issue
Block a user