Skip to content

Commit 2f15b95

Browse files
committed
Group adjacent and non-adjacent options/commands
1 parent 8214451 commit 2f15b95

File tree

8 files changed

+74
-54
lines changed

8 files changed

+74
-54
lines changed

src/Options/Applicative/Help/Core.hs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@ module Options.Applicative.Help.Core (
2121

2222
import Control.Applicative
2323
import Control.Monad (guard)
24-
import Data.Function (on)
25-
import Data.List (sort, intercalate, intersperse, groupBy)
24+
import Data.List (sort, intercalate, intersperse)
2625
import Data.Foldable (any, foldl')
27-
import Data.Maybe (fromMaybe)
26+
import Data.Maybe (fromMaybe, catMaybes)
2827
#if !MIN_VERSION_base(4,8,0)
2928
import Data.Monoid (mempty)
3029
#endif
@@ -34,7 +33,7 @@ import Data.Semigroup (Semigroup (..))
3433
import Prelude hiding (any)
3534

3635
import Options.Applicative.Common
37-
import Options.Applicative.Internal (groupFst)
36+
import Options.Applicative.Internal (groupFstAll)
3837
import Options.Applicative.Types
3938
import Options.Applicative.Help.Pretty
4039
import Options.Applicative.Help.Chunk
@@ -202,7 +201,7 @@ optionsDesc global pprefs p = vsepChunks
202201
$ mapParser doc p
203202
where
204203
groupByTitle :: [Maybe (OptGroup, (Doc, Doc))] -> [[(OptGroup, (Doc, Doc))]]
205-
groupByTitle = groupFst
204+
groupByTitle = groupFstAll . catMaybes
206205

207206
tabulateGroup :: [(OptGroup, (Doc, Doc))] -> (OptGroup, Chunk Doc)
208207
tabulateGroup l@((title,_):_) = (title, tabulate (prefTabulateFill pprefs) (snd <$> l))
@@ -271,7 +270,7 @@ parserHelp pprefs p =
271270
: (group_title <$> cs)
272271
where
273272
def = "Available commands:"
274-
cs = groupBy ((==) `on` fst) $ cmdDesc pprefs p
273+
cs = groupFstAll $ cmdDesc pprefs p
275274

276275
group_title a@((n, _) : _) =
277276
with_title (fromMaybe def n) $

src/Options/Applicative/Internal.hs

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ module Options.Applicative.Internal
2828
, disamb
2929

3030
, mapParserOptions
31-
, groupFst
31+
, groupFstAll
3232
) where
3333

3434
import Control.Applicative
@@ -41,8 +41,9 @@ import Control.Monad.Trans.Reader
4141
(mapReaderT, runReader, runReaderT, Reader, ReaderT, ask)
4242
import Control.Monad.Trans.State (StateT, get, put, modify, evalStateT, runStateT)
4343
import Data.Function (on)
44-
import Data.List (groupBy)
45-
import Data.Maybe (catMaybes)
44+
import qualified Data.List as L
45+
import Data.List.NonEmpty (NonEmpty ((:|)))
46+
import qualified Data.List.NonEmpty as NE
4647

4748
import Options.Applicative.Types
4849

@@ -275,11 +276,53 @@ hoistList = foldr cons empty
275276
where
276277
cons x xs = pure x <|> xs
277278

278-
-- | Strips 'Nothing', then groups on the first element of the tuple.
279+
-- | Groups on the first element of the tuple. This differs from the simple
280+
-- @groupBy ((==) `on` fst)@ in that non-adjacent groups are __also__ grouped
281+
-- together. For example:
282+
--
283+
-- @
284+
-- groupFst = groupBy ((==) `on` fst)
285+
--
286+
-- let xs = [(1, "a"), (1, "b"), (3, "c"), (2, "d"), (3, "e"), (2, "f")]
287+
--
288+
-- groupFst xs === [[(1,"a"),(1,"b")],[(3,"c")],[(2,"d")],[(3,"e")],[(2,"f")]]
289+
-- groupFstAll xs === [[(1,"a"),(1,"b")],[(3,"c"),(3,"e")],[(2,"d"),(2,"f")]]
290+
-- @
291+
--
292+
-- Notice that the original order is preserved i.e. we do not first sort on
293+
-- the first element.
279294
--
280295
-- @since 0.19.0.0
281-
groupFst :: (Eq a) => [Maybe (a, b)] -> [[(a, b)]]
282-
groupFst = groupBy ((==) `on` fst) . catMaybes
296+
groupFstAll :: Ord a => [(a, b)] -> [[(a, b)]]
297+
groupFstAll =
298+
-- In order to group all (adjacent + non-adjacent) Eq elements together, we
299+
-- sort the list so that the Eq elements are in fact adjacent, _then_ group.
300+
-- We don't want to destroy the original order, however, so we add a
301+
-- temporary index that maintains this original order. The full logic is:
302+
--
303+
-- 1. Add index i that preserves original order.
304+
-- 2. Sort on tuple's fst.
305+
-- 3. Group by fst.
306+
-- 4. Sort by i, restoring original order.
307+
-- 5. Drop index i.
308+
fmap (NE.toList . dropIdx)
309+
. L.sortOn toIdx
310+
. NE.groupBy ((==) `on` fst')
311+
. L.sortOn fst'
312+
. addIdx
313+
where
314+
dropIdx :: NonEmpty (Int, (a, b)) -> NonEmpty (a, b)
315+
dropIdx = fmap (\(_, y) -> y)
316+
317+
toIdx :: NonEmpty (Int, (a, b)) -> Int
318+
toIdx ((x, _) :| _) = x
319+
320+
-- Like fst, ignores our added index
321+
fst' :: (Int, (a, b)) -> a
322+
fst' (_, (x, _)) = x
323+
324+
addIdx :: [(a, b)] -> [(Int, (a, b))]
325+
addIdx = zip [1 ..]
283326

284327
-- | Maps an Option modifying function over the Parser.
285328
--

src/Options/Applicative/Types.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ data OptVisibility
152152
--
153153
-- @since 0.19.0.0
154154
newtype OptGroup = OptGroup [String]
155-
deriving (Eq, Show)
155+
deriving (Eq, Ord, Show)
156156

157157
instance Semigroup OptGroup where
158158
OptGroup xs <> OptGroup ys = OptGroup (xs ++ ys)

tests/parser_group_basic.err.txt

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,14 @@ Usage: parser_group_basic --hello TARGET [--file-log-path PATH]
88

99
Available options:
1010
--hello TARGET Target for the greeting
11+
-q,--quiet Whether to be quiet
12+
-v,--verbosity ARG Console verbosity
13+
-h,--help Show this help text
1114

1215
Logging
1316
--file-log-path PATH Log file path
1417
--file-log-verbosity INT File log verbosity
1518

16-
Available options:
17-
-q,--quiet Whether to be quiet
18-
1919
System Options
2020
--poll Whether to poll
2121
--timeout INT Whether to time out
22-
23-
Available options:
24-
-v,--verbosity ARG Console verbosity
25-
-h,--help Show this help text

tests/parser_group_command_groups.err.txt

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,25 @@ Usage: parser_group_command_groups --hello TARGET [--file-log-path PATH]
99

1010
Available options:
1111
--hello TARGET Target for the greeting
12+
-q,--quiet Whether to be quiet
13+
-v,--verbosity ARG Console verbosity
14+
-h,--help Show this help text
1215

1316
Logging
1417
--file-log-path PATH Log file path
1518
--file-log-verbosity INT File log verbosity
1619

17-
Available options:
18-
-q,--quiet Whether to be quiet
19-
2020
System Options
2121
--poll Whether to poll
2222
--timeout INT Whether to time out
2323

24-
Available options:
25-
-v,--verbosity ARG Console verbosity
26-
-h,--help Show this help text
27-
2824
Available commands:
2925
list 2 Lists elements
26+
delete Deletes elements
3027

3128
Info commands
3229
list Lists elements
3330
print Prints table
3431

35-
Available commands:
36-
delete Deletes elements
37-
3832
Query commands
3933
query Runs a query

tests/parser_group_duplicate_command_groups.err.txt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,13 @@ Available options:
1212
-v,--verbosity ARG Console verbosity
1313
-h,--help Show this help text
1414

15+
Available commands:
16+
query Runs a query
17+
1518
Info commands
1619
list Lists elements
20+
print Prints table
1721

1822
Update commands
1923
delete Deletes elements
2024
insert Inserts elements
21-
22-
Available commands:
23-
query Runs a query
24-
25-
Info commands
26-
print Prints table

tests/parser_group_duplicates.err.txt

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,16 @@ Usage: parser_group_duplicates --hello TARGET [--file-log-path PATH]
99

1010
Available options:
1111
--hello TARGET Target for the greeting
12+
-q,--quiet Whether to be quiet
13+
-v,--verbosity ARG Console verbosity
14+
-h,--help Show this help text
1215

1316
Logging
1417
--file-log-path PATH Log file path
1518
--file-log-verbosity INT File log verbosity
16-
17-
Available options:
18-
-q,--quiet Whether to be quiet
19+
--log-namespace STR Log namespace
1920

2021
System
2122
--poll Whether to poll
2223
--timeout INT Whether to time out
2324
--sysFlag Some flag
24-
25-
Logging
26-
--log-namespace STR Log namespace
27-
28-
Available options:
29-
-v,--verbosity ARG Console verbosity
30-
-h,--help Show this help text

tests/parser_group_nested.err.txt

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,14 @@ Usage: parser_group_nested --hello TARGET [--file-log-path PATH] [--poll]
88

99
Available options:
1010
--hello TARGET Target for the greeting
11+
-q,--quiet Whether to be quiet
12+
-v,--verbosity ARG Console verbosity
13+
-h,--help Show this help text
1114

1215
Logging
1316
--file-log-path PATH Log file path
17+
--file-log-verbosity INT File log verbosity
1418

1519
Logging.System Options
1620
--poll Whether to poll
1721
--timeout INT Whether to time out
18-
19-
Logging
20-
--file-log-verbosity INT File log verbosity
21-
22-
Available options:
23-
-q,--quiet Whether to be quiet
24-
-v,--verbosity ARG Console verbosity
25-
-h,--help Show this help text

0 commit comments

Comments
 (0)