12 Commits
Author SHA1 Message Date
Paul Brinkmeier 3ff0dc79d5 Add aarch64-linux target and bump version 2025-07-13 12:06:11 +02:00
paul-brinkmeier 911e0f5f4f Force search index evaluation on startup 2025-07-10 23:15:52 +02:00
paul-brinkmeier 11ed38d364 Improve flake situation 2025-07-10 23:14:45 +02:00
paul 7d7f628175 Bump nixpkgs 2025-02-22 19:26:10 +01:00
paul 4ffefe1f9e Bump version 2025-02-22 18:59:35 +01:00
paul 34c28813fe Implement slightly faster search 2025-02-22 18:58:12 +01:00
paul 8b31df9193 Add search benchmark 2025-02-22 18:37:32 +01:00
paul a37643b5cd Simplify nix setup 2025-02-22 11:44:49 +01:00
paul 61355405d5 Implement search 2025-02-22 11:26:25 +01:00
paul d698ebce72 Bump nixpkgs 2023-12-27 22:16:49 +01:00
paul b0451300a5 Bump version in cabal file too 2023-12-27 13:55:48 +01:00
paul 7afe93727c Update dependencies, remove Nix overlay 2023-12-26 22:30:12 +01:00
15 changed files with 278 additions and 273 deletions
+2
View File
@@ -1,4 +1,6 @@
.stack-work/ .stack-work/
dist-newstyle
.vscode/ .vscode/
*.swp *.swp
result result
bench.html
+3 -11
View File
@@ -6,12 +6,6 @@
$ nix build $ nix build
``` ```
## Running
```
$ nix run
```
## Building the Docker image ## Building the Docker image
``` ```
@@ -26,9 +20,7 @@ Includes Stack, GHC, `haskell-language-server`, `gen-hie` etc.
$ nix develop $ nix develop
``` ```
## Running Stack and GHC ## TODO
``` - [x] Benchmark, profile and optimize search
$ nix run .#stack - [ ] Trim down the docker image
$ nix run .#ghc
```
+64 -39
View File
@@ -3,6 +3,7 @@
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
@@ -12,11 +13,13 @@
module Main (main) where module Main (main) where
import Control.DeepSeq (force)
import Control.Exception (evaluate)
import Control.Monad (void)
import Data.Char (chr) import Data.Char (chr)
import Data.FileEmbed (embedFile) import Data.FileEmbed (embedFile)
import Data.Foldable (for_) import Data.Foldable (for_)
import Data.List (intercalate) import Data.Maybe (fromMaybe)
import Data.Maybe (fromMaybe, maybeToList)
import Data.Text (Text) import Data.Text (Text)
import Data.Word (Word8) import Data.Word (Word8)
import Network.HTTP.Media ((//), (/:)) import Network.HTTP.Media ((//), (/:))
@@ -48,15 +51,19 @@ import qualified Data.Text.Encoding as Encoding
import qualified Network.Wai.Handler.Warp as Warp import qualified Network.Wai.Handler.Warp as Warp
import qualified Text.Blaze.Html5 as H import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A import qualified Text.Blaze.Html5.Attributes as A
import qualified Unicode.Char.General.Blocks as UnicodeBlocks
import qualified Unicode.Char.General.Names as UnicodeNames
import qualified UToy.Decode as Decode import qualified UToy.Decode as Decode
import qualified UToy.Table as Table import qualified UToy.Table as Table
import qualified UToy.Names as Names
import qualified UToy.Parsers as Parsers import qualified UToy.Parsers as Parsers
main :: IO () main :: IO ()
main = Warp.run 3000 app main = do
-- Forced evaluation of Names.lowerNames to reduce number of thunks.
putStrLn "* Building search index"
void $ evaluate $ force Names.lowerNames
putStrLn "* Listening on http://localhost:3000"
Warp.run 3000 app
app :: Application app :: Application
app = serve (Proxy :: Proxy API) server app = serve (Proxy :: Proxy API) server
@@ -66,13 +73,14 @@ type API =
:<|> "utf8" :> Capture "bytes" Text :> Get '[PlainText, HTML] Utf8Model :<|> "utf8" :> Capture "bytes" Text :> Get '[PlainText, HTML] Utf8Model
:<|> "codepoints" :> Capture "codepoints" Text :> Get '[PlainText, HTML] CodepointsModel :<|> "codepoints" :> Capture "codepoints" Text :> Get '[PlainText, HTML] CodepointsModel
:<|> "text" :> Capture "text" Text :> Get '[PlainText, HTML] TextModel :<|> "text" :> Capture "text" Text :> Get '[PlainText, HTML] TextModel
:<|> "search" :> Capture "search" Text :> Get '[PlainText, HTML] SearchModel
server :: Server API server :: Server API
server = server =
rootR :<|> utf8R :<|> codepointsR :<|> textR rootR :<|> utf8R :<|> codepointsR :<|> textR :<|> searchR
where where
rootR host = do rootR host' = do
pure $ RootModel $ fromMaybe "" host pure $ RootModel $ fromMaybe "" host'
utf8R bytesP = do utf8R bytesP = do
bytes <- Parsers.parseHexBytes bytesP `orThrow` const err400 bytes <- Parsers.parseHexBytes bytesP `orThrow` const err400
@@ -85,6 +93,9 @@ server =
textR textP = do textR textP = do
pure $ TextModel textP pure $ TextModel textP
searchR searchP = do
pure $ mkSearchModel searchP
-- / -- /
newtype RootModel = RootModel newtype RootModel = RootModel
@@ -96,6 +107,7 @@ examples =
[ "/text/✅🤔" [ "/text/✅🤔"
, "/codepoints/x2705+x1F914" , "/codepoints/x2705+x1F914"
, "/utf8/e2.9c.85.f0.9f.a4.94" , "/utf8/e2.9c.85.f0.9f.a4.94"
, "/search/asterisk"
] ]
instance MimeRender PlainText RootModel where instance MimeRender PlainText RootModel where
@@ -139,8 +151,8 @@ instance MimeRender PlainText Utf8Model where
Right c -> Right c ->
[ Text.pack [c] [ Text.pack [c]
, Text.pack $ printf "U+%04X" c , Text.pack $ printf "U+%04X" c
, Text.pack $ intercalate ", " $ allNames c , Text.intercalate ", " $ Names.allNames c
, Text.pack $ fromMaybe "" $ blockName c , fromMaybe "" $ Names.blockName c
] ]
) )
] ]
@@ -158,8 +170,8 @@ instance MimeRender HTML Utf8Model where
Right c -> do Right c -> do
H.td $ H.input ! A.class_ "charbox" ! A.value (H.toValue [c]) H.td $ H.input ! A.class_ "charbox" ! A.value (H.toValue [c])
H.td $ H.code $ printfHtml "U+%04X" c H.td $ H.code $ printfHtml "U+%04X" c
H.td $ H.code $ H.toHtml $ intercalate ", " $ allNames c H.td $ H.code $ H.toHtml $ Text.intercalate ", " $ Names.allNames c
H.td $ H.code $ H.toHtml $ fromMaybe "" $ blockName c H.td $ H.code $ H.toHtml $ fromMaybe "" $ Names.blockName c
-- /codepoints/<codepoints> -- /codepoints/<codepoints>
@@ -173,14 +185,14 @@ mkCodepointsModel =
-- Limit number of returned codepoints. Otherwise it's -- Limit number of returned codepoints. Otherwise it's
-- too easy to provoke massive response bodies with requests like -- too easy to provoke massive response bodies with requests like
-- /codepoints/0-99999999 -- /codepoints/0-99999999
. take 100000 . take 100_000
. map go . map go
. concatMap (uncurry enumFromTo) . concatMap (uncurry enumFromTo)
where where
go codepoint = (codepoint, toChar codepoint) go codepoint = (codepoint, toChar codepoint)
toChar codepoint toChar codepoint
| codepoint > 0x10FFFF = Left "Would be too big (maximum: U+10FFFF)" | codepoint > 0x10_FFFF = Left "Would be too big (maximum: U+10FFFF)"
| isSurrogate codepoint = Left "Is a surrogate" | isSurrogate codepoint = Left "Is a surrogate"
| otherwise = Right $ chr $ fromIntegral codepoint | otherwise = Right $ chr $ fromIntegral codepoint
@@ -196,8 +208,8 @@ instance MimeRender PlainText CodepointsModel where
Right c -> Right c ->
[ Text.pack [c] [ Text.pack [c]
, Text.pack $ printf "U+%04X" c , Text.pack $ printf "U+%04X" c
, Text.pack $ intercalate ", " $ allNames c , Text.intercalate ", " $ Names.allNames c
, Text.pack $ fromMaybe "" $ blockName c , fromMaybe "" $ Names.blockName c
] ]
) )
| (codepoint, eiC) <- model.codepoints | (codepoint, eiC) <- model.codepoints
@@ -214,8 +226,8 @@ instance MimeRender HTML CodepointsModel where
Right c -> do Right c -> do
H.td $ H.input ! A.class_ "charbox" ! A.value (H.toValue [c]) H.td $ H.input ! A.class_ "charbox" ! A.value (H.toValue [c])
H.td $ H.code $ printfHtml "U+%04X" c H.td $ H.code $ printfHtml "U+%04X" c
H.td $ H.code $ H.toHtml $ intercalate ", " $ allNames c H.td $ H.code $ H.toHtml $ Text.intercalate ", " $ Names.allNames c
H.td $ H.code $ H.toHtml $ fromMaybe "" $ blockName c H.td $ H.code $ H.toHtml $ fromMaybe "" $ Names.blockName c
-- /text/<text> -- /text/<text>
@@ -224,28 +236,50 @@ newtype TextModel = TextModel
} }
instance MimeRender HTML TextModel where instance MimeRender HTML TextModel where
mimeRender _ model = renderHtml $ documentWithBody $ do mimeRender _ model = charTableHtml $ Text.unpack model.text
H.table $ for_ (Text.unpack model.text) $ \c -> do instance MimeRender PlainText TextModel where
mimeRender _ model = charTableText $ Text.unpack model.text
-- /search/<search>
newtype SearchModel = SearchModel
{ results :: [Char]
}
mkSearchModel :: Text -> SearchModel
mkSearchModel search = SearchModel $ take 100_000 $ Names.searchCaseInsensitive search
instance MimeRender HTML SearchModel where
mimeRender _ model = charTableHtml model.results
instance MimeRender PlainText SearchModel where
mimeRender _ model = charTableText model.results
-- Utilities
charTableHtml :: [Char] -> BL.ByteString
charTableHtml chars =
renderHtml $ documentWithBody $ do
H.table $ for_ chars $ \c -> do
H.tr $ do H.tr $ do
H.td $ H.input ! A.class_ "charbox" ! A.value (H.toValue [c]) H.td $ H.input ! A.class_ "charbox" ! A.value (H.toValue [c])
H.td $ H.code $ printfHtml "U+%04X" c H.td $ H.code $ printfHtml "U+%04X" c
H.td $ H.code $ H.toHtml $ intercalate ", " $ allNames c H.td $ H.code $ H.toHtml $ Text.intercalate ", " $ Names.allNames c
H.td $ H.code $ H.toHtml $ fromMaybe "" $ blockName c H.td $ H.code $ H.toHtml $ fromMaybe "" $ Names.blockName c
instance MimeRender PlainText TextModel where
mimeRender _ model = renderText $ Table.render " " charTableText :: [Char] -> BL.ByteString
charTableText chars =
renderText $ Table.render " "
[ map Table.cl [ map Table.cl
[ Text.pack [c] [ Text.pack [c]
, Text.pack $ printf "U+%04X" c , Text.pack $ printf "U+%04X" c
, Text.pack $ intercalate ", " $ allNames c , Text.intercalate ", " $ Names.allNames c
, Text.pack $ fromMaybe "" $ blockName c , fromMaybe "" $ Names.blockName c
] ]
| c <- Text.unpack model.text | c <- chars
] ]
-- Utilities
renderText :: Text -> BL.ByteString renderText :: Text -> BL.ByteString
renderText = BL.fromStrict . Encoding.encodeUtf8 renderText = BL.fromStrict . Encoding.encodeUtf8
@@ -255,15 +289,6 @@ showByteHex = printf " %02X"
showByteBin :: Word8 -> String showByteBin :: Word8 -> String
showByteBin = printf "%08b" showByteBin = printf "%08b"
-- | Retrieve name and aliases (suffixed with @*@) of a 'Char'.
allNames :: Char -> [String]
allNames c =
maybeToList (UnicodeNames.name c)
++ map (++ "*") (UnicodeNames.nameAliases c)
blockName :: Char -> Maybe String
blockName c = UnicodeBlocks.blockName . UnicodeBlocks.blockDefinition <$> UnicodeBlocks.block c
orThrow :: Either a b -> (a -> ServerError) -> Handler b orThrow :: Either a b -> (a -> ServerError) -> Handler b
orThrow (Left err) f = throwError $ f err orThrow (Left err) f = throwError $ f err
orThrow (Right val) _ = pure val orThrow (Right val) _ = pure val
+69
View File
@@ -0,0 +1,69 @@
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Criterion.Main (bench, bgroup, defaultMain, whnf)
import Data.Char (ord)
import Data.Maybe (maybeToList)
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Vector as Vector
import qualified Unicode.Char.General.Names as UnicodeNames
main = defaultMain
[ bench "naiveSearchCI" $ whnf naiveSearchCI "latin"
, bench "naiveSearchCS" $ whnf naiveSearchCS "LATIN"
, bench "memoSearchCI" $ whnf memoSearchCI "latin"
, bench "memoSearchCS" $ whnf memoSearchCS "LATIN"
]
naiveSearchCI :: Text -> [Char]
naiveSearchCI search =
filter go [minBound..maxBound]
where
go c = any matches $ allNames c
matches t = Text.toLower search `Text.isInfixOf` Text.toLower (Text.pack t)
naiveSearchCS :: Text -> [Char]
naiveSearchCS search =
filter go [minBound..maxBound]
where
go c = any matches $ allNames c
matches t = search `Text.isInfixOf` Text.pack t
memoSearchCS :: Text -> [Char]
memoSearchCS search =
filter go [minBound..maxBound]
where
go c = any matches $ allNamesText c
matches t = search `Text.isInfixOf` t
memoSearchCI :: Text -> [Char]
memoSearchCI search =
filter go [minBound..maxBound]
where
go c = any matches $ allNamesTextLower c
matches t = Text.toLower search `Text.isInfixOf` t
-- | Retrieve name and aliases (suffixed with @*@) of a 'Char'.
allNames :: Char -> [String]
allNames c =
maybeToList (UnicodeNames.name c)
++ map (++ "*") (UnicodeNames.nameAliases c)
allNamesText :: Char -> [Text]
allNamesText c = Vector.unsafeIndex textNames $ ord c
textNames :: Vector.Vector [Text]
textNames = Vector.fromList $ map go [minBound..maxBound]
where
go c = map Text.pack $ allNames c
allNamesTextLower :: Char -> [Text]
allNamesTextLower c = Vector.unsafeIndex textNamesLower $ ord c
textNamesLower :: Vector.Vector [Text]
textNamesLower = Vector.fromList $ map go [minBound..maxBound]
where
go c = map (Text.toLower . Text.pack) $ allNames c
Generated
+38 -4
View File
@@ -1,25 +1,59 @@
{ {
"nodes": { "nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1680273054, "lastModified": 1751290243,
"narHash": "sha256-Bs6/5LpvYp379qVqGt9mXxxx9GSE789k3oFc+OAL07M=", "narHash": "sha256-kNf+obkpJZWar7HZymXZbW+Rlk3HTEIMlpc6FCNz0Ds=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "3364b5b117f65fe1ce65a3cdd5612a078a3b31e3", "rev": "5ab036a8d97cb9476fbe81b09076e6e91d15e1b6",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nixos", "owner": "nixos",
"ref": "nixpkgs-unstable", "ref": "release-24.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"root": { "root": {
"inputs": { "inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
} }
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",
+52 -79
View File
@@ -1,90 +1,63 @@
{ {
description = "Unicode toy"; description = "Unicode toy";
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; inputs.nixpkgs.url = "github:nixos/nixpkgs/release-24.11";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs }: outputs = { self, nixpkgs, flake-utils }:
let flake-utils.lib.eachSystem ["x86_64-linux" "aarch64-linux" "aarch64-darwin"] (system:
overlay = import ./nix/overlay.nix; let
settings = import ./nix/settings.nix; pkgs = import nixpkgs { inherit system; };
haskellDeps = import ./nix/haskell-deps.nix;
pkgs = nixpkgs.legacyPackages.x86_64-linux.extend overlay; inherit (pkgs.haskell.lib) compose;
haskellPackages = pkgs.haskell.packages."${settings.ghc}";
ghc = haskellPackages.ghcWithPackages haskellDeps; # Fix broken unicode-data-names by overriding the version of the unicode-data package.
haskellPackagesOverrides = self: super: {
unicode-data = super.unicode-data_0_6_0;
unicode-data-names = pkgs.haskell.lib.markUnbroken super.unicode-data-names;
};
# Wrap stack to disable its slow Nix integration. utoy = pkgs.lib.pipe
# Instead, make it use the GHC defined above. # The basic package
stack = pkgs.stdenv.mkDerivation { (pkgs.haskellPackages.developPackage {
name = "stack"; root = ./.;
overrides = haskellPackagesOverrides;
})
# And some build configuration.
# See https://nixos.org/manual/nixpkgs/unstable/#haskell-packaging-helpers.
[
# Remove warp which pulls GHC into the runtime deps for some reason.
(compose.overrideCabal (drv: {
postInstall = ''
remove-references-to -t ${pkgs.haskellPackages.warp} $out/bin/utoy
'';
}))
# Return only the bin folder to curb image size
compose.justStaticExecutables
];
in {
packages = rec {
docker =
pkgs.dockerTools.buildImage {
name = "git.pbrinkmeier.de/paul/utoy";
tag = utoy.version;
config.Cmd = [ "${utoy}/bin/utoy" ];
};
# The build is simply a call to makeWrapper, so we don't have to default = utoy;
# do any of the typical build steps. };
phases = [ "installPhase" ];
nativeBuildInputs = [ pkgs.makeWrapper ]; devShells.default =
# makeBinaryWrapper creates a stack executable for us that uses (pkgs.haskellPackages.developPackage {
# the GHC defined in this file. root = ./.;
installPhase = '' overrides = haskellPackagesOverrides;
makeWrapper ${pkgs.stack}/bin/stack $out/bin/stack \ modifier = drv:
--prefix PATH : ${ghc}/bin \ pkgs.haskell.lib.addBuildTools drv [
--add-flags '--no-nix --system-ghc --no-install-ghc' pkgs.cabal-install
''; pkgs.haskellPackages.implicit-hie
}; pkgs.haskell-language-server
utoy = pkgs.haskell.lib.justStaticExecutables (haskellPackages.callPackage
({ mkDerivation }:
mkDerivation {
# Keep this in sync with package.yaml
version = "0.6";
pname = "utoy";
license = pkgs.lib.licenses.mit;
src =
# We only need these files for building:
let
whitelist = [
./LICENSE
./utoy.cabal
./Setup.hs
./app
./src
./static
./test
]; ];
in cabal2nixOptions = "--benchmark";
pkgs.lib.sources.cleanSourceWith { }).env;
src = ./.; });
filter = path: _type: pkgs.lib.any (prefix: pkgs.lib.hasPrefix (toString prefix) path) whitelist;
};
libraryHaskellDepends = haskellDeps haskellPackages;
}) {});
in {
packages.x86_64-linux = {
inherit ghc;
inherit stack;
docker =
pkgs.dockerTools.buildImage {
name = "git.pbrinkmeier.de/paul/utoy";
tag = utoy.version;
config.Cmd = [ "${utoy}/bin/utoy" ];
};
default = utoy;
};
devShells.x86_64-linux.default = pkgs.mkShell {
packages = [
stack
ghc
haskellPackages.haskell-language-server
haskellPackages.implicit-hie
];
shellHook = ''
PS1+="(utoy) ";
'';
};
};
} }
+6 -6
View File
@@ -1,10 +1,10 @@
cradle: cradle:
stack: cabal:
- path: "./src" - path: "src"
component: "utoy:lib" component: "lib:utoy"
- path: "./app/Main.hs" - path: "app/Main.hs"
component: "utoy:exe:utoy" component: "utoy:exe:utoy"
- path: "./test" - path: "bench/Main.hs"
component: "utoy:test:utoy-test" component: "utoy:bench:utoy-bench"
-13
View File
@@ -1,13 +0,0 @@
haskellPackages: with haskellPackages; [
attoparsec
blaze-html
bytestring
file-embed
http-media
servant-server
text
unicode-data
unicode-data-names
wai
warp
]
-23
View File
@@ -1,23 +0,0 @@
let
settings = import ./settings.nix;
overlay = final: prev: {
haskell = prev.haskell // {
packages = prev.haskell.packages // {
"${settings.ghc}" = prev.haskell.packages."${settings.ghc}".override {
overrides = haskellOverlay prev;
};
};
};
};
haskellOverlay = pkgs: final: prev: {
attoparsec-iso8601 = prev.attoparsec-iso8601_1_1_0_0;
http-api-data = prev.http-api-data_0_5;
servant = pkgs.haskell.lib.doJailbreak prev.servant;
servant-server = pkgs.haskell.lib.doJailbreak prev.servant-server;
unicode-data = prev.unicode-data_0_4_0_1;
unicode-data-names = pkgs.haskell.lib.markUnbroken prev.unicode-data-names;
};
in
overlay
-5
View File
@@ -1,5 +0,0 @@
{
# GHC version to use with Nix.
# Should match the one in stack.yaml.
ghc = "ghc944";
}
-70
View File
@@ -1,70 +0,0 @@
# Adapted from new-template.hsfiles
name: utoy
# Keep this in sync with the version in flake.nix.
version: 0.6
git: "https://git.pbrinkmeier.de/paul/utoy"
license: MIT
author: "Paul Brinkmeier"
maintainer: "hallo@pbrinkmeier.de"
copyright: "2023 Paul Brinkmeier"
extra-source-files:
- README.md
- static/utoy.css
dependencies:
- base >= 4.7 && < 5
- attoparsec
- text
ghc-options:
- -Wall
- -Wcompat
- -Widentities
- -Wincomplete-record-updates
- -Wincomplete-uni-patterns
- -Wmissing-export-lists
- -Wmissing-home-modules
- -Wpartial-fields
- -Wredundant-constraints
library:
source-dirs: src
executables:
utoy:
main: Main.hs
source-dirs: app
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- utoy
- blaze-html
- bytestring
- file-embed
- http-media
- servant-server
- text
- unicode-data
- unicode-data-names
- wai
- warp
# Fix "Multiple files use the same module name", see
# https://stackoverflow.com/questions/67519851/multiple-files-use-the-same-module-name
when:
- condition: false
other-modules: Paths_utoy
tests:
utoy-test:
main: Spec.hs
source-dirs: test
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- utoy
+31
View File
@@ -0,0 +1,31 @@
module UToy.Names (allNames, blockName, lowerNames, searchCaseInsensitive) where
import Data.Char (ord)
import Data.Maybe (maybeToList)
import Data.Text (Text)
import Data.Vector (Vector)
import qualified Data.Text as Text
import qualified Data.Vector as Vector
import qualified Unicode.Char.General.Blocks as UnicodeBlocks
import qualified Unicode.Char.General.Names as UnicodeNames
-- | Retrieve name and aliases (suffixed with @*@) of a 'Char'.
allNames :: Char -> [Text]
allNames c = map Text.pack $
maybeToList (UnicodeNames.name c)
++ map (++ "*") (UnicodeNames.nameAliases c)
blockName :: Char -> Maybe Text
blockName c = Text.pack . UnicodeBlocks.blockName . UnicodeBlocks.blockDefinition <$> UnicodeBlocks.block c
searchCaseInsensitive :: Text -> [Char]
searchCaseInsensitive search = filter go [minBound..maxBound]
where
go c = any matches $ Vector.unsafeIndex lowerNames (ord c - ord minBound)
matches t = Text.toLower search `Text.isInfixOf` t
lowerNames :: Vector (Vector Text)
lowerNames = Vector.fromList $ map go [minBound..maxBound]
where
go = Vector.fromList . map Text.toLower . allNames
-5
View File
@@ -1,5 +0,0 @@
# You can get a working environment using nix develop.
# Keep this in sync with nix/settings.nix
resolver: ghc-9.4.4
packages:
- .
-2
View File
@@ -1,2 +0,0 @@
main :: IO ()
main = putStrLn "Test suite not yet implemented"
+13 -16
View File
@@ -1,11 +1,7 @@
cabal-version: 1.12 cabal-version: 1.12
-- This file has been generated from package.yaml by hpack version 0.35.2.
--
-- see: https://github.com/sol/hpack
name: utoy name: utoy
version: 0.6 version: 0.6.3
author: Paul Brinkmeier author: Paul Brinkmeier
maintainer: hallo@pbrinkmeier.de maintainer: hallo@pbrinkmeier.de
copyright: 2023 Paul Brinkmeier copyright: 2023 Paul Brinkmeier
@@ -24,6 +20,7 @@ library
exposed-modules: exposed-modules:
UToy.Decode UToy.Decode
UToy.Parsers UToy.Parsers
UToy.Names
UToy.Table UToy.Table
other-modules: other-modules:
Paths_utoy Paths_utoy
@@ -34,6 +31,9 @@ library
attoparsec attoparsec
, base >=4.7 && <5 , base >=4.7 && <5
, text , text
, unicode-data
, unicode-data-names
, vector
default-language: Haskell2010 default-language: Haskell2010
executable utoy executable utoy
@@ -46,28 +46,25 @@ executable utoy
, base >=4.7 && <5 , base >=4.7 && <5
, blaze-html , blaze-html
, bytestring , bytestring
, deepseq
, file-embed , file-embed
, http-media , http-media
, servant-server , servant-server
, text , text
, unicode-data
, unicode-data-names
, utoy , utoy
, wai , wai
, warp , warp
default-language: Haskell2010 default-language: Haskell2010
test-suite utoy-test benchmark utoy-bench
type: exitcode-stdio-1.0 type: exitcode-stdio-1.0
main-is: Spec.hs main-is: Main.hs
other-modules:
Paths_utoy
hs-source-dirs: hs-source-dirs:
test bench
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
build-depends: build-depends:
attoparsec base >=4.7 && <5
, base >=4.7 && <5 , criterion
, text , text
, utoy , unicode-data-names
, vector
default-language: Haskell2010 default-language: Haskell2010