diff --git a/app/Main.hs b/app/Main.hs index de1c1ab..05079da 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -1,6 +1,38 @@ module Main where -import Lib +import Options.Applicative +import Data.Semigroup ( (<>) ) + +data CliFlags = CliFlags { inputFile :: String + , inPlace :: Bool + , configFile :: String + } deriving (Show, Eq) + +-- | The parser for command line arguments, to be used with +-- optparse-applicative +cliParse :: Parser CliFlags +cliParse = + CliFlags + <$> strArgument (metavar "INPUT" <> help "The input filename") + <*> switch + ( long "in-place" + <> short 'i' + <> help + "Whether to modify the file in-place (this is a destructive operation" + ) + <*> strOption + (long "config" <> metavar "CONFIG" <> help "The config filename" + ) + + +printFile :: CliFlags -> IO () +printFile (CliFlags _ filename _) = print filename main :: IO () -main = someFunc +main = printFile =<< execParser opts + where + opts = + info (cliParse <**> helper) + $ fullDesc + <> progDesc "Format a markdown file" + <> header "mdfmt - a markdown formatter" diff --git a/package.yaml b/package.yaml index 8ad2bde..29aaca0 100644 --- a/package.yaml +++ b/package.yaml @@ -18,9 +18,14 @@ extra-source-files: # complications of embedding Haddock markup inside cabal files, it is # common to point users to the README.md file. description: Please see the README on GitHub at +default-extensions: OverloadedStrings dependencies: - base >= 4.7 && < 5 +- cmark +- optparse-applicative +- text +- word-wrap library: source-dirs: src @@ -46,3 +51,4 @@ tests: - -with-rtsopts=-N dependencies: - mdfmt + - QuickCheck diff --git a/src/Lib.hs b/src/Lib.hs index d36ff27..f1de174 100644 --- a/src/Lib.hs +++ b/src/Lib.hs @@ -1,6 +1,24 @@ module Lib - ( someFunc - ) where + ( wordWrap + , WordWrapError + ) +where -someFunc :: IO () -someFunc = putStrLn "someFunc" +import qualified Data.Text as T + +-- | An error representing what can go wrong when performing a word wrap +data WordWrapError a = InvalidColumnWidth Int + +-- | Word wrap some text to the desired column length +wordWrap :: T.Text -> Int -> Either (WordWrapError a) T.Text +wordWrap input width | width < 1 = Left $ InvalidColumnWidth width + | otherwise = Right $ T.intercalate "\n" wrappedLines + where + wrappedLines = map T.unwords $ gobble 0 [] $ T.words input + gobble :: Int -> [T.Text] -> [T.Text] -> [[T.Text]] + gobble _ acc [] = [reverse acc] + gobble k acc ws@(w : rest) + | 1 >= width = reverse acc : [w] : gobble 0 [] rest + | k + 1 >= width = reverse acc : gobble 0 [] ws + | otherwise = gobble (k + l + 1) (w : acc) rest + where l = T.length w diff --git a/stack.yaml.lock b/stack.yaml.lock new file mode 100644 index 0000000..ac9a8f3 --- /dev/null +++ b/stack.yaml.lock @@ -0,0 +1,12 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: [] +snapshots: +- completed: + size: 525663 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/14/14.yaml + sha256: 6edc48df46eb8bf7b861e98dd30d021a92c2e1820c9bb6528aac5d997b0e14ef + original: lts-14.14 diff --git a/test/Spec.hs b/test/Spec.hs index cd4753f..21a31a0 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -1,2 +1,31 @@ +import Test.QuickCheck +import qualified Data.Text as T +import Data.Either +import Lib +import Text.Wrap + main :: IO () -main = putStrLn "Test suite not yet implemented" +main = do + quickCheckWith stdArgs { maxSuccess = 100000 } prop_wordsAreWrapped + +printWordWrap :: Show b => Either a b -> IO () +printWordWrap (Left _ ) = print "error" +printWordWrap (Right text) = print text + +prop_wordsAreWrapped :: [Char] -> Int -> Bool +prop_wordsAreWrapped slowString width + | length wrappedLines < 1 = True + | otherwise = maxLineSize <= expectedLineLength + where + input = T.pack slowString + result = wrapText + (WrapSettings { preserveIndentation = True, breakLongWords = False }) + width + input + wrappedLines = T.lines result + maxLineSize = maximum $ map T.length wrappedLines + maxWordSize = maximum $ map T.length $ T.words input + -- if there is a word that's longer than the given width, then the + -- line will have to be as long as the longest word (at minimum) + expectedLineLength = max width (maxWordSize + 1) +