Skip to content

Commit

Permalink
md phlog post links + images rendered respective gopher item link
Browse files Browse the repository at this point in the history
  • Loading branch information
Home committed Dec 30, 2024
1 parent 276fc2b commit 455f8a5
Showing 6 changed files with 111 additions and 18 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -5,6 +5,11 @@ user in the changelog.

## [Unreleased]

### Added

* Markdown link footnotes and images are now transformed into the appropriate kind of
gopher menu item type if the article is being rendered as a menu

## [0.27.0.0] - 2024-12-20

### Added
5 changes: 4 additions & 1 deletion bore.cabal
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ cabal-version: 2.2
-- see: https://github.com/sol/hpack

name: bore
version: 0.26.0.0
version: 0.27.0.0
synopsis: Build gopherholes.
description: Static site builder, but for gopherholes. Manage phlogs with tags, use the Markdown renderer and Mustache templating system.
category: Network
@@ -82,6 +82,7 @@ library
, hashmap
, hourglass
, liquidhaskell
, mime-types
, mtl
, mustache
, neat-interpolation
@@ -145,6 +146,7 @@ executable bore
, hashmap
, hourglass
, liquidhaskell
, mime-types
, mtl
, mustache
, neat-interpolation
@@ -206,6 +208,7 @@ test-suite bore-test
, hashmap
, hourglass
, liquidhaskell
, mime-types
, mtl
, mustache
, neat-interpolation
1 change: 1 addition & 0 deletions package.yaml
Original file line number Diff line number Diff line change
@@ -71,6 +71,7 @@ dependencies:
- spacecookie
- edit-distance
- safe
- mime-types

library:
source-dirs: src
45 changes: 42 additions & 3 deletions src/Bore/Text/Gophermap.hs
Original file line number Diff line number Diff line change
@@ -16,10 +16,48 @@ specification)
-}

module Bore.Text.Gophermap (toGophermap) where
module Bore.Text.Gophermap (toGophermap, imagePathToGopherType, gopherTypeByExt) where

import Data.Text (Text)
import qualified Data.Text as T
import System.FilePath (takeExtension)
import Network.Mime (defaultMimeMap, mimeByExt)
import qualified Data.ByteString.Char8 as BS
import Data.List (isPrefixOf)

{- | Return a guess for the gopher item type based on the file extension of the
path/selector passed (assumed to be an image).
The default is just 'I' for image file. Basically, if it's not a GIF.
Could be expanded to support non-canonical 'p' (png) type.
-}
imagePathToGopherType
:: FilePath
-- ^ Selector for an image which includes the file extension.
-> Char
-- ^ Gopher item type.
imagePathToGopherType selector =
case takeExtension selector of
".gif" -> 'g'
_ -> 'I'

-- could be expanded to handle different things
-- Map MIME types to Gopher menu item types
mimeToGopherType :: BS.ByteString -> Char
mimeToGopherType mime
| "text/" `BS.isPrefixOf` mime = '0' -- Plain text
| "image/" `BS.isPrefixOf` mime = 'I' -- Image
| "audio/" `BS.isPrefixOf` mime = 's' -- Sound
| otherwise = '9' -- Binary or other

-- fixme: what about http/h
-- Map file extensions to Gopher menu item types
gopherTypeByExt :: BS.ByteString -> FilePath -> Char
gopherTypeByExt defaultType filePath
| "http://" `isPrefixOf` filePath || "https://" `isPrefixOf` filePath = 'h'
| otherwise =
mimeToGopherType $ mimeByExt defaultMimeMap defaultType (T.pack $ "." ++ takeExtension filePath)

{- | Transform a regular text file into a Gopher menu.
@@ -41,9 +79,10 @@ toGophermap domain port inputText = (T.unlines . map processLine . T.lines $ inp
isMenuEntry :: Text -> Bool
isMenuEntry t = not (T.null t) && T.head t `elem` gopherFileTypes && "\t" `T.isInfixOf` t

-- Gopher file types as per RFC1436 and spacecookie.
-- Gopher file types as per RFC1436 and spacecookie. Also include 'h' for HTTP. May
-- have to be expanded in future. Potential for complications.
gopherFileTypes :: [Char]
gopherFileTypes = "0123456789+gITi"
gopherFileTypes = "0123456789+gITih"

-- Add domain and port if they're missing from the line.
addDomainAndPort :: Text -> Text
5 changes: 3 additions & 2 deletions src/Bore/Text/Template/Replacements.hs
Original file line number Diff line number Diff line change
@@ -103,12 +103,13 @@ initialSubstitutions library maybeFrontMatter =
let
-- FIXME: why is there this doubling-up on the effort of translating the frontmatter, seemingly? just because it can't handle lists?
postFm = substitutionsFromFrontMatter maybeFrontMatter (toSubstitutions . toMustache <$> maybeFrontMatter)
gophermap = fromMaybe False (isGophermap <$> maybeFrontMatter)
in
postFm ++ [
("containerize", overText (lambdaContainerize library.containers)),
("figlet", overText (lambdaFiglet library.fonts)),
("wrapVt320WideMode", overText (wrapMarkdown 132)),
("wrapVt320StandardMode", overText (wrapMarkdown 80))
("wrapVt320WideMode", overText (wrapMarkdown library gophermap 132)),
("wrapVt320StandardMode", overText (wrapMarkdown library gophermap 80))
]
where
-- FIXME; if i want to add tagging support i'd add it here for hardcoding
68 changes: 56 additions & 12 deletions src/Bore/Text/Wrap.hs
Original file line number Diff line number Diff line change
@@ -18,10 +18,20 @@ import Data.Text (Text)
import qualified Data.Text as T
import Text.Wrap (wrapText, defaultWrapSettings)
import Control.Monad.State (State, get, modify, runState, gets)
import Data.Maybe (fromMaybe)

import qualified Bore.Library as Library
import Bore.Text.Gophermap (imagePathToGopherType, gopherTypeByExt)
import Bore.Config

-- Configuration for the renderer
data WrapConfig = WrapConfig
{ lineWidth :: Int -- Configurable line width
{ library :: Library.Library
-- ^ Assets and config(s) to use for rendering.
, gopherMap :: Bool
-- ^ Render as a gophermap? Meaning menu links.
, lineWidth :: Int
-- ^ Configurable line width
}

-- State to hold configuration and footnote data
@@ -33,10 +43,9 @@ data RendererState = RendererState

newtype WrapRenderer = WrapRenderer { getWrappedText :: State RendererState Text }

-- FIXME: what the heck is going on here?
instance Show WrapRenderer where
show (WrapRenderer t) =
let initialState = RendererState (WrapConfig 80) [] 1
in show (fst $ runState t initialState)
show _ = "Do not use!"

instance Rangeable WrapRenderer where
ranged _ (WrapRenderer t) = WrapRenderer t
@@ -130,18 +139,30 @@ instance IsInline WrapRenderer where
let footnoteRef = "[" <> T.pack (show fnNumber) <> "]"
return $ content <> footnoteRef

image _ _ (WrapRenderer t) = WrapRenderer t
-- FIXME
image url _ (WrapRenderer t) = WrapRenderer $ do
altText <- t
let
imageGopherType = imagePathToGopherType (T.unpack url)
isGopherMap <- gets (gopherMap . config)
port <- gets (T.pack . show . fromMaybe 70 . listenPort . server . Library.config . library . config)
host <- gets (hostname . server . Library.config . library . config)
if isGopherMap
then return $ (T.pack [imageGopherType]) <> altText <> "\t" <> url <> "\t" <> host <> "\t" <> port
else return $ "!" <> altText <> " => " <> url

escapedChar c = WrapRenderer $ return $ T.singleton c
entity t = WrapRenderer $ return t
rawInline _ rawContent = WrapRenderer $ return rawContent

-- TODO/FIXME: add option for parsing as menu (for footnotes and images)
-- Parse and Render Markdown
wrapMarkdownParagraphs :: Int -> Text -> Either String Text
wrapMarkdownParagraphs width input =
wrapMarkdownParagraphs :: Library.Library -> Bool -> Int -> Text -> Either String Text
wrapMarkdownParagraphs library gopherMap width input =
case commonmark "source" input of
Left err -> Left $ show err
Right (WrapRenderer result) ->
let initialState = RendererState (WrapConfig width) [] 1
let initialState = RendererState (WrapConfig library gopherMap width) [] 1
(mainText, finalState) = runState result initialState
footnotesList = footnotes finalState
numberedFootnotes = zip [1 :: Int ..] footnotesList
@@ -150,10 +171,33 @@ wrapMarkdownParagraphs width input =
else "## Footnotes\n\n" <> T.intercalate "\n" (map formatFootnote numberedFootnotes)
in Right $ mainText <> formattedFootnotes
where
formatFootnote (n, (text, url)) = "[" <> T.pack (show n) <> "]: " <> text <> ": " <> url
-- FIXME: make into links if gopherMap! need way to determine type of gopherlink in the end.
formatFootnote :: (Int, (Text, Text)) -> Text
formatFootnote (n, (text, url)) = do
let
host = library.config.server.hostname
port = T.pack . show . fromMaybe 70 $ library.config.server.listenPort
if gopherMap
then
case gopherTypeByExt "text/" $ T.unpack url of
'h' -> "h" <> "[" <> T.pack (show n) <> "]: " <> text <> ": " <> url <> "\tURL:" <> url <> "\t" <> host <> "\t" <> port
otherType -> (T.pack [otherType]) <> "[" <> T.pack (show n) <> "]: " <> text <> ": " <> url <> "\t" <> url <> "\t" <> host <> "\t" <> port
else "[" <> T.pack (show n) <> "]: " <> text <> ": " <> url

{- | Do special things to Markdown for gopherspace.
I mostly made this for phlog articles.
wrapMarkdown :: Int -> Text -> Text
wrapMarkdown width input =
case wrapMarkdownParagraphs width input of
-}
wrapMarkdown
:: Library.Library
-- ^ Format the markdown document according to these Assets and config!
-> Bool
-- ^ Output a gophermap isntead of just a regular text document.
-> Int
-> Text
-> Text
wrapMarkdown library gopherMap width input =
case wrapMarkdownParagraphs library gopherMap width input of
Left err -> "Error: " <> T.pack err
Right output -> output

0 comments on commit 455f8a5

Please sign in to comment.