summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorvapourismo <>2019-06-12 11:39:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2019-06-12 11:39:00 (GMT)
commit84083b69f67c47cd1d730b8cc0947c7b4186a044 (patch)
tree3c7a4bbfe4945403a556d586f95ac4daaed7b1a0
parentfb53e00d69033f7f5093f8f4e1ad436df6790322 (diff)
version 0.0.5HEAD0.0.5master
-rw-r--r--indent.cabal10
-rw-r--r--lib/Data/Text/Indent.hs96
-rw-r--r--src/Main.hs17
3 files changed, 101 insertions, 22 deletions
diff --git a/indent.cabal b/indent.cabal
index d0a263b..a36137e 100644
--- a/indent.cabal
+++ b/indent.cabal
@@ -2,7 +2,7 @@ cabal-version: >= 1.10
name: indent
synopsis: Fix your indentation.
description: Fix your indentation with this dead simple tool.
-version: 0.0.4
+version: 0.0.5
license: BSD3
license-file: LICENSE
author: Ole Kr├╝ger
@@ -17,7 +17,7 @@ source-repository head
library
default-language: Haskell2010
- build-depends: base == 4.*, text
+ build-depends: base == 4.*, text, containers
hs-source-dirs: lib
exposed-modules: Data.Text.Indent
@@ -33,4 +33,10 @@ executable indent
build-depends: base, indent, text, optparse-applicative
hs-source-dirs: src
main-is: Main.hs
+
ghc-options: -Wall
+
+ if impl(ghc >= 8)
+ ghc-options: -Wno-name-shadowing
+ else
+ ghc-options: -fno-warn-name-shadowing
diff --git a/lib/Data/Text/Indent.hs b/lib/Data/Text/Indent.hs
index f7e4910..97dd8ba 100644
--- a/lib/Data/Text/Indent.hs
+++ b/lib/Data/Text/Indent.hs
@@ -1,7 +1,86 @@
-module Data.Text.Indent (Options (..), defaultOptions, fixIndentation) where
+module Data.Text.Indent (Options (..), defaultOptions, guessOptions, fixIndentation) where
-import Data.Char (isSpace)
-import qualified Data.Text as Text
+import Data.Char (isSpace)
+import Data.Function (on)
+import Data.List (groupBy, sortBy)
+import qualified Data.Map.Strict as Map
+import Data.Maybe (listToMaybe, mapMaybe)
+import qualified Data.Set as Set
+import qualified Data.Text as Text
+
+----------------------------------------------------------------------------------------------------
+
+-- | Indentation options
+data Options = Options
+ { optionCharacter :: !Char -- ^ Indentation character
+ , optionMultiplier :: !Int -- ^ Indentation multiplier
+ }
+ deriving (Show, Eq)
+
+-- | Default indentation options
+defaultOptions :: Options
+defaultOptions = Options ' ' 2
+
+-- | List of possible multipliers.
+possibleMultipliers :: Set.Set Int
+possibleMultipliers = Set.fromList [1 .. 8]
+
+-- | Guess a 'Options' that match the given lines.
+guessOptions :: [Text.Text] -> Maybe Options
+guessOptions =
+ (>>= toOptions)
+ -- Keep the multipliers that were used the most and those that were used at least 66% of the time
+ -- of the maximum multiplier.
+ . fmap (fmap keep66th)
+ . listToMaybe
+ -- Sort in a way that makes the character that was used the most the head of the list.
+ . sortBy (on (flip compare) (maximum . snd))
+ -- Group multipliers by character.
+ . map gatherGrouped
+ . groupBy (on (==) fst)
+ . sortBy (on compare fst)
+ -- We only want characters that are used for indentation.
+ . filter (isSpace . fst)
+ -- Guess indentation for all lines.
+ . mapMaybe guessLineIndentation
+ where
+ gatherGrouped cs =
+ ( fst (head cs)
+ , foldr (Map.unionWith (+) . snd) (Map.fromSet (const 0) possibleMultipliers) cs
+ )
+
+ moreThan66th x y = y >= div (x * 2) 3
+
+ keep66th vs = Map.filter (moreThan66th (maximum vs)) vs
+
+ pickMultiplier (m, _ ) [] = m
+ pickMultiplier l@(m, times) (r@(m', times') : ms)
+ | m' > m && moreThan66th times times' = pickMultiplier r ms
+ | m' < m && moreThan66th times' times = pickMultiplier l ms
+ | times' > times = pickMultiplier r ms
+ | otherwise = pickMultiplier l ms
+
+ toOptions (char, multipliers)
+ | Map.null multipliers = Nothing
+ | m <- Map.findMax multipliers = Just Options
+ { optionCharacter = char
+ , optionMultiplier = pickMultiplier m (Map.toList (Map.delete (fst m) multipliers))
+ }
+
+-- | Guess the character used for indentation and account for possible multipliers.
+guessLineIndentation :: Text.Text -> Maybe (Char, Map.Map Int Int)
+guessLineIndentation line
+ | Text.null line || Text.all isSpace line = Nothing
+ | otherwise = Just (initChar, lineMultipliers)
+ where
+ initChar = Text.head line
+
+ prefixLength = Text.length (Text.takeWhile (initChar ==) line)
+
+ lineMultipliers =
+ Map.fromSet (\multiplier -> 1 - signum (mod prefixLength multiplier)) possibleMultipliers
+
+----------------------------------------------------------------------------------------------------
-- | Line details
data Line = Line
@@ -29,17 +108,6 @@ findBlock :: [Block] -> Block
findBlock [] = Block 0 0
findBlock (block : _) = block
--- | Indentation options
-data Options = Options
- { optionCharacter :: !Char -- ^ Indentation character
- , optionMultiplier :: !Int -- ^ Indentation multiplier
- }
- deriving (Show, Eq)
-
--- | Default indentation options
-defaultOptions :: Options
-defaultOptions = Options ' ' 2
-
-- | Indent lines
fixIndentation :: Options -> [Text.Text] -> [Text.Text]
fixIndentation (Options character multiplier) =
diff --git a/src/Main.hs b/src/Main.hs
index a7d3e08..f82f630 100644
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -5,17 +5,18 @@ import Control.Applicative ((<|>))
import qualified Options.Applicative as Options
import Data.Foldable (traverse_)
+import Data.Maybe (fromMaybe)
import Data.Monoid ((<>))
import qualified Data.Text as Text
-import Data.Text.Indent (Options (..), defaultOptions, fixIndentation)
+import Data.Text.Indent (Options (..), defaultOptions, fixIndentation, guessOptions)
import qualified Data.Text.IO as Text
-- | Command-line options parser
-optionsInfo :: Options.ParserInfo Options
+optionsInfo :: Options.ParserInfo (Maybe Options)
optionsInfo =
- Options.info (Options.helper <*> (spaces <|> pure defaultOptions)) mempty
+ Options.info (Options.helper <*> (spaces <|> pure Nothing)) mempty
where
- spaces = Options ' ' <$>
+ spaces = Just . Options ' ' <$>
Options.option
Options.auto
(Options.short 's' <> Options.long "spaces" <> Options.metavar "NUM")
@@ -23,6 +24,10 @@ optionsInfo =
-- | Indentation
main :: IO ()
main = do
- options <- Options.execParser optionsInfo
+ mbOptions <- Options.execParser optionsInfo
+
content <- Text.getContents
- traverse_ Text.putStrLn (fixIndentation options (Text.lines content))
+ let lines = Text.lines content
+
+ traverse_ Text.putStrLn $
+ fixIndentation (fromMaybe defaultOptions (mbOptions <|> guessOptions lines)) lines