Files
docspell/modules/webapp/src/main/elm/Data/ItemTemplate.elm
2021-07-04 10:57:53 +02:00

377 lines
7.2 KiB
Elm

{-
Copyright 2020 Docspell Contributors
SPDX-License-Identifier: GPL-3.0-or-later
-}
module Data.ItemTemplate exposing
( ItemTemplate
, TemplateContext
, concEquip
, concPerson
, concat
, concerning
, corrOrg
, corrPerson
, correspondent
, dateLong
, dateShort
, direction
, dueDateLong
, dueDateShort
, empty
, fileCount
, folder
, from
, fromMaybe
, isEmpty
, literal
, map
, name
, nonEmpty
, readTemplate
, render
, source
, splitTokens
)
import Api.Model.IdName exposing (IdName)
import Api.Model.ItemLight exposing (ItemLight)
import Data.Direction exposing (Direction)
import Set
import Util.List
import Util.String
type alias TemplateContext =
{ dateFormatLong : Int -> String
, dateFormatShort : Int -> String
, directionLabel : Direction -> String
}
type ItemTemplate
= ItemTemplate (TemplateContext -> ItemLight -> String)
readTemplate : String -> Maybe ItemTemplate
readTemplate str =
let
read tokens =
List.map patternToken tokens
|> concat
in
if str == "" then
Just empty
else
Maybe.map read (splitTokens str)
render : ItemTemplate -> TemplateContext -> ItemLight -> String
render pattern ctx item =
case pattern of
ItemTemplate f ->
f ctx item
isEmpty : ItemTemplate -> ItemLight -> Bool
isEmpty pattern item =
let
ctx =
{ dateFormatLong = \_ -> "non-empty"
, dateFormatShort = \_ -> "non-empty"
, directionLabel = \_ -> "non-empty"
}
in
render pattern ctx item |> String.isEmpty
nonEmpty : ItemTemplate -> ItemLight -> Bool
nonEmpty pattern item =
isEmpty pattern item |> not
--- Pattern Combinators
map : (String -> String) -> ItemTemplate -> ItemTemplate
map f pattern =
case pattern of
ItemTemplate p ->
ItemTemplate (\ctx -> p ctx >> f)
map2 : (String -> String -> String) -> ItemTemplate -> ItemTemplate -> ItemTemplate
map2 f pattern1 pattern2 =
case ( pattern1, pattern2 ) of
( ItemTemplate p1, ItemTemplate p2 ) ->
ItemTemplate (\ctx -> \i -> f (p1 ctx i) (p2 ctx i))
combine : String -> ItemTemplate -> ItemTemplate -> ItemTemplate
combine sep p1 p2 =
map2
(\s1 ->
\s2 ->
List.filter (String.isEmpty >> not) [ s1, s2 ]
|> String.join sep
)
p1
p2
concat : List ItemTemplate -> ItemTemplate
concat patterns =
ItemTemplate
(\ctx ->
\i ->
List.map (\p -> render p ctx i) patterns
|> String.join ""
)
firstNonEmpty : List ItemTemplate -> ItemTemplate
firstNonEmpty patterns =
ItemTemplate
(\ctx ->
\i ->
List.map (\p -> render p ctx i) patterns
|> List.filter (String.isEmpty >> not)
|> List.head
|> Maybe.withDefault ""
)
--- Patterns
from : (ItemLight -> String) -> ItemTemplate
from f =
ItemTemplate (\_ -> f)
fromMaybe : (TemplateContext -> ItemLight -> Maybe String) -> ItemTemplate
fromMaybe f =
ItemTemplate (\ctx -> f ctx >> Maybe.withDefault "")
literal : String -> ItemTemplate
literal str =
ItemTemplate (\_ -> \_ -> str)
empty : ItemTemplate
empty =
literal ""
name : ItemTemplate
name =
from (.name >> Util.String.underscoreToSpace)
direction : ItemTemplate
direction =
let
dirStr ctx ms =
Maybe.andThen Data.Direction.fromString ms
|> Maybe.map ctx.directionLabel
in
fromMaybe (\ctx -> .direction >> dirStr ctx)
dateLong : ItemTemplate
dateLong =
ItemTemplate (\ctx -> .date >> ctx.dateFormatLong)
dateShort : ItemTemplate
dateShort =
ItemTemplate (\ctx -> .date >> ctx.dateFormatShort)
dueDateLong : ItemTemplate
dueDateLong =
fromMaybe (\ctx -> .dueDate >> Maybe.map ctx.dateFormatLong)
dueDateShort : ItemTemplate
dueDateShort =
fromMaybe (\ctx -> .dueDate >> Maybe.map ctx.dateFormatShort)
source : ItemTemplate
source =
from .source
folder : ItemTemplate
folder =
from (.folder >> getName)
corrOrg : ItemTemplate
corrOrg =
from (.corrOrg >> getName)
corrPerson : ItemTemplate
corrPerson =
from (.corrPerson >> getName)
correspondent : ItemTemplate
correspondent =
combine ", " corrOrg corrPerson
concPerson : ItemTemplate
concPerson =
from (.concPerson >> getName)
concEquip : ItemTemplate
concEquip =
from (.concEquipment >> getName)
concerning : ItemTemplate
concerning =
combine ", " concPerson concEquip
fileCount : ItemTemplate
fileCount =
from (.attachments >> List.length >> String.fromInt)
--- Helpers
getName : Maybe IdName -> String
getName =
Maybe.map .name >> Maybe.withDefault ""
--- Parse pattern
knownPattern : String -> Maybe ItemTemplate
knownPattern str =
case str of
"{{name}}" ->
Just name
"{{source}}" ->
Just source
"{{folder}}" ->
Just folder
"{{corrOrg}}" ->
Just corrOrg
"{{corrPerson}}" ->
Just corrPerson
"{{correspondent}}" ->
Just correspondent
"{{concPerson}}" ->
Just concPerson
"{{concEquip}}" ->
Just concEquip
"{{concerning}}" ->
Just concerning
"{{fileCount}}" ->
Just fileCount
"{{dateLong}}" ->
Just dateLong
"{{dateShort}}" ->
Just dateShort
"{{dueDateLong}}" ->
Just dueDateLong
"{{dueDateShort}}" ->
Just dueDateShort
"{{direction}}" ->
Just direction
_ ->
Nothing
patternToken : String -> ItemTemplate
patternToken str =
knownPattern str
|> Maybe.withDefault
(alternativeToken str
|> Maybe.withDefault (literal str)
)
alternativeToken : String -> Maybe ItemTemplate
alternativeToken str =
let
inner =
String.dropLeft 2 str
|> String.dropRight 2
|> String.split "|"
|> List.filter (String.isEmpty >> not)
pattern s =
knownPattern ("{{" ++ s ++ "}}")
|> Maybe.withDefault (literal s)
in
if String.startsWith "{{" str && String.endsWith "}}" str then
case inner of
[] ->
Nothing
_ ->
List.map pattern inner
|> firstNonEmpty
|> Just
else
Nothing
splitTokens : String -> Maybe (List String)
splitTokens str =
let
begins =
String.indexes "{{" str
ends =
String.indexes "}}" str
|> List.map ((+) 2)
indexes =
Set.union (Set.fromList begins) (Set.fromList ends)
|> Set.insert 0
|> Set.insert (String.length str)
|> Set.toList
|> List.sort
mkSubstring i1 i2 =
String.slice i1 i2 str
in
if List.length begins == List.length ends then
Util.List.sliding mkSubstring indexes |> Just
else
Nothing