owickstrom / pandoc-include-code

A Pandoc filter for including code from source files
Mozilla Public License 2.0
141 stars 17 forks source link

Issue 23: adding hyperlinks #36

Closed asellak closed 4 years ago

asellak commented 4 years ago

This implements the feature requested in issue 11 by allowing hyperlinks to the original source code. It allows for both direct links and the option to specify a base url. I've listed the changes and modified the README.md with instructions and examples.

Changes:

Added containers to build-dependencies in pandoc-include-code.cabal

build-depends:   base                 >= 4        && < 5
                , unordered-containers >= 0.2      && < 0.3
                , process
                , filepath
                , text                 >= 1.2      && < 2
                , mtl                  >= 2.2      && < 3
                , pandoc-types         >= 1.21     && <= 1.21
                , containers

Added the following imports to IncludeCode.hs

import Data.Either      (partitionEithers)
import Data.Map         (delete)

Added a field for the base url attribute in InclusionSpec for when the base url is specified in the CodeBlock

data InclusionSpec = InclusionSpec
{ include :: FilePath
, mode    :: InclusionMode
, dedent  :: Maybe Int
, base :: Maybe [Char] 
}

Changed InclusionState from newtype to data in order to add the filepath specified in the include attribute of the CodeBlock

data InclusionState = InclusionState
    { startLineNumber :: Maybe LineNumber,
    link :: Maybe FilePath
    }

Modified ParseInclusion to retrieve the base url specified as an attribute in the CodeBlock (which may be Nothing) and add it to the base field in InclusionSpec

base <- getBase
.
.
.
getBase = case (HM.lookup "base" attrs) of
            Nothing -> return Nothing 
            Just b -> return $ Just (Text.unpack b)

Added a function to set the filepath in InclusionState. If a base url is specified through the meta block or command-line, append it to the link specified in the include attribute. Otherwise, set the link to the same path defined in include

setLink :: Maybe [Char] -> [Char] -> Inclusion () 
setLink (Just b) n = modify (\s -> s {link = Just (b ++ n)})
setLink Nothing n = modify (\s -> s {link = Just n})

Added the base attribute to the list of items to remove from the CodeBlock

attributeNames = ["include", "startLine", "endLine", "snippet", "dedent", "base"] 

Added function to remove the .includeLink class from the CodeBlock

modifyClasses :: [Text] -> [Text] 
modifyClasses classes = filter (\x -> x `notElem` ["includeLink"]) classes

Added function to retrieve the include and base attribute from the respective fields in InclusionSpec to then be used to create the complete link

includeLink :: Inclusion ()
includeLink =  do
    links <- asks include
    bases <- asks base
    setLink bases links 

Added includeLink to steps to be performed

allSteps :: Inclusion Text
allSteps =
    includeLink >> readIncluded >>= splitLines >>= includeByMode >>= dedentLines >>= joinLines

Added function to extract the filename from the FilePath

getName :: Text -> Text
getName filepath = last $ splitOn (Text.pack "/") filepath

Added function to create the a Block list containing either a CodeBlock or a CodeBlock and Plain text with a Link

modifiedCodeBlock :: Block -> (Text, InclusionState) -> Text -> Either InclusionError [Block]
modifiedCodeBlock cb@(CodeBlock (id', classes, attrs) _) (contents, state) base =
  case link state of
      Just path | "includeLink" `elem` classes  -> Right [(CodeBlock (id', modifyClasses classes, modifyAttributes state classes attrs) contents), Plain [Link nullAttr [Str (getName path)] ( addBase path, "")]]
      _ -> Right [CodeBlock (id', classes, modifyAttributes state classes attrs) contents]
    where
      addBase link
          | or $ map ($ link) (map Text.isPrefixOf ["C:", "/", "\\", "file:", "http:", "https:"]) = link
          | otherwise =  Text.append base link

Modified includeCode' to add thebase` url if the user specified it in the metadata block or command line

includeCode' :: Text -> Block -> IO (Either InclusionError [Block])
includeCode' base cb@(CodeBlock (id', classes, attrs) _) =  
    case parseInclusion (HM.fromList attrs) of
      Right (Just spec) ->
        runInclusion' spec allSteps >>= \case
          Left err -> return (Left err)
          Right out-> return (modifiedCodeBlock cb out base)
      Right Nothing -> return (Right [cb]) 
      Left err -> return (Left err)
includeCode' _ x = return (Right [x])

Changed includeCode to modify Pandoc instead of Block types: this allows the metadata to be used for extending links with a base url the user may specify by adding the value to the metadata block of the input file or by adding the metavalue flag in command line

includeCode :: Maybe Format -> Pandoc -> IO Pandoc
includeCode _ (Pandoc m@(Meta list) bs) = do
  b <- sequence $ map (includeCode' $ getBaseURL (lookupMeta "base" m) ) bs
  return (Pandoc modMeta (modBlocks b))
  where modMeta = Meta (Data.Map.delete "base" list)
        modBlocks b = concat $ snd $ partitionEithers b
        getBaseURL base = case base of 
                    Just (MetaInlines [Str baseURL]) -> baseURL
                    Just (MetaString baseURL) -> baseURL
                    Nothing-> ""
LaurentRDC commented 4 years ago

Awesome, thanks

owickstrom commented 3 years ago

Nice work!