File: Util.hs

package info (click to toggle)
haskell-graphviz 2999.17.0.1-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 1,488 kB
  • sloc: haskell: 12,152; makefile: 2
file content (165 lines) | stat: -rw-r--r-- 6,002 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
{-# LANGUAGE OverloadedStrings, PatternGuards #-}
{-# OPTIONS_HADDOCK hide #-}

{- |
   Module      : Data.GraphViz.Internal.Util
   Description : Internal utility functions
   Copyright   : (c) Ivan Lazar Miljenovic
   License     : 3-Clause BSD-style
   Maintainer  : Ivan.Miljenovic@gmail.com

   This module defines internal utility functions.
-}
module Data.GraphViz.Internal.Util where

import Data.Char (isAsciiLower, isAsciiUpper, isDigit, ord)

import           Control.Monad       (liftM2)
import           Data.Function       (on)
import           Data.List           (groupBy, sortBy)
import           Data.Maybe          (isJust)
import           Data.Set            (Set)
import qualified Data.Set            as Set
import           Data.Text.Lazy      (Text)
import qualified Data.Text.Lazy      as T
import qualified Data.Text.Lazy.Read as T
import           Data.Version        (Version (..))

-- -----------------------------------------------------------------------------

isIDString :: Text -> Bool
isIDString = maybe False (\(f,os) -> frstIDString f && T.all restIDString os)
             . T.uncons

-- | First character of a non-quoted 'String' must match this.
frstIDString   :: Char -> Bool
frstIDString c = any ($c) [ isAsciiUpper
                          , isAsciiLower
                          , (==) '_'
                          , (\ x -> ord x >= 128)
                          ]

-- | The rest of a non-quoted 'String' must match this.
restIDString   :: Char -> Bool
restIDString c = frstIDString c || isDigit c

-- | Determine if this String represents a number.
isNumString     :: Text -> Bool
isNumString ""  = False
isNumString "-" = False
isNumString str = case T.uncons $ T.toLower str of
                    Just ('-',str') -> go str'
                    _               -> go str
  where
    -- Can't use Data.Text.Lazy.Read.double as it doesn't cover all
    -- possible cases
    go s = uncurry go' $ T.span isDigit s
    go' ds nds
      | T.null nds = True
      | T.null ds && nds == "." = False
      | T.null ds
      , Just ('.',nds') <- T.uncons nds
      , Just (d,nds'') <- T.uncons nds' = isDigit d && checkEs' nds''
      | Just ('.',nds') <- T.uncons nds = checkEs $ T.dropWhile isDigit nds'
      | T.null ds = False
      | otherwise = checkEs nds
    checkEs' s = case T.break ('e' ==) s of
                   ("", _) -> False
                   (ds,es) -> T.all isDigit ds && checkEs es
    checkEs str' = case T.uncons str' of
                     Nothing       -> True
                     Just ('e',ds) -> isIntString ds
                     _             -> False

{-
-- | This assumes that 'isNumString' is 'True'.
toDouble     :: Text -> Double
toDouble str = case T.uncons $ T.toLower str of
                 Just ('-', str') -> toD $ '-' `T.cons` adj str'
                 _                -> toD $ adj str
  where
    adj s = T.cons '0'
            $ case T.span ('.' ==) s of
                (ds, ".") | not $ T.null ds -> s `T.snoc` '0'
                (ds, ds') | Just ('.',es) <- T.uncons ds'
                          , Just ('e',es') <- T.uncons es
                            -> ds `T.snoc` '.' `T.snoc` '0'
                                   `T.snoc` 'e' `T.snoc` '0' `T.append` es'
                _         -> s
    toD = either (const $ error "Not a Double") fst . T.signed T.double
-}
-- | This assumes that 'isNumString' is 'True'.
toDouble     :: Text -> Double
toDouble str = case T.uncons $ T.toLower str of
                 Just ('-', str') -> toD $ '-' `T.cons` adj str'
                 _                -> toD $ adj str
  where
    adj s = T.cons '0'
            $ case T.span ('.' ==) s of
                (ds, ".") | not $ T.null ds -> s `T.snoc` '0'
                (ds, ds') | Just ('.',es) <- T.uncons ds'
                          , Just ('e',_) <- T.uncons es
                            -> ds `T.snoc` '.' `T.snoc` '0' `T.append` es
                _              -> s
    toD = read . T.unpack

isIntString :: Text -> Bool
isIntString = isJust . stringToInt

-- | Determine if this String represents an integer.
stringToInt     :: Text -> Maybe Int
stringToInt str = case T.signed T.decimal str of
                       Right (n, "") -> Just n
                       _             -> Nothing

-- | Graphviz requires double quotes to be explicitly escaped.
escapeQuotes           :: String -> String
escapeQuotes []        = []
escapeQuotes ('"':str) = '\\':'"': escapeQuotes str
escapeQuotes (c:str)   = c : escapeQuotes str

-- | Remove explicit escaping of double quotes.
descapeQuotes                :: String -> String
descapeQuotes []             = []
descapeQuotes ('\\':'"':str) = '"' : descapeQuotes str
descapeQuotes (c:str)        = c : descapeQuotes str

isKeyword :: Text -> Bool
isKeyword = (`Set.member` keywords) . T.toLower

-- | The following are Dot keywords and are not valid as labels, etc. unquoted.
keywords :: Set Text
keywords = Set.fromList [ "node"
                        , "edge"
                        , "graph"
                        , "digraph"
                        , "subgraph"
                        , "strict"
                        ]

createVersion    :: [Int] -> Version
createVersion bs = Version { versionBranch = bs, versionTags = []}

-- -----------------------------------------------------------------------------

uniq :: (Ord a) => [a] -> [a]
uniq = uniqBy id

uniqBy   :: (Ord b) => (a -> b) -> [a] -> [a]
uniqBy f = map head . groupSortBy f

groupSortBy   :: (Ord b) => (a -> b) -> [a] -> [[a]]
groupSortBy f = groupBy ((==) `on` f) . sortBy (compare `on` f)

groupSortCollectBy     :: (Ord b) => (a -> b) -> (a -> c) -> [a] -> [(b,[c])]
groupSortCollectBy f g = map (liftM2 (,) (f . head) (map g)) . groupSortBy f

-- | Fold over 'Bool's; first param is for 'False', second for 'True'.
bool       :: a -> a -> Bool -> a
bool f t b = if b
             then t
             else f

isSingle     :: [a] -> Bool
isSingle [_] = True
isSingle _   = False