Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 75389691-a67c-422a-91e9-aa58bfb5-main-patch-32205
  • cabal-pipeline
  • extended-scripts
  • guix-pipeline
  • main
  • structured-badges
  • test-pipe
7 results

Target

Select target project
No results found
Select Git revision
  • 75389691-a67c-422a-91e9-aa58bfb5-main-patch-32205
  • cabal-pipeline
  • extended-scripts
  • guix-pipeline
  • main
  • structured-badges
  • test-pipe
7 results
Show changes
Commits on Source (71)
dist-newstyle/*
.stack-work
walint.cabal
result*
# This is a custom hlint config with rules that suggest
# using Universum functions whenever it has a suitable
# alternative. You may either copy-paste this config into
# your .hlint.yaml or run hlint twice: once with your
# default config, and the second time with this one.
# This config is *not* intended for Universum developers.
############################################################################
## Universum
############################################################################
# There's no 'head' in Universum
- ignore: {name: "Use head"}
# We have 'whenJust' for this
- ignore: {name: "Use Foldable.forM_"}
- warn: {lhs: Data.Text.pack, rhs: Universum.toText}
- warn: {lhs: Data.Text.unpack, rhs: Universum.toString}
- warn: {lhs: Data.Text.Lazy.pack, rhs: Universum.toLText}
- warn: {lhs: Data.Text.Lazy.unpack, rhs: Universum.toString}
- warn: {lhs: Data.Text.Lazy.toStrict, rhs: Universum.toText}
- warn: {lhs: Data.Text.Lazy.fromStrict, rhs: Universum.toLText}
- warn: {lhs: Data.Text.pack (show x), rhs: Universum.show x}
- warn: {lhs: Data.Text.Lazy.pack (show x), rhs: Universum.show x}
- warn: {lhs: Control.Exception.evaluate, rhs: evaluateWHNF}
- warn: {lhs: Control.Exception.evaluate (force x), rhs: evaluateNF x}
- warn: {lhs: Control.Exception.evaluate (x `deepseq` ()), rhs: evaluateNF_ x}
- warn: {lhs: void (evaluateWHNF x), rhs: evaluateWHNF_ x}
- warn: {lhs: void (evaluateNF x), rhs: evaluateNF_ x}
## Containers
- hint: {lhs: Data.HashMap.Lazy.keys, rhs: Universum.keys}
- hint: {lhs: Data.HashMap.Strict.keys, rhs: Universum.keys}
- hint: {lhs: Data.Map.Lazy.keys, rhs: Universum.keys}
- hint: {lhs: Data.Map.Strict.keys, rhs: Universum.keys}
- hint: {lhs: Data.IntMap.Lazy.keys, rhs: Universum.keys}
- hint: {lhs: Data.IntMap.Strict.keys, rhs: Universum.keys}
- hint: {lhs: Data.HashMap.Lazy.elems, rhs: Universum.elems}
- hint: {lhs: Data.HashMap.Strict.elems, rhs: Universum.elems}
- hint: {lhs: Data.Map.Lazy.elems, rhs: Universum.elems}
- hint: {lhs: Data.Map.Strict.elems, rhs: Universum.elems}
- hint: {lhs: Data.IntMap.Lazy.elems, rhs: Universum.elems}
- hint: {lhs: Data.IntMap.Strict.elems, rhs: Universum.elems}
- hint: {lhs: Data.HashMap.Lazy.toList, rhs: Universum.toPairs}
- hint: {lhs: Data.HashMap.Strict.toList, rhs: Universum.toPairs}
- hint: {lhs: Data.Map.Lazy.toList, rhs: Universum.toPairs}
- hint: {lhs: Data.Map.Lazy.assocs, rhs: Universum.toPairs}
- hint: {lhs: Data.Map.Strict.toList, rhs: Universum.toPairs}
- hint: {lhs: Data.Map.Strict.assocs, rhs: Universum.toPairs}
- hint: {lhs: Data.IntMap.Lazy.toList, rhs: Universum.toPairs}
- hint: {lhs: Data.IntMap.Lazy.assocs, rhs: Universum.toPairs}
- hint: {lhs: Data.IntMap.Strict.toList, rhs: Universum.toPairs}
- hint: {lhs: Data.IntMap.Strict.assocs, rhs: Universum.toPairs}
- warn: { lhs: Data.Map.toAscList (Data.Map.fromList x)
, rhs: Universum.sortWith fst x
}
- warn: { lhs: Data.Map.toDescList (Data.Map.fromList x)
, rhs: Universum.sortWith (Down . fst) x
}
- warn: {lhs: Data.Set.toList (Data.Set.fromList l), rhs: Universum.sortNub l}
- warn: {lhs: Data.Set.assocs (Data.Set.fromList l), rhs: Universum.sortNub l}
- warn: {lhs: Data.Set.toAscList (Data.Set.fromList l), rhs: Universum.sortNub l}
- warn: {lhs: Data.HashSet.toList (Data.HashSet.fromList l), rhs: Universum.unstableNub}
- hint: { lhs: nub, rhs: Universum.ordNub
, note: "'nub' is O(n^2), 'ordNub' is O(n log n)" }
- warn: { lhs: sortBy (comparing f), rhs: Universum.sortWith f
, note: "If the function you are using for 'comparing' is slow, use 'sortOn' instead of 'sortWith', because 'sortOn' caches applications the function and 'sortWith' doesn't." }
- warn: { lhs: sortOn fst, rhs: Universum.sortWith fst
, note: "'sortWith' will be faster here because it doesn't do caching" }
- warn: { lhs: sortOn snd, rhs: Universum.sortWith snd
, note: "'sortWith' will be faster here because it doesn't do caching" }
- warn: { lhs: sortOn (Down . fst), rhs: Universum.sortWith (Down . fst)
, note: "'sortWith' will be faster here because it doesn't do caching" }
- warn: { lhs: sortOn (Down . snd), rhs: Universum.sortWith (Down . snd)
, note: "'sortWith' will be faster here because it doesn't do caching" }
- warn: {lhs: map fst &&& map snd, rhs: unzip}
- warn: {lhs: f >>= guard, rhs: guardM}
- warn: {lhs: guard =<< f, rhs: guardM}
- warn: {lhs: fmap concat (mapM f s), rhs: Universum.concatMapM f s}
- warn: {lhs: concat <$> mapM f s, rhs: Universum.concatMapM f s}
- warn: {lhs: fmap concat (forM f s), rhs: Universum.concatForM s f}
- warn: {lhs: fmap concat (for f s), rhs: Universum.concatForM s f}
- warn: {lhs: concat <$> forM f s, rhs: Universum.concatForM s f}
- warn: {lhs: concat <$> for f s, rhs: Universum.concatForM s f}
- hint: { lhs: fmap and (sequence s), rhs: Universum.andM s
, note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." }
- hint: { lhs: and <$> sequence s, rhs: Universum.andM s
, note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." }
- hint: { lhs: fmap or (sequence s), rhs: Universum.orM s
, note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." }
- hint: { lhs: or <$> sequence s, rhs: Universum.orM s
, note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." }
- hint: { lhs: fmap and (mapM f s), rhs: Universum.allM f s
, note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." }
- hint: { lhs: and <$> mapM f s, rhs: Universum.allM f s
, note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." }
- hint: { lhs: fmap or (mapM f s), rhs: Universum.anyM f s
, note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." }
- hint: { lhs: or <$> mapM f s, rhs: Universum.anyM f s
, note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." }
- warn: {lhs: whenM (not <$> x), rhs: unlessM x}
- warn: {lhs: unlessM (not <$> x), rhs: whenM x}
- warn: {lhs: either (const True) (const False), rhs: isLeft}
- warn: {lhs: either (const False) (const True), rhs: isRight}
- warn: {lhs: either id (const a), rhs: fromLeft a}
- warn: {lhs: either (const b) id, rhs: fromRight b}
- warn: {lhs: either Just (const Nothing), rhs: leftToMaybe}
- warn: {lhs: either (const Nothing) Just, rhs: rightToMaybe}
- warn: {lhs: maybe (Left l) Right, rhs: maybeToRight}
- warn: {lhs: maybe (Right r) Left, rhs: maybeToLeft}
- warn: {lhs: fromMaybe mempty, rhs: maybeToMonoid}
- warn: {lhs: "m ?: mempty", rhs: maybeToMonoid m}
# Probably will be reduced when function equality is done:
# https://github.com/ndmitchell/hlint/issues/434
- warn: {lhs: (case m of Just x -> f x; Nothing -> pure () ), rhs: Universum.whenJust m f}
- warn: {lhs: (case m of Just x -> f x; Nothing -> return ()), rhs: Universum.whenJust m f}
- warn: {lhs: (case m of Just x -> f x; Nothing -> pass ), rhs: Universum.whenJust m f}
- warn: {lhs: (case m of Nothing -> pure () ; Just x -> f x), rhs: Universum.whenJust m f}
- warn: {lhs: (case m of Nothing -> return (); Just x -> f x), rhs: Universum.whenJust m f}
- warn: {lhs: (case m of Nothing -> pass ; Just x -> f x), rhs: Universum.whenJust m f}
- warn: {lhs: (maybe (pure ()) f m), rhs: Universum.whenJust m f}
- warn: {lhs: (maybe (return ()) f m), rhs: Universum.whenJust m f}
- warn: {lhs: (maybe pass f m), rhs: Universum.whenJust m f}
- warn: {lhs: (m >>= \case Just x -> f x; Nothing -> pure () ), rhs: Universum.whenJustM m f}
- warn: {lhs: (m >>= \case Just x -> f x; Nothing -> return ()), rhs: Universum.whenJustM m f}
- warn: {lhs: (m >>= \case Just x -> f x; Nothing -> pass ), rhs: Universum.whenJustM m f}
- warn: {lhs: (m >>= \case Nothing -> pure () ; Just x -> f x), rhs: Universum.whenJustM m f}
- warn: {lhs: (m >>= \case Nothing -> return (); Just x -> f x), rhs: Universum.whenJustM m f}
- warn: {lhs: (m >>= \case Nothing -> pass ; Just x -> f x), rhs: Universum.whenJustM m f}
- warn: {lhs: (maybe (pure ()) f =<< m), rhs: Universum.whenJustM m f}
- warn: {lhs: (maybe (return ()) f =<< m), rhs: Universum.whenJustM m f}
- warn: {lhs: (maybe pass f =<< m), rhs: Universum.whenJustM m f}
- warn: {lhs: (m >>= maybe (pure ()) f), rhs: Universum.whenJustM m f}
- warn: {lhs: (m >>= maybe (return ()) f), rhs: Universum.whenJustM m f}
- warn: {lhs: (m >>= maybe pass f), rhs: Universum.whenJustM m f}
- warn: {lhs: (case m of Just _ -> pure () ; Nothing -> x), rhs: Universum.whenNothing_ m x}
- warn: {lhs: (case m of Just _ -> return (); Nothing -> x), rhs: Universum.whenNothing_ m x}
- warn: {lhs: (case m of Just _ -> pass ; Nothing -> x), rhs: Universum.whenNothing_ m x}
- warn: {lhs: (case m of Nothing -> x; Just _ -> pure () ), rhs: Universum.whenNothing_ m x}
- warn: {lhs: (case m of Nothing -> x; Just _ -> return ()), rhs: Universum.whenNothing_ m x}
- warn: {lhs: (case m of Nothing -> x; Just _ -> pass ), rhs: Universum.whenNothing_ m x}
- warn: {lhs: (maybe x (\_ -> pure () ) m), rhs: Universum.whenNothing_ m x}
- warn: {lhs: (maybe x (\_ -> return () ) m), rhs: Universum.whenNothing_ m x}
- warn: {lhs: (maybe x (\_ -> pass ) m), rhs: Universum.whenNothing_ m x}
- warn: {lhs: (maybe x (const (pure () )) m), rhs: Universum.whenNothing_ m x}
- warn: {lhs: (maybe x (const (return ())) m), rhs: Universum.whenNothing_ m x}
- warn: {lhs: (maybe x (const (pass )) m), rhs: Universum.whenNothing_ m x}
- warn: {lhs: (m >>= \case Just _ -> pure () ; Nothing -> x), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (m >>= \case Just _ -> return (); Nothing -> x), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (m >>= \case Just _ -> pass ; Nothing -> x), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (m >>= \case Nothing -> x; Just _ -> pure () ), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (m >>= \case Nothing -> x; Just _ -> return ()), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (m >>= \case Nothing -> x; Just _ -> pass ), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (maybe x (\_ -> pure () ) =<< m), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (maybe x (\_ -> return () ) =<< m), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (maybe x (\_ -> pass ) =<< m), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (maybe x (const (pure () )) =<< m), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (maybe x (const (return ())) =<< m), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (maybe x (const (pass )) =<< m), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (m >>= maybe x (\_ -> pure ()) ), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (m >>= maybe x (\_ -> return ()) ), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (m >>= maybe x (\_ -> pass) ), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (m >>= maybe x (const (pure ()) )), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (m >>= maybe x (const (return ()))), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (m >>= maybe x (const (pass) )), rhs: Universum.whenNothingM_ m x}
- warn: {lhs: (case m of Left x -> f x; Right _ -> pure () ), rhs: Universum.whenLeft m f}
- warn: {lhs: (case m of Left x -> f x; Right _ -> return ()), rhs: Universum.whenLeft m f}
- warn: {lhs: (case m of Left x -> f x; Right _ -> pass ), rhs: Universum.whenLeft m f}
- warn: {lhs: (case m of Right _ -> pure () ; Left x -> f x), rhs: Universum.whenLeft m f}
- warn: {lhs: (case m of Right _ -> return (); Left x -> f x), rhs: Universum.whenLeft m f}
- warn: {lhs: (case m of Right _ -> pass ; Left x -> f x), rhs: Universum.whenLeft m f}
- warn: {lhs: (either f (\_ -> pure () ) m), rhs: Universum.whenLeft m f}
- warn: {lhs: (either f (\_ -> return () ) m), rhs: Universum.whenLeft m f}
- warn: {lhs: (either f (\_ -> pass ) m), rhs: Universum.whenLeft m f}
- warn: {lhs: (either f (const (pure () )) m), rhs: Universum.whenLeft m f}
- warn: {lhs: (either f (const (return ())) m), rhs: Universum.whenLeft m f}
- warn: {lhs: (either f (const (pass )) m), rhs: Universum.whenLeft m f}
- warn: {lhs: (m >>= \case Left x -> f x; Right _ -> pure () ), rhs: Universum.whenLeftM m f}
- warn: {lhs: (m >>= \case Left x -> f x; Right _ -> return ()), rhs: Universum.whenLeftM m f}
- warn: {lhs: (m >>= \case Left x -> f x; Right _ -> pass ), rhs: Universum.whenLeftM m f}
- warn: {lhs: (m >>= \case Right _ -> pure () ; Left x -> f x), rhs: Universum.whenLeftM m f}
- warn: {lhs: (m >>= \case Right _ -> return (); Left x -> f x), rhs: Universum.whenLeftM m f}
- warn: {lhs: (m >>= \case Right _ -> pass ; Left x -> f x), rhs: Universum.whenLeftM m f}
- warn: {lhs: (either f (\_ -> pure () ) =<< m), rhs: Universum.whenLeftM m f}
- warn: {lhs: (either f (\_ -> return () ) =<< m), rhs: Universum.whenLeftM m f}
- warn: {lhs: (either f (\_ -> pass ) =<< m), rhs: Universum.whenLeftM m f}
- warn: {lhs: (either f (const (pure () )) =<< m), rhs: Universum.whenLeftM m f}
- warn: {lhs: (either f (const (return ())) =<< m), rhs: Universum.whenLeftM m f}
- warn: {lhs: (either f (const (pass )) =<< m), rhs: Universum.whenLeftM m f}
- warn: {lhs: (m >>= either f (\_ -> pure ()) ), rhs: Universum.whenLeftM m f}
- warn: {lhs: (m >>= either f (\_ -> return ()) ), rhs: Universum.whenLeftM m f}
- warn: {lhs: (m >>= either f (\_ -> pass) ), rhs: Universum.whenLeftM m f}
- warn: {lhs: (m >>= either f (const (pure ()) )), rhs: Universum.whenLeftM m f}
- warn: {lhs: (m >>= either f (const (return ()))), rhs: Universum.whenLeftM m f}
- warn: {lhs: (m >>= either f (const (pass) )), rhs: Universum.whenLeftM m f}
- warn: {lhs: (case m of Right x -> f x; Left _ -> pure () ), rhs: Universum.whenRight m f}
- warn: {lhs: (case m of Right x -> f x; Left _ -> return ()), rhs: Universum.whenRight m f}
- warn: {lhs: (case m of Right x -> f x; Left _ -> pass ), rhs: Universum.whenRight m f}
- warn: {lhs: (case m of Left _ -> pure () ; Right x -> f x), rhs: Universum.whenRight m f}
- warn: {lhs: (case m of Left _ -> return (); Right x -> f x), rhs: Universum.whenRight m f}
- warn: {lhs: (case m of Left _ -> pass ; Right x -> f x), rhs: Universum.whenRight m f}
- warn: {lhs: (either (\_ -> pure () ) f m), rhs: Universum.whenRight m f}
- warn: {lhs: (either (\_ -> return () ) f m), rhs: Universum.whenRight m f}
- warn: {lhs: (either (\_ -> pass ) f m), rhs: Universum.whenRight m f}
- warn: {lhs: (either (const (pure () )) f m), rhs: Universum.whenRight m f}
- warn: {lhs: (either (const (return ())) f m), rhs: Universum.whenRight m f}
- warn: {lhs: (either (const (pass )) f m), rhs: Universum.whenRight m f}
- warn: {lhs: (m >>= \case Right x -> f x; Left _ -> pure () ), rhs: Universum.whenRightM m f}
- warn: {lhs: (m >>= \case Right x -> f x; Left _ -> return ()), rhs: Universum.whenRightM m f}
- warn: {lhs: (m >>= \case Right x -> f x; Left _ -> pass ), rhs: Universum.whenRightM m f}
- warn: {lhs: (m >>= \case Left _ -> pure () ; Right x -> f x), rhs: Universum.whenRightM m f}
- warn: {lhs: (m >>= \case Left _ -> return (); Right x -> f x), rhs: Universum.whenRightM m f}
- warn: {lhs: (m >>= \case Left _ -> pass ; Right x -> f x), rhs: Universum.whenRightM m f}
- warn: {lhs: (either (\_ -> pure () ) f =<< m), rhs: Universum.whenRightM m f}
- warn: {lhs: (either (\_ -> return () ) f =<< m), rhs: Universum.whenRightM m f}
- warn: {lhs: (either (\_ -> pass ) f =<< m), rhs: Universum.whenRightM m f}
- warn: {lhs: (either (const (pure () )) f =<< m), rhs: Universum.whenRightM m f}
- warn: {lhs: (either (const (return ())) f =<< m), rhs: Universum.whenRightM m f}
- warn: {lhs: (either (const (pass )) f =<< m), rhs: Universum.whenRightM m f}
- warn: {lhs: (m >>= either (\_ -> pure ()) f), rhs: Universum.whenRightM m f}
- warn: {lhs: (m >>= either (\_ -> return ()) f), rhs: Universum.whenRightM m f}
- warn: {lhs: (m >>= either (\_ -> pass) f), rhs: Universum.whenRightM m f}
- warn: {lhs: (m >>= either (const (pure ()) ) f), rhs: Universum.whenRightM m f}
- warn: {lhs: (m >>= either (const (return ())) f), rhs: Universum.whenRightM m f}
- warn: {lhs: (m >>= either (const (pass) ) f), rhs: Universum.whenRightM m f}
- warn: {lhs: "(case m of [] -> return (); (x:xs) -> f (x :| xs))", rhs: Universum.whenNotNull m f}
- warn: {lhs: "(case m of [] -> pure () ; (x:xs) -> f (x :| xs))", rhs: Universum.whenNotNull m f}
- warn: {lhs: "(case m of [] -> pass ; (x:xs) -> f (x :| xs))", rhs: Universum.whenNotNull m f}
- warn: {lhs: "(case m of (x:xs) -> f (x :| xs); [] -> return ())", rhs: Universum.whenNotNull m f}
- warn: {lhs: "(case m of (x:xs) -> f (x :| xs); [] -> pure () )", rhs: Universum.whenNotNull m f}
- warn: {lhs: "(case m of (x:xs) -> f (x :| xs); [] -> pass )", rhs: Universum.whenNotNull m f}
- warn: {lhs: "(m >>= \\case [] -> pass ; (x:xs) -> f (x :| xs))", rhs: Universum.whenNotNullM m f}
- warn: {lhs: "(m >>= \\case [] -> pure () ; (x:xs) -> f (x :| xs))", rhs: Universum.whenNotNullM m f}
- warn: {lhs: "(m >>= \\case [] -> return (); (x:xs) -> f (x :| xs))", rhs: Universum.whenNotNullM m f}
- warn: {lhs: "(m >>= \\case (x:xs) -> f (x :| xs); [] -> pass )", rhs: Universum.whenNotNullM m f}
- warn: {lhs: "(m >>= \\case (x:xs) -> f (x :| xs); [] -> pure () )", rhs: Universum.whenNotNullM m f}
- warn: {lhs: "(m >>= \\case (x:xs) -> f (x :| xs); [] -> return ())", rhs: Universum.whenNotNullM m f}
- warn: {lhs: mapMaybe leftToMaybe, rhs: lefts}
- warn: {lhs: mapMaybe rightToMaybe, rhs: rights}
############################################################################
## Reexports
############################################################################
## Applicative
- warn: { name: "Use 'Alternative' from Universum"
, lhs: Control.Applicative.Alternative, rhs: Universum.Alternative }
- warn: { name: "Use 'empty' from Universum"
, lhs: Control.Applicative.empty, rhs: Universum.empty }
- warn: { name: "Use '(<|>)' from Universum"
, lhs: Control.Applicative.(<|>), rhs: Universum.(<|>) }
- warn: { name: "Use 'some' from Universum"
, lhs: Control.Applicative.some, rhs: Universum.some }
- warn: { name: "Use 'many' from Universum"
, lhs: Control.Applicative.many, rhs: Universum.many }
- warn: { name: "Use 'Const' from Universum"
, lhs: Control.Applicative.Const, rhs: Universum.Const }
- warn: { name: "Use 'getConst' from Universum"
, lhs: Control.Applicative.getConst, rhs: Universum.getConst }
- warn: { name: "Use 'ZipList' from Universum"
, lhs: Control.Applicative.ZipList, rhs: Universum.ZipList }
- warn: { name: "Use 'getZipList' from Universum"
, lhs: Control.Applicative.getZipList, rhs: Universum.getZipList }
- warn: { name: "Use 'liftA2' from Universum"
, lhs: Control.Applicative.liftA2, rhs: Universum.liftA2 }
- warn: { name: "Use 'liftA3' from Universum"
, lhs: Control.Applicative.liftA3, rhs: Universum.liftA3 }
- warn: { name: "Use 'optional' from Universum"
, lhs: Control.Applicative.optional, rhs: Universum.optional }
- warn: { name: "Use '(<**>)' from Universum"
, lhs: Control.Applicative.(<**>), rhs: Universum.(<**>) }
## Base
- warn: { name: "Use 'xor' from Universum"
, lhs: Data.Bits.xor, rhs: Universum.xor }
- warn: { name: "Use 'chr' from Universum"
, lhs: Data.Char.chr, rhs: Universum.chr }
- warn: { name: "Use 'Int16' from Universum"
, lhs: Data.Int.Int16, rhs: Universum.Int16 }
- warn: { name: "Use 'Int32' from Universum"
, lhs: Data.Int.Int32, rhs: Universum.Int32 }
- warn: { name: "Use 'Int64' from Universum"
, lhs: Data.Int.Int64, rhs: Universum.Int64 }
- warn: { name: "Use 'Int8' from Universum"
, lhs: Data.Int.Int8, rhs: Universum.Int8 }
- warn: { name: "Use 'Word16' from Universum"
, lhs: Data.Word.Word16, rhs: Universum.Word16 }
- warn: { name: "Use 'Word32' from Universum"
, lhs: Data.Word.Word32, rhs: Universum.Word32 }
- warn: { name: "Use 'Word64' from Universum"
, lhs: Data.Word.Word64, rhs: Universum.Word64 }
- warn: { name: "Use 'Word8' from Universum"
, lhs: Data.Word.Word8, rhs: Universum.Word8 }
- warn: { name: "Use 'byteSwap16' from Universum"
, lhs: Data.Word.byteSwap16, rhs: Universum.byteSwap16 }
- warn: { name: "Use 'byteSwap32' from Universum"
, lhs: Data.Word.byteSwap32, rhs: Universum.byteSwap32 }
- warn: { name: "Use 'byteSwap64' from Universum"
, lhs: Data.Word.byteSwap64, rhs: Universum.byteSwap64 }
- warn: { name: "Use 'Natural' from Universum"
, lhs: Numeric.Natural.Natural, rhs: Universum.Natural }
- warn: { name: "Use 'Handle' from Universum"
, lhs: System.IO.Handle, rhs: Universum.Handle }
- warn: { name: "Use 'IOMode' from Universum"
, lhs: System.IO.IOMode, rhs: Universum.IOMode }
- warn: { name: "Use 'ReadMode' from Universum"
, lhs: System.IO.ReadMode, rhs: Universum.ReadMode }
- warn: { name: "Use 'WriteMode' from Universum"
, lhs: System.IO.WriteMode, rhs: Universum.WriteMode }
- warn: { name: "Use 'AppendMode' from Universum"
, lhs: System.IO.AppendMode, rhs: Universum.AppendMode }
- warn: { name: "Use 'ReadWriteMode' from Universum"
, lhs: System.IO.ReadWriteMode, rhs: Universum.ReadWriteMode }
- warn: { name: "Use 'stderr' from Universum"
, lhs: System.IO.stderr, rhs: Universum.stderr }
- warn: { name: "Use 'stdin' from Universum"
, lhs: System.IO.stdin, rhs: Universum.stdin }
- warn: { name: "Use 'stdout' from Universum"
, lhs: System.IO.stdout, rhs: Universum.stdout }
- warn: { name: "Use 'withFile' from Universum"
, lhs: System.IO.withFile, rhs: Universum.withFile }
- warn: { name: "Use 'foldlM' from Universum"
, lhs: Data.Foldable.foldlM, rhs: Universum.foldlM }
- warn: { name: "Use 'foldrM' from Universum"
, lhs: Data.Foldable.foldrM, rhs: Universum.foldrM }
- warn: { name: "Use 'maximumBy' from Universum"
, lhs: Data.Foldable.maximumBy, rhs: Universum.maximumBy }
- warn: { name: "Use 'minimumBy' from Universum"
, lhs: Data.Foldable.minimumBy, rhs: Universum.minimumBy }
- warn: { name: "Use 'Down' from Universum"
, lhs: Data.Ord.Down, rhs: Universum.Down }
- warn: { name: "Use 'comparing' from Universum"
, lhs: Data.Ord.comparing, rhs: Universum.comparing }
- warn: { name: "Use 'fmapDefault' from Universum"
, lhs: Data.Traversable.fmapDefault, rhs: Universum.fmapDefault }
- warn: { name: "Use 'foldMapDefault' from Universum"
, lhs: Data.Traversable.foldMapDefault, rhs: Universum.foldMapDefault }
- warn: { name: "Use 'forM' from Universum"
, lhs: Data.Traversable.forM, rhs: Universum.forM }
- warn: { name: "Use 'mapAccumL' from Universum"
, lhs: Data.Traversable.mapAccumL, rhs: Universum.mapAccumL }
- warn: { name: "Use 'mapAccumR' from Universum"
, lhs: Data.Traversable.mapAccumR, rhs: Universum.mapAccumR }
- warn: { name: "Use 'Proxy' from Universum"
, lhs: Data.Proxy.Proxy, rhs: Universum.Proxy }
- warn: { name: "Use 'Typeable' from Universum"
, lhs: Data.Typeable.Typeable, rhs: Universum.Typeable }
- warn: { name: "Use 'Void' from Universum"
, lhs: Data.Void.Void, rhs: Universum.Void }
- warn: { name: "Use 'absurd' from Universum"
, lhs: Data.Void.absurd, rhs: Universum.absurd }
- warn: { name: "Use 'vacuous' from Universum"
, lhs: Data.Void.vacuous, rhs: Universum.vacuous }
- warn: { name: "Use 'maxInt' from Universum"
, lhs: Data.Base.maxInt, rhs: Universum.maxInt }
- warn: { name: "Use 'minInt' from Universum"
, lhs: Data.Base.minInt, rhs: Universum.minInt }
- warn: { name: "Use 'ord' from Universum"
, lhs: Data.Base.ord, rhs: Universum.ord }
- warn: { name: "Use 'boundedEnumFrom' from Universum"
, lhs: GHC.Enum.boundedEnumFrom, rhs: Universum.boundedEnumFrom }
- warn: { name: "Use 'boundedEnumFromThen' from Universum"
, lhs: GHC.Enum.boundedEnumFromThen, rhs: Universum.boundedEnumFromThen }
- warn: { name: "Use 'Constraint' from Universum"
, lhs: GHC.Exts.Constraint, rhs: Universum.Constraint }
- warn: { name: "Use 'FunPtr' from Universum"
, lhs: GHC.Exts.FunPtr, rhs: Universum.FunPtr }
- warn: { name: "Use 'Ptr' from Universum"
, lhs: GHC.Exts.Ptr, rhs: Universum.Ptr }
- warn: { name: "Use 'Generic' from Universum"
, lhs: GHC.Generics.Generic, rhs: Universum.Generic }
- warn: { name: "Use 'Ratio' from Universum"
, lhs: GHC.Real.Ratio, rhs: Universum.Ratio }
- warn: { name: "Use 'Rational' from Universum"
, lhs: GHC.Real.Rational, rhs: Universum.Rational }
- warn: { name: "Use 'CmpNat' from Universum"
, lhs: GHC.TypeNats.CmpNat, rhs: Universum.CmpNat }
- warn: { name: "Use 'KnownNat' from Universum"
, lhs: GHC.TypeNats.KnownNat, rhs: Universum.KnownNat }
- warn: { name: "Use 'Nat' from Universum"
, lhs: GHC.TypeNats.Nat, rhs: Universum.Nat }
- warn: { name: "Use 'SomeNat' from Universum"
, lhs: GHC.TypeNats.SomeNat, rhs: Universum.SomeNat }
- warn: { name: "Use 'natVal' from Universum"
, lhs: GHC.TypeNats.natVal, rhs: Universum.natVal }
- warn: { name: "Use 'someNatVal' from Universum"
, lhs: GHC.TypeNats.someNatVal, rhs: Universum.someNatVal }
- warn: { name: "Use 'CmpNat' from Universum"
, lhs: GHC.TypeLits.CmpNat, rhs: Universum.CmpNat }
- warn: { name: "Use 'KnownNat' from Universum"
, lhs: GHC.TypeLits.KnownNat, rhs: Universum.KnownNat }
- warn: { name: "Use 'Nat' from Universum"
, lhs: GHC.TypeLits.Nat, rhs: Universum.Nat }
- warn: { name: "Use 'SomeNat' from Universum"
, lhs: GHC.TypeLits.SomeNat, rhs: Universum.SomeNat }
- warn: { name: "Use 'natVal' from Universum"
, lhs: GHC.TypeLits.natVal, rhs: Universum.natVal }
- warn: { name: "Use 'someNatVal' from Universum"
, lhs: GHC.TypeLits.someNatVal, rhs: Universum.someNatVal }
- warn: { name: "Use 'Coercible' from Universum"
, lhs: GHC.Types.Coercible, rhs: Universum.Coercible }
- warn: { name: "Use 'getStackTrace' from Universum"
, lhs: GHC.ExecutionStack.getStackTrace, rhs: Universum.getStackTrace }
- warn: { name: "Use 'showStackTrace' from Universum"
, lhs: GHC.ExecutionStack.showStackTrace, rhs: Universum.showStackTrace }
- warn: { name: "Use 'IsLabel' from Universum"
, lhs: GHC.OverloadedLabels.IsLabel, rhs: Universum.IsLabel }
- warn: { name: "Use 'fromLabel' from Universum"
, lhs: GHC.OverloadedLabels.fromLabel, rhs: Universum.fromLabel }
- warn: { name: "Use 'CallStack' from Universum"
, lhs: GHC.Stack.CallStack, rhs: Universum.CallStack }
- warn: { name: "Use 'HasCallStack' from Universum"
, lhs: GHC.Stack.HasCallStack, rhs: Universum.HasCallStack }
- warn: { name: "Use 'callStack' from Universum"
, lhs: GHC.Stack.callStack, rhs: Universum.callStack }
- warn: { name: "Use 'currentCallStack' from Universum"
, lhs: GHC.Stack.currentCallStack, rhs: Universum.currentCallStack }
- warn: { name: "Use 'getCallStack' from Universum"
, lhs: GHC.Stack.getCallStack, rhs: Universum.getCallStack }
- warn: { name: "Use 'prettyCallStack' from Universum"
, lhs: GHC.Stack.prettyCallStack, rhs: Universum.prettyCallStack }
- warn: { name: "Use 'prettySrcLoc' from Universum"
, lhs: GHC.Stack.prettySrcLoc, rhs: Universum.prettySrcLoc }
- warn: { name: "Use 'withFrozenCallStack' from Universum"
, lhs: GHC.Stack.withFrozenCallStack, rhs: Universum.withFrozenCallStack }
- warn: { name: "Use 'Type' from Universum"
, lhs: Data.Kind.Type, rhs: Universum.Type }
## Bool
- warn: { name: "Use 'guard' from Universum"
, lhs: Control.Monad.guard, rhs: Universum.guard }
- warn: { name: "Use 'unless' from Universum"
, lhs: Control.Monad.unless, rhs: Universum.unless }
- warn: { name: "Use 'when' from Universum"
, lhs: Control.Monad.when, rhs: Universum.when }
- warn: { name: "Use 'bool' from Universum"
, lhs: Data.Bool.bool, rhs: Universum.bool }
## Container
- warn: { name: "Use 'Hashable' from Universum"
, lhs: Data.Hashable.Hashable, rhs: Universum.Hashable }
- warn: { name: "Use 'hashWithSalt' from Universum"
, lhs: Data.Hashable.hashWithSalt, rhs: Universum.hashWithSalt }
- warn: { name: "Use 'HashMap' from Universum"
, lhs: Data.HashMap.Strict.HashMap, rhs: Universum.HashMap }
- warn: { name: "Use 'HashSet' from Universum"
, lhs: Data.HashSet.HashSet, rhs: Universum.HashSet }
- warn: { name: "Use 'IntMap' from Universum"
, lhs: Data.IntMap.Strict.IntMap, rhs: Universum.IntMap }
- warn: { name: "Use 'IntSet' from Universum"
, lhs: Data.IntSet.IntSet, rhs: Universum.IntSet }
- warn: { name: "Use 'Map' from Universum"
, lhs: Data.Map.Strict.Map, rhs: Universum.Map }
- warn: { name: "Use 'Sequence' from Universum"
, lhs: Data.Sequence.Sequence, rhs: Universum.Sequence }
- warn: { name: "Use 'Set' from Universum"
, lhs: Data.Set.Set, rhs: Universum.Set }
- warn: { name: "Use 'swap' from Universum"
, lhs: Data.Tuple.swap, rhs: Universum.swap }
- warn: { name: "Use 'Vector' from Universum"
, lhs: Data.Vector.Vector, rhs: Universum.Vector }
## Deepseq
- warn: { name: "Use 'NFData' from Universum"
, lhs: Control.DeepSeq.NFData, rhs: Universum.NFData }
- warn: { name: "Use 'rnf' from Universum"
, lhs: Control.DeepSeq.rnf, rhs: Universum.rnf }
- warn: { name: "Use 'deepseq' from Universum"
, lhs: Control.DeepSeq.deepseq, rhs: Universum.deepseq }
- warn: { name: "Use 'force' from Universum"
, lhs: Control.DeepSeq.force, rhs: Universum.force }
- warn: { name: "Use '($!!)' from Universum"
, lhs: "Control.DeepSeq.($!!)", rhs: "Universum.($!!)" }
## Exception
- warn: { name: "Use 'Exception' from Universum"
, lhs: Control.Exception.Exception, rhs: Universum.Exception }
- warn: { name: "Use 'toException' from Universum"
, lhs: Control.Exception.toException, rhs: Universum.toException }
- warn: { name: "Use 'fromException' from Universum"
, lhs: Control.Exception.fromException, rhs: Universum.fromException }
- warn: { name: "Use 'Exception' from Universum"
, lhs: Control.Exception.Safe.Exception, rhs: Universum.Exception }
- warn: { name: "Use 'toException' from Universum"
, lhs: Control.Exception.Safe.toException, rhs: Universum.toException }
- warn: { name: "Use 'fromException' from Universum"
, lhs: Control.Exception.Safe.fromException, rhs: Universum.fromException }
- warn: { name: "Use 'displayException' from Universum"
, lhs: Control.Exception.Safe.displayException, rhs: Universum.displayException }
- warn: { name: "Use 'MonadCatch' from Universum"
, lhs: Control.Exception.Safe.MonadCatch, rhs: Universum.MonadCatch }
- warn: { name: "Use 'MonadMask' from Universum"
, lhs: Control.Exception.Safe.MonadMask, rhs: Universum.MonadMask }
- warn: { name: "Use 'mask' from Universum"
, lhs: Control.Exception.Safe.mask, rhs: Universum.mask }
- warn: { name: "Use 'uninterruptibleMask' from Universum"
, lhs: Control.Exception.Safe.uninterruptibleMask, rhs: Universum.uninterruptibleMask }
- warn: { name: "Use 'MonadThrow' from Universum"
, lhs: Control.Exception.Safe.MonadThrow, rhs: Universum.MonadThrow }
- warn: { name: "Use 'SomeException' from Universum"
, lhs: Control.Exception.Safe.SomeException, rhs: Universum.SomeException }
- warn: { name: "Use 'bracket' from Universum"
, lhs: Control.Exception.Safe.bracket, rhs: Universum.bracket }
- warn: { name: "Use 'bracketOnError' from Universum"
, lhs: Control.Exception.Safe.bracketOnError, rhs: Universum.bracketOnError }
- warn: { name: "Use 'bracket_' from Universum"
, lhs: Control.Exception.Safe.bracket_, rhs: Universum.bracket_ }
- warn: { name: "Use 'catch' from Universum"
, lhs: Control.Exception.Safe.catch, rhs: Universum.catch }
- warn: { name: "Use 'catchAny' from Universum"
, lhs: Control.Exception.Safe.catchAny, rhs: Universum.catchAny }
- warn: { name: "Use 'finally' from Universum"
, lhs: Control.Exception.Safe.finally, rhs: Universum.finally }
- warn: { name: "Use 'handleAny' from Universum"
, lhs: Control.Exception.Safe.handleAny, rhs: Universum.handleAny }
- warn: { name: "Use 'onException' from Universum"
, lhs: Control.Exception.Safe.onException, rhs: Universum.onException }
- warn: { name: "Use 'throwM' from Universum"
, lhs: Control.Exception.Safe.throwM, rhs: Universum.throwM }
- warn: { name: "Use 'try' from Universum"
, lhs: Control.Exception.Safe.try, rhs: Universum.try }
- warn: { name: "Use 'tryAny' from Universum"
, lhs: Control.Exception.Safe.tryAny, rhs: Universum.tryAny }
## Function
- warn: { name: "Use 'fix' from Universum"
, lhs: Data.Function.fix, rhs: Universum.fix }
- warn: { name: "Use 'on' from Universum"
, lhs: Data.Function.on, rhs: Universum.on }
## Functor
- warn: { name: "Use '(&&&)' from Universum"
, lhs: Control.Arrow.(&&&), rhs: Universum.(&&&) }
- warn: { name: "Use 'Bifunctor' from Universum"
, lhs: Data.Bifunctor.Bifunctor, rhs: Universum.Bifunctor }
- warn: { name: "Use 'bimap' from Universum"
, lhs: Data.Bifunctor.bimap, rhs: Universum.bimap }
- warn: { name: "Use 'first' from Universum"
, lhs: Data.Bifunctor.first, rhs: Universum.first }
- warn: { name: "Use 'second' from Universum"
, lhs: Data.Bifunctor.second, rhs: Universum.second }
- warn: { name: "Use 'void' from Universum"
, lhs: Data.Functor.void, rhs: Universum.void }
- warn: { name: "Use '($>)' from Universum"
, lhs: Data.Functor.($>), rhs: Universum.($>) }
- warn: { name: "Use 'Compose' from Universum"
, lhs: Data.Functor.Compose.Compose, rhs: Universum.Compose }
- warn: { name: "Use 'getCompose' from Universum"
, lhs: Data.Functor.Compose.getCompose, rhs: Universum.getCompose }
- warn: { name: "Use 'Identity' from Universum"
, lhs: Data.Functor.Identity.Identity, rhs: Universum.Identity }
- warn: { name: "Use 'runIdentity' from Universum"
, lhs: Data.Functor.Identity.runIdentity, rhs: Universum.runIdentity }
## List
- warn: { name: "Use 'genericDrop' from Universum"
, lhs: Data.List.genericDrop, rhs: Universum.genericDrop }
- warn: { name: "Use 'genericLength' from Universum"
, lhs: Data.List.genericLength, rhs: Universum.genericLength }
- warn: { name: "Use 'genericReplicate' from Universum"
, lhs: Data.List.genericReplicate, rhs: Universum.genericReplicate }
- warn: { name: "Use 'genericSplitAt' from Universum"
, lhs: Data.List.genericSplitAt, rhs: Universum.genericSplitAt }
- warn: { name: "Use 'genericTake' from Universum"
, lhs: Data.List.genericTake, rhs: Universum.genericTake }
- warn: { name: "Use 'group' from Universum"
, lhs: Data.List.group, rhs: Universum.group }
- warn: { name: "Use 'inits' from Universum"
, lhs: Data.List.inits, rhs: Universum.inits }
- warn: { name: "Use 'intercalate' from Universum"
, lhs: Data.List.intercalate, rhs: Universum.intercalate }
- warn: { name: "Use 'intersperse' from Universum"
, lhs: Data.List.intersperse, rhs: Universum.intersperse }
- warn: { name: "Use 'isPrefixOf' from Universum"
, lhs: Data.List.isPrefixOf, rhs: Universum.isPrefixOf }
- warn: { name: "Use 'permutations' from Universum"
, lhs: Data.List.permutations, rhs: Universum.permutations }
- warn: { name: "Use 'sort' from Universum"
, lhs: Data.List.sort, rhs: Universum.sort }
- warn: { name: "Use 'sortBy' from Universum"
, lhs: Data.List.sortBy, rhs: Universum.sortBy }
- warn: { name: "Use 'sortOn' from Universum"
, lhs: Data.List.sortOn, rhs: Universum.sortOn }
- warn: { name: "Use 'subsequences' from Universum"
, lhs: Data.List.subsequences, rhs: Universum.subsequences }
- warn: { name: "Use 'tails' from Universum"
, lhs: Data.List.tails, rhs: Universum.tails }
- warn: { name: "Use 'transpose' from Universum"
, lhs: Data.List.transpose, rhs: Universum.transpose }
- warn: { name: "Use 'unfoldr' from Universum"
, lhs: Data.List.unfoldr, rhs: Universum.unfoldr }
- warn: { name: "Use 'NonEmpty' from Universum"
, lhs: Data.List.NonEmpty.NonEmpty, rhs: Universum.NonEmpty }
- warn: { name: "Use '(:|)' from Universum"
, lhs: "Data.List.NonEmpty.(:|)", rhs: "Universum.(:|)"}
- warn: { name: "Use 'nonEmpty' from Universum"
, lhs: Data.List.NonEmpty.nonEmpty, rhs: Universum.nonEmpty}
- warn: { name: "Use 'head' from Universum"
, lhs: Data.List.NonEmpty.head, rhs: Universum.head }
- warn: { name: "Use 'init' from Universum"
, lhs: Data.List.NonEmpty.init, rhs: Universum.init }
- warn: { name: "Use 'last' from Universum"
, lhs: Data.List.NonEmpty.last, rhs: Universum.last }
- warn: { name: "Use 'tail' from Universum"
, lhs: Data.List.NonEmpty.tail, rhs: Universum.tail }
- warn: { name: "Use 'sortWith' from Universum"
, lhs: GHC.Exts.sortWith, rhs: Universum.sortWith }
## Monad
- warn: { name: "Use '(>=>)' from Universum"
, lhs: Control.Monad.(>=>), rhs: Universum.(>=>) }
- warn: { name: "Use '(<=<)' from Universum"
, lhs: Control.Monad.(<=<), rhs: Universum.(<=<) }
- warn: { name: "Use 'forever' from Universum"
, lhs: Control.Monad.forever, rhs: Universum.forever }
- warn: { name: "Use 'join' from Universum"
, lhs: Control.Monad.join, rhs: Universum.join }
- warn: { name: "Use 'mfilter' from Universum"
, lhs: Control.Monad.mfilter, rhs: Universum.mfilter }
- warn: { name: "Use 'filterM' from Universum"
, lhs: Control.Monad.filterM, rhs: Universum.filterM }
- warn: { name: "Use 'mapAndUnzipM' from Universum"
, lhs: Control.Monad.mapAndUnzipM, rhs: Universum.mapAndUnzipM }
- warn: { name: "Use 'zipWithM' from Universum"
, lhs: Control.Monad.zipWithM, rhs: Universum.zipWithM }
- warn: { name: "Use 'zipWithM_' from Universum"
, lhs: Control.Monad.zipWithM_, rhs: Universum.zipWithM_ }
- warn: { name: "Use 'foldM' from Universum"
, lhs: Control.Monad.foldM, rhs: Universum.foldM }
- warn: { name: "Use 'foldM_' from Universum"
, lhs: Control.Monad.foldM_, rhs: Universum.foldM_ }
- warn: { name: "Use 'replicateM' from Universum"
, lhs: Control.Monad.replicateM, rhs: Universum.replicateM }
- warn: { name: "Use 'replicateM_' from Universum"
, lhs: Control.Monad.replicateM_, rhs: Universum.replicateM_ }
- warn: { name: "Use 'liftM2' from Universum"
, lhs: Control.Monad.liftM2, rhs: Universum.liftM2 }
- warn: { name: "Use 'liftM3' from Universum"
, lhs: Control.Monad.liftM3, rhs: Universum.liftM3 }
- warn: { name: "Use 'liftM4' from Universum"
, lhs: Control.Monad.liftM4, rhs: Universum.liftM4 }
- warn: { name: "Use 'liftM5' from Universum"
, lhs: Control.Monad.liftM5, rhs: Universum.liftM5 }
- warn: { name: "Use 'ap' from Universum"
, lhs: Control.Monad.ap, rhs: Universum.ap }
- warn: { name: "Use '(<$!>)' from Universum"
, lhs: Control.Monad.(<$!>), rhs: Universum.(<$!>) }
- warn: { name: "Use 'ExceptT' from Universum"
, lhs: Control.Monad.Except.ExceptT, rhs: Universum.ExceptT }
- warn: { name: "Use 'runExceptT' from Universum"
, lhs: Control.Monad.Except.runExceptT, rhs: Universum.runExceptT }
- warn: { name: "Use 'MonadReader' from Universum"
, lhs: Control.Monad.Reader.MonadReader, rhs: Universum.MonadReader }
- warn: { name: "Use 'Reader' from Universum"
, lhs: Control.Monad.Reader.Reader, rhs: Universum.Reader }
- warn: { name: "Use 'ReaderT' from Universum"
, lhs: Control.Monad.Reader.ReaderT, rhs: Universum.ReaderT }
- warn: { name: "Use 'runReaderT' from Universum"
, lhs: Control.Monad.Reader.runReaderT, rhs: Universum.runReaderT }
- warn: { name: "Use 'ask' from Universum"
, lhs: Control.Monad.Reader.ask, rhs: Universum.ask }
- warn: { name: "Use 'local' from Universum"
, lhs: Control.Monad.Reader.local, rhs: Universum.local }
- warn: { name: "Use 'reader' from Universum"
, lhs: Control.Monad.Reader.reader, rhs: Universum.reader }
- warn: { name: "Use 'runReader' from Universum"
, lhs: Control.Monad.Reader.runReader, rhs: Universum.runReader }
- warn: { name: "Use 'MonadState' from Universum"
, lhs: Control.Monad.State.Strict.MonadState, rhs: Universum.MonadState }
- warn: { name: "Use 'State' from Universum"
, lhs: Control.Monad.State.Strict.State, rhs: Universum.State }
- warn: { name: "Use 'StateT' from Universum"
, lhs: Control.Monad.State.Strict.StateT, rhs: Universum.StateT }
- warn: { name: "Use 'runStateT' from Universum"
, lhs: Control.Monad.State.Strict.runStateT, rhs: Universum.runStateT }
- warn: { name: "Use 'evalState' from Universum"
, lhs: Control.Monad.State.Strict.evalState, rhs: Universum.evalState }
- warn: { name: "Use 'evalStateT' from Universum"
, lhs: Control.Monad.State.Strict.evalStateT, rhs: Universum.evalStateT }
- warn: { name: "Use 'execState' from Universum"
, lhs: Control.Monad.State.Strict.execState, rhs: Universum.execState }
- warn: { name: "Use 'execStateT' from Universum"
, lhs: Control.Monad.State.Strict.execStateT, rhs: Universum.execStateT }
- warn: { name: "Use 'get' from Universum"
, lhs: Control.Monad.State.Strict.get, rhs: Universum.get }
- warn: { name: "Use 'gets' from Universum"
, lhs: Control.Monad.State.Strict.gets, rhs: Universum.gets }
- warn: { name: "Use 'modify' from Universum"
, lhs: Control.Monad.State.Strict.modify, rhs: Universum.modify }
- warn: { name: "Use 'modify'' from Universum"
, lhs: "Control.Monad.State.Strict.modify'", rhs: "Universum.modify'" }
- warn: { name: "Use 'put' from Universum"
, lhs: Control.Monad.State.Strict.put, rhs: Universum.put }
- warn: { name: "Use 'runState' from Universum"
, lhs: Control.Monad.State.Strict.runState, rhs: Universum.runState }
- warn: { name: "Use 'state' from Universum"
, lhs: Control.Monad.State.Strict.state, rhs: Universum.state }
- warn: { name: "Use 'withState' from Universum"
, lhs: Control.Monad.State.Strict.withState, rhs: Universum.withState }
- warn: { name: "Use 'MonadFail' from Universum"
, lhs: Control.Monad.Fail.MonadFail, rhs: Universum.MonadFail }
- warn: { name: "Use 'MonadIO' from Universum"
, lhs: Control.Monad.Trans.MonadIO, rhs: Universum.MonadIO }
- warn: { name: "Use 'MonadTrans' from Universum"
, lhs: Control.Monad.Trans.MonadTrans, rhs: Universum.MonadTrans }
- warn: { name: "Use 'lift' from Universum"
, lhs: Control.Monad.Trans.lift, rhs: Universum.lift }
- warn: { name: "Use 'liftIO' from Universum"
, lhs: Control.Monad.Trans.liftIO, rhs: Universum.liftIO }
- warn: { name: "Use 'IdentityT' from Universum"
, lhs: Control.Monad.Trans.Identity.IdentityT, rhs: Universum.IdentityT }
- warn: { name: "Use 'runIdentityT' from Universum"
, lhs: Control.Monad.Trans.Identity.runIdentityT, rhs: Universum.runIdentityT }
- warn: { name: "Use 'MaybeT' from Universum"
, lhs: Control.Monad.Trans.Maybe.MaybeT, rhs: Universum.MaybeT }
- warn: { name: "Use 'maybeToExceptT' from Universum"
, lhs: Control.Monad.Trans.Maybe.maybeToExceptT, rhs: Universum.maybeToExceptT }
- warn: { name: "Use 'exceptToMaybeT' from Universum"
, lhs: Control.Monad.Trans.Maybe.exceptToMaybeT, rhs: Universum.exceptToMaybeT }
- warn: { name: "Use 'catMaybes' from Universum"
, lhs: Data.Maybe.catMaybes, rhs: Universum.catMaybes }
- warn: { name: "Use 'fromMaybe' from Universum"
, lhs: Data.Maybe.fromMaybe, rhs: Universum.fromMaybe }
- warn: { name: "Use 'isJust' from Universum"
, lhs: Data.Maybe.isJust, rhs: Universum.isJust }
- warn: { name: "Use 'isNothing' from Universum"
, lhs: Data.Maybe.isNothing, rhs: Universum.isNothing }
- warn: { name: "Use 'listToMaybe' from Universum"
, lhs: Data.Maybe.listToMaybe, rhs: Universum.listToMaybe }
- warn: { name: "Use 'mapMaybe' from Universum"
, lhs: Data.Maybe.mapMaybe, rhs: Universum.mapMaybe }
- warn: { name: "Use 'maybeToList' from Universum"
, lhs: Data.Maybe.maybeToList, rhs: Universum.maybeToList }
- warn: { name: "Use 'isLeft' from Universum"
, lhs: Data.Either.isLeft, rhs: Universum.isLeft }
- warn: { name: "Use 'isRight' from Universum"
, lhs: Data.Either.isRight, rhs: Universum.isRight }
- warn: { name: "Use 'lefts' from Universum"
, lhs: Data.Either.lefts, rhs: Universum.lefts }
- warn: { name: "Use 'partitionEithers' from Universum"
, lhs: Data.Either.partitionEithers, rhs: Universum.partitionEithers }
- warn: { name: "Use 'rights' from Universum"
, lhs: Data.Either.rights, rhs: Universum.rights }
- warn: { name: "Use 'newTVar' from Universum"
, lhs: Control.Concurrent.STM.TVar.newTVar, rhs: Universum.newTVar }
- warn: { name: "Use 'readTVar' from Universum"
, lhs: Control.Concurrent.STM.TVar.readTVar, rhs: Universum.readTVar }
- warn: { name: "Use 'writeTVar' from Universum"
, lhs: Control.Concurrent.STM.TVar.writeTVar, rhs: Universum.writeTVar }
- warn: { name: "Use 'modifyTVar'' from Universum"
, lhs: "Control.Concurrent.STM.TVar.modifyTVar'", rhs: "Universum.modifyTVar'" }
- warn: { name: "Use 'newTVarIO' from Universum"
, lhs: Control.Concurrent.STM.TVar.newTVarIO, rhs: Universum.newTVarIO }
- warn: { name: "Use 'readTVarIO' from Universum"
, lhs: Control.Concurrent.STM.TVar.readTVarIO, rhs: Universum.readTVarIO }
- warn: { name: "Use 'newIORef' from Universum"
, lhs: Data.IORef.newIORef, rhs: Universum.newIORef }
- warn: { name: "Use 'readIORef' from Universum"
, lhs: Data.IORef.readIORef, rhs: Universum.readIORef }
- warn: { name: "Use 'writeIORef' from Universum"
, lhs: Data.IORef.writeIORef, rhs: Universum.writeIORef }
- warn: { name: "Use 'modifyIORef' from Universum"
, lhs: Data.IORef.modifyIORef, rhs: Universum.modifyIORef }
- warn: { name: "Use 'modifyIORef'' from Universum"
, lhs: "Data.IORef.modifyIORef'", rhs: "Universum.modifyIORef'" }
- warn: { name: "Use 'atomicModifyIORef' from Universum"
, lhs: Data.IORef.atomicModifyIORef, rhs: Universum.atomicModifyIORef }
- warn: { name: "Use 'atomicModifyIORef'' from Universum"
, lhs: "Data.IORef.atomicModifyIORef'", rhs: "Universum.atomicModifyIORef'" }
- warn: { name: "Use 'atomicWriteIORef' from Universum"
, lhs: Data.IORef.atomicWriteIORef, rhs: Universum.atomicWriteIORef }
## Monoid
- warn: { name: "Use 'All' from Universum"
, lhs: Data.Monoid.All, rhs: Universum.All }
- warn: { name: "Use 'Alt' from Universum"
, lhs: Data.Monoid.Alt, rhs: Universum.Alt }
- warn: { name: "Use 'Any' from Universum"
, lhs: Data.Monoid.Any, rhs: Universum.Any }
- warn: { name: "Use 'Dual' from Universum"
, lhs: Data.Monoid.Dual, rhs: Universum.Dual }
- warn: { name: "Use 'Endo' from Universum"
, lhs: Data.Monoid.Endo, rhs: Universum.Endo }
- warn: { name: "Use 'First' from Universum"
, lhs: Data.Monoid.First, rhs: Universum.First }
- warn: { name: "Use 'Last' from Universum"
, lhs: Data.Monoid.Last, rhs: Universum.Last }
- warn: { name: "Use 'Product' from Universum"
, lhs: Data.Monoid.Product, rhs: Universum.Product }
- warn: { name: "Use 'Sum' from Universum"
, lhs: Data.Monoid.Sum, rhs: Universum.Sum }
- warn: { name: "Use 'Option' from Universum"
, lhs: Data.Semigroup.Option, rhs: Universum.Option }
- warn: { name: "Use 'Semigroup' from Universum"
, lhs: Data.Semigroup.Semigroup, rhs: Universum.Semigroup }
- warn: { name: "Use 'sconcat' from Universum"
, lhs: Data.Semigroup.sconcat, rhs: Universum.sconcat }
- warn: { name: "Use 'stimes' from Universum"
, lhs: Data.Semigroup.stimes, rhs: Universum.stimes }
- warn: { name: "Use '(<>)' from Universum"
, lhs: Data.Semigroup.(<>), rhs: Universum.(<>) }
- warn: { name: "Use 'WrappedMonoid' from Universum"
, lhs: Data.Semigroup.WrappedMonoid, rhs: Universum.WrappedMonoid }
- warn: { name: "Use 'cycle1' from Universum"
, lhs: Data.Semigroup.cycle1, rhs: Universum.cycle1 }
- warn: { name: "Use 'mtimesDefault' from Universum"
, lhs: Data.Semigroup.mtimesDefault, rhs: Universum.mtimesDefault }
- warn: { name: "Use 'stimesIdempotent' from Universum"
, lhs: Data.Semigroup.stimesIdempotent, rhs: Universum.stimesIdempotent }
- warn: { name: "Use 'stimesIdempotentMonoid' from Universum"
, lhs: Data.Semigroup.stimesIdempotentMonoid, rhs: Universum.stimesIdempotentMonoid }
- warn: { name: "Use 'stimesMonoid' from Universum"
, lhs: Data.Semigroup.stimesMonoid, rhs: Universum.stimesMonoid }
## String
- warn: { name: "Use 'ByteString' from Universum"
, lhs: Data.ByteString.ByteString, rhs: Universum.ByteString }
- warn: { name: "Use 'IsString' from Universum"
, lhs: Data.String.IsString, rhs: Universum.IsString }
- warn: { name: "Use 'Text' from Universum"
, lhs: Data.Text.Text, rhs: Universum.Text }
- warn: { name: "Use 'lines' from Universum"
, lhs: Data.Text.lines, rhs: Universum.lines }
- warn: { name: "Use 'unlines' from Universum"
, lhs: Data.Text.unlines, rhs: Universum.unlines }
- warn: { name: "Use 'words' from Universum"
, lhs: Data.Text.words, rhs: Universum.words }
- warn: { name: "Use 'unwords' from Universum"
, lhs: Data.Text.unwords, rhs: Universum.unwords }
- warn: { name: "Use 'LText' from Universum"
, lhs: Data.Text.Lazy.Text, rhs: Universum.LText }
- warn: { name: "Use 'LByteString' from Universum"
, lhs: Data.ByteString.Lazy.LByteString, rhs: Universum.LByteString }
- warn: { name: "Use 'Buildable' from Universum"
, lhs: Data.Text.Buildable, rhs: Universum.Buildable }
- warn: { name: "Use 'decodeUtf8'' from Universum"
, lhs: "Data.Text.Encoding.decodeUtf8'", rhs: "Universum.decodeUtf8'" }
- warn: { name: "Use 'decodeUtf8With' from Universum"
, lhs: Data.Text.Encoding.decodeUtf8With, rhs: Universum.decodeUtf8With }
- warn: { name: "Use 'OnDecodeError' from Universum"
, lhs: Data.Text.Encoding.Error.OnDecodeError, rhs: Universum.OnDecodeError }
- warn: { name: "Use 'OnDecodeError' from Universum"
, lhs: Data.Text.Encoding.Error.OnDecodeError, rhs: Universum.OnDecodeError }
- warn: { name: "Use 'OnError' from Universum"
, lhs: Data.Text.Encoding.Error.OnError, rhs: Universum.OnError }
- warn: { name: "Use 'UnicodeException' from Universum"
, lhs: Data.Text.Encoding.Error.UnicodeException, rhs: Universum.UnicodeException }
- warn: { name: "Use 'lenientDecode' from Universum"
, lhs: Data.Text.Encoding.Error.lenientDecode, rhs: Universum.lenientDecode }
- warn: { name: "Use 'strictDecode' from Universum"
, lhs: Data.Text.Encoding.Error.strictDecode, rhs: Universum.strictDecode }
- warn: { name: "Use 'fromStrict' from Universum"
, lhs: Data.Text.Lazy.fromStrict, rhs: Universum.fromStrict }
- warn: { name: "Use 'toStrict' from Universum"
, lhs: Data.Text.Lazy.toStrict, rhs: Universum.toStrict }
- warn: { name: "Use 'readMaybe' from Universum"
, lhs: Text.Read.readMaybe, rhs: Universum.readMaybe }
- warn: { name: "Use 'getLine' from Universum"
, lhs: Data.Text.IO.getLine, rhs: Universum.getLine }
- warn: { name: "Use 'readFile' from Universum"
, lhs: Data.Text.IO.readFile, rhs: Universum.readFile }
- warn: { name: "Use 'writeFile' from Universum"
, lhs: Data.Text.IO.writeFile, rhs: Universum.writeFile }
- warn: { name: "Use 'appendFile' from Universum"
, lhs: Data.Text.IO.appendFile, rhs: Universum.appendFile }
## Unsafe
- warn: { name: "Use 'head' from Universum.Unsafe"
, lhs: Data.List.head, rhs: Universum.Unsafe.head
, note: "Use 'import qualified Universum.Unsafe as Unsafe (head)'" }
- warn: { name: "Use 'tail' from Universum.Unsafe"
, lhs: Data.List.tail, rhs: Universum.Unsafe.tail
, note: "Use 'import qualified Universum.Unsafe as Unsafe (tail)'" }
- warn: { name: "Use 'init' from Universum.Unsafe"
, lhs: Data.List.init, rhs: Universum.Unsafe.init
, note: "Use 'import qualified Universum.Unsafe as Unsafe (init)'" }
- warn: { name: "Use 'last' from Universum.Unsafe"
, lhs: Data.List.last, rhs: Universum.Unsafe.last
, note: "Use 'import qualified Universum.Unsafe as Unsafe (last)'" }
- warn: { name: "Use '(!!)' from Universum.Unsafe"
, lhs: "Data.List.(!!)", rhs: "Universum.Unsafe.(!!)"
, note: "Use 'import qualified Universum.Unsafe as Unsafe ((!!))'" }
- warn: { name: "Use 'fromJust' from Universum.Unsafe"
, lhs: "Data.Maybe.fromJust", rhs: "Universum.Unsafe.fromJust"
, note: "Use 'import qualified Universum.Unsafe as Unsafe (fromJust)'" }
############################################################################
## Lifted functions in Universum
############################################################################
## concurrency
- warn: { name: "liftIO is not needed", lhs: liftIO newEmptyMVar, rhs: Universum.newEmptyMVar
, note: "If you import 'newEmptyMVar' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (newMVar x), rhs: Universum.newMVar x
, note: "If you import 'newMVar' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (putMVar x y), rhs: Universum.putMVar x y
, note: "If you import 'putMVar' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (readMVar x), rhs: Universum.readMVar x
, note: "If you import 'readMVar' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (swapMVar x y), rhs: Universum.swapMVar x y
, note: "If you import 'swapMVar' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (takeMVar x), rhs: Universum.takeMVar x
, note: "If you import 'takeMVar' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (tryPutMVar x y), rhs: Universum.tryPutMVar x y
, note: "If you import 'tryPutMVar' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (tryReadMVar x), rhs: Universum.tryReadMVar x
, note: "If you import 'tryReadMVar' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (tryTakeMVar x), rhs: Universum.tryTakeMVar x
, note: "If you import 'tryTakeMVar' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (atomically x), rhs: Universum.atomically x
, note: "If you import 'atomically' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (newTVarIO x), rhs: Universum.newTVarIO x
, note: "If you import 'newTVarIO' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (readTVarIO x), rhs: Universum.readTVarIO x
, note: "If you import 'readTVarIO' from Universum, it's already lifted" }
## IORef
- warn: { name: "liftIO is not needed", lhs: liftIO (newIORef x), rhs: Universum.newIORef x
, note: "If you import 'newIORef' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (readIORef x), rhs: Universum.readIORef x
, note: "If you import 'readIORef' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (writeIORef x y), rhs: Universum.writeIORef x y
, note: "If you import 'writeIORef' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (modifyIORef x y), rhs: Universum.modifyIORef x y
, note: "If you import 'modifyIORef' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: "liftIO (modifyIORef' x y)", rhs: "Universum.modifyIORef' x y"
, note: "If you import 'modifyIORef'' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (atomicModifyIORef x y), rhs: Universum.atomicModifyIORef x y
, note: "If you import 'atomicModifyIORef' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: "liftIO (atomicModifyIORef' x y)", rhs: "Universum.atomicModifyIORef' x y"
, note: "If you import 'atomicModifyIORef'' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (atomicWriteIORef x y), rhs: Universum.atomicWriteIORef x y
, note: "If you import 'atomicWriteIORef' from Universum, it's already lifted" }
## others
- warn: { name: "liftIO is not needed", lhs: liftIO Universum.getLine, rhs: Universum.getLine
, note: "If you import 'getLine' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (Universum.readFile x), rhs: Universum.readFile x
, note: "If you import 'readFile' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (Universum.writeFile x y), rhs: Universum.writeFile x y
, note: "If you import 'writeFile' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (Universum.appendFile x y), rhs: Universum.appendFile x y
, note: "If you import 'appendFile' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (Universum.openFile x y), rhs: Universum.openFile x y
, note: "If you import 'openFile' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (exitWith x), rhs: Universum.exitWith x
, note: "If you import 'exitWith' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO exitFailure, rhs: Universum.exitFailure
, note: "If you import 'exitFailure' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO exitSuccess, rhs: Universum.exitSuccess
, note: "If you import 'exitSuccess' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (die x), rhs: Universum.die x
, note: "If you import 'die' from Universum, it's already lifted" }
- warn: { name: "liftIO is not needed", lhs: liftIO (stToIO x), rhs: Universum.stToIO x
, note: "If you import 'stToIO' from Universum, it's already lifted" }
# walint: lint & adjust workadventure maps
`walint` is intended as a simple linter that will check workadventure maps for
common errors, such as non-existent map entrypoints or missing asset files, and
additionally suggest changes to improve accessability.
### Overview & Components
Optionally, it can also *adjust* maps — e.g. to automatically insert property
values or help enforce an event's map policies — and then write them out again,
copying all needed assets and minifying the map's json. This is used to simulate
a `bbbRoom` property (via `openWebsite`), collect and remove badge tokens before
maps are published, and to resolve special-schema URIs (e.g. `world://`).
`walint` is intended as a linter for workadventure maps that checks for common
errors (such as non-existent map entrypoints or missing asset files) and makes
suggestions to improve accessability.
It can also *adjust* maps — e.g. to automatically insert property values or help
enforce an event's map policies (among other things, this is used to resolve
special inter-assembly `world://` links).
`walint-mapserver` is a minimal implementation of a server that periodically
fetches, lints, and adjusts maps from a set of git repositories, writing them
to a path that can then be served by a webserver. It can be used as a (very
simple) replacement for rc3's hub and mapservice at smaller events.
`cwality-maps` is a small server for type-safe map templating, to be used if
maps need to be generated on-the-fly — for example, to provide custom intermediate
maps displaying CWs before another map can be reached.
## Installing
......@@ -25,7 +34,7 @@ running `ldd walint` and see if anything is marked as not found, then install it
### Build using stack
This uses a lockfile to pin versions of dependencies (as well as `ghc`, the haskell
compiler). You will need [stack](https://docs.haskellstack.org/en/stable/README/).
compiler). You will need [the haskell stack](https://docs.haskellstack.org/en/stable/README/).
Then just run
......@@ -34,8 +43,8 @@ stack build
```
If you lack `ghc` in the correct version and don't know how to install it, you can
pass it `--install-ghc` to take care of that for you (note that on Nix, `stack` may
automatically use a fitting `ghc` derivation if it finds one).
pass it `--install-ghc` to take care of that for you (note that on NixOS, `stack` may
use a fitting `ghc` derivation if it finds one, even without `--install-ghc`).
To install into your `$PATH`, use
......@@ -50,25 +59,11 @@ stack run -- walint [options as normal]
```
However, in this case stack will re-check files every time to ensure your build
is up to date with the sources, which will make it slower to start.
is up to date with the sources, increasing startup time.
### Build using cabal
Note that this does not pin dependencies, and `walint` currently does not even
define semver ranges to ensure it compiles at all! Even so, you can use
[cabal](https://www.haskell.org/cabal/) if for some reason you absolutely must,
as long as your package list is sufficiently recent.
Run:
```
cabal update
cabal build
```
Note that `cabal` might decide to pull in an older version of Aeson which is
still vulnerable to hash flooding; in that case `walint` will print a warning
on startup.
You can, but probably should not. Beware of older Aeson versions!
## Usage
``` sh
......@@ -136,10 +131,8 @@ For now there are three types of such rules:
In case an URI is encountered and there is no applicable rule, it will be rejected
(note that this means you'll have to explicitly allow `https://` for links!)
There are currently four possible scopes: `map` applies to tiled map links
(i.e. `exitUrl`), `website` to `openWebsite`, `audio` to `playAudio`, and
`bbb` to Big Blue Button rooms (though that last one may be changed again,
depending on the bbb deployment in use).
There are currently three possible scopes: `map` applies to tiled map links
(i.e. `exitUrl`), `website` to `openWebsite`, `audio` to `playAudio`.
## Output
......
......@@ -5,21 +5,14 @@
"AllowScripts":false,
"MaxLintLevel":"Warning",
"DontCopyAssets":false,
"UriSchemas": [
["https", {
"scope" : ["website"],
"allowed" : ["media.ccc.de", "streaming.media.ccc.de", "static.rc3.world", "cdn.c3voc.de", "pretalx.c3voc.de"],
"blocked" : ["blocked.com"],
"prefix" : "https:\/\/rc3.world\/2021\/wa_dereferrer\/"
}],
["https", {
"scope" : ["audio"],
"allowed" : ["cdn.c3voc.de", "media.ccc.de", "streaming.media.ccc.de", "static.rc3.world", "live.dort.c3voc.de"]
}],
["world", {
"scope" : ["map"],
"substs" : {
}
}]
]
"UriSchemas": {
"https:": [
{
"scope" : [ "website", "audio" ]
},
{
"scope" : [ "script" ],
"allowed" : [ "scripts.world.di.c3voc.de" ]
}]
}
}
port = 8080
verbose = true
tmpdir = "/tmp"
# linting interval in seconds
interval = 36000
# where to post map updates to
# exneuland = "http://localhost:4000"
# auth token for map updates
token = "hello, world!"
[[org]]
slug = "divoc"
# baseurl of maps as seen by the frontend
url = "https://world.di.c3voc.de/maps/"
# webdir into which maps should be written
webdir = "/tmp/var/www/divoc"
# increment this if you change the server / linter config
# (part of urls for linted maps; allows indefinite browser caching)
generation = 1
backlink_prefix = "world://lobby#start_"
contact_mail = "world@muc.hacc.space"
howto_link = "https://di.c3voc.de/howto:world"
# linter's config for this org
lintconfig = "./config.json"
# map's entrypoint (only maps reachable from here are included)
entrypoint = "main.json"
[[org.repo]] # I hate TOML
url = "https://gitlab.infra4future.de/hacc/events/hacc-map"
ref = "master"
name = "hacc"
[[org.repo]]
url = "https://github.com/namiko/assembly_2021"
ref = "master"
name = "haecksen"
verbose = true
port = 8080
# directory containing template maps.
# all .json files therein will be interpreted as maps;
# other files are served statically
template = "./example-templates"
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
module Config ( loadConfig
, Config, port, verbose, template
) where
import Universum
import Data.List (isSuffixOf)
import qualified Data.Map.Strict as M
import Data.Tiled (Tiledmap,
loadTiledmap)
import Lens.Micro.Platform (makeLenses, traverseOf)
import System.Directory (listDirectory)
import System.FilePath ((</>))
import Toml (TomlCodec, (.=))
import qualified Toml as T
type family ConfigRes (b :: Bool) a where
ConfigRes True a = a
ConfigRes False a = FilePath
-- | the server's configuration
data Config (loaded :: Bool) = Config
{ _port :: Int
, _verbose :: Bool
, _template :: ConfigRes loaded (FilePath, Map Text Tiledmap)
} deriving Generic
makeLenses ''Config
configCodec :: TomlCodec (Config False)
configCodec = Config
<$> T.int "port" .= _port
<*> T.bool "verbose" .= _verbose
<*> T.string "template" .= _template
loadConfig :: FilePath -> IO (Config True)
loadConfig path = do
T.decodeFileEither configCodec path >>= \case
Right c -> traverseOf template loadMaps c
Left err -> error (show err)
where loadMaps path = do
maps <- listDirectory path
<&> filter (".json" `isSuffixOf`)
list <- forM maps $ \mapname ->
loadTiledmap (path </> mapname) >>= \case
Right tmap -> pure (toText mapname, tmap)
err -> error (show err)
pure (path, M.fromList list)
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
-- | simple server offering linting "as a service"
module Main where
import Universum
import Config (Config, loadConfig, port,
template, verbose)
import Data.Aeson (FromJSON)
import qualified Data.Aeson as A
import qualified Data.Map.Strict as M
import qualified Data.Text as T
import Data.Text.Encoding.Base64.URL (decodeBase64Unpadded)
import Data.Tiled (Tiledmap)
import Network.Wai.Handler.Warp (defaultSettings,
runSettings, setPort)
import Network.Wai.Middleware.Gzip (def)
import Network.Wai.Middleware.RequestLogger (OutputFormat (..),
RequestLoggerSettings (..),
mkRequestLogger)
import Servant (Application, Capture,
CaptureAll,
FromHttpApiData (parseUrlPiece),
Get, Handler, JSON, Raw,
Server, err400, err404,
serve, throwError,
type (:<|>) (..),
type (:>))
import Servant.Server.StaticFiles (serveDirectoryWebApp)
import Substitute (Substitutable (substitute),
SubstitutionError)
import Control.Monad.Logger
-- | a map's filename ending in .json
-- (a newtype to differentiate between maps and assets in a route)
newtype JsonFilename = JsonFilename Text
instance FromHttpApiData JsonFilename where
parseUrlPiece url =
if ".json" `T.isSuffixOf` url
then Right (JsonFilename url)
else Left url
newtype Tag = Tag Text
deriving (Generic, FromJSON)
newtype MapParams = MapParams
{ substs :: Map Text Text
} deriving (Generic, FromJSON)
instance FromHttpApiData MapParams where
parseUrlPiece urltext =
case decodeBase64Unpadded urltext of
Right text -> case A.decode (encodeUtf8 text) of
Just params -> params
Nothing -> Left "decoding params failed?"
-- for fun (and testing) also allow non-encoded json
Left _err -> case A.decode (encodeUtf8 urltext) of
Just params -> Right params
Nothing -> Left "decoding MapParams failed"
-- | actual set of routes: api for json & html + static pages from disk
type Routes =
"generate" :> Capture "params" MapParams :>
(Capture "map.json" JsonFilename :> Get '[JSON] Tiledmap
-- explicitly capture broken json to return 400 instead of looking for files
:<|> Capture "map.json" JsonFilename :> CaptureAll "rest" Text :> Get '[JSON] Void
:<|> Raw)
mkMap :: Config True -> Tiledmap -> MapParams -> ([SubstitutionError], Tiledmap)
mkMap _config basemap params =
substitute basemap (substs params)
mapHandler :: MapParams -> Config True -> JsonFilename -> Handler Tiledmap
mapHandler params config (JsonFilename mapname) =
case M.lookup mapname (snd $ view template config) of
Just basemap -> runStdoutLoggingT $
logWarnN (pretty errors) >> pure tiledmap
where (errors, tiledmap) = mkMap config basemap params
pretty errors = T.concat
. intersperse "\n "
$ concatMap (lines . show) errors
Nothing -> throwError err404
-- | Complete set of routes: API + HTML sites
server :: Config True -> Server Routes
server config params =
mapHandler params config
:<|> (\_ _ -> throwError err400)
:<|> serveDirectoryWebApp (fst . view template $ config)
app :: Config True -> Application
app = serve (Proxy @Routes) . server
main :: IO ()
main = do
config <- loadConfig "./cwality-config.toml"
loggerMiddleware <- mkRequestLogger
$ def { outputFormat = Detailed (view verbose config) }
let warpsettings =
setPort (view port config)
defaultSettings
runSettings warpsettings
. loggerMiddleware
$ app config
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
-- | Typeclasses for (generic) substitution on all strings contained in an ADT,
-- failsafe, but with error reporting
module Substitute (SubstitutionError, Substitutable(..)) where
import Universum
import qualified Data.Aeson as A
import qualified Data.Foldable as Fold
import Data.Tiled (GlobalId, LocalId)
import GHC.Generics (Generic (Rep, from, to), K1 (K1),
M1 (M1), U1, type (:*:) ((:*:)),
type (:+:) (..))
import qualified Text.Mustache as MU
import qualified Text.Mustache.Render as MU
import Text.Parsec.Error (ParseError)
-- | errors that might be encountered. SubstitutionErrors occur during substitution
-- and a generally non-fatal (but might result e.g. in empty strings being inserted
-- instead of variables), while CompileErrors may indicate that (invalid) template
-- syntax got leaked into the output
data SubstitutionError = CompileError ParseError | Mustache MU.SubstitutionError
deriving Show
class Substitutable s where
substitute :: s -> Map Text Text -> ([SubstitutionError], s)
instance Substitutable Text where
substitute orig substs = case MU.compileTemplate "" orig of
Right template -> first (map Mustache) $ MU.checkedSubstitute template substs
Left err -> ([CompileError err], orig) -- just ignore syntactic errors (TODO: add a log message?)
instance {-# OVERLAPS #-} Substitutable String where
substitute orig substs = second toString (substitute (toText orig) substs)
instance {-# OVERLAPPING #-} (Functor a, Substitutable b, Foldable a) => Substitutable (a b) where
substitute orig substs = (Fold.fold $ map fst orig',) $ map snd orig'
where orig' = map (`substitute` substs) orig
-- | helper: don't substitute anything, don't produce errors
trivial :: t -> b -> ([a], t)
trivial = const . ([],)
instance {-# OVERLAPS #-} Substitutable A.Value where
substitute (A.Object fields) params =
second A.Object $ traverse (`substitute` params) fields
substitute (A.String str) params =
second A.String $ substitute str params
substitute other params = ([], other)
instance Substitutable Int where
substitute = trivial
instance Substitutable GlobalId where
substitute = trivial
instance Substitutable LocalId where
substitute = trivial
instance Substitutable Double where
substitute = trivial
instance Substitutable Float where
substitute = trivial
class GSubstitutable i where
gsubstitute :: i p -> Map Text Text -> ([SubstitutionError], i p)
instance Substitutable c => GSubstitutable (K1 i c) where
gsubstitute (K1 text) = second K1 . substitute text
instance (GSubstitutable a, GSubstitutable b) => GSubstitutable (a :*: b) where
gsubstitute (a :*: b) substs = (e1 <> e2, a' :*: b')
where (e1, a') = gsubstitute a substs
(e2, b') = gsubstitute b substs
instance (GSubstitutable a, GSubstitutable b) => GSubstitutable (a :+: b) where
gsubstitute (L1 a) = second L1 . gsubstitute a
gsubstitute (R1 a) = second R1 . gsubstitute a
instance (GSubstitutable a) => GSubstitutable (M1 x y a) where
gsubstitute (M1 a) = second M1 . gsubstitute a
instance GSubstitutable U1 where
gsubstitute = trivial
instance {-# OVERLAPPABLE #-} (GSubstitutable (Rep a), Generic a) => Substitutable a where
substitute a substs = second to (gsubstitute (from a) substs)
let
sources = import ./nix/sources.nix {};
haskellNix = import sources.haskellNix {};
# Import nixpkgs and pass the haskell.nix provided nixpkgsArgs
pkgs = import
# use haskell.nix's nixpkgs, which may (?) have more substitutes available
haskellNix.sources.nixpkgs-unstable
# args for nixpkgs; includes the haskell.nix overlay
(haskellNix.nixpkgsArgs // { system = "x86_64-linux"; });
drvs = pkgs.haskell-nix.project {
# 'cleanGit' cleans a source directory based on the files known by git
src = pkgs.haskell-nix.haskellLib.cleanGit {
src = ./.;
name = "walint";
};
modules = [{
packages.walint.components.exes = {
# don't include gcc or ghc in the dependency closure …
walint-server.dontStrip = false;
walint.dontStrip = false;
};
}];
stack-sha256 = "0bp3dwj2kl6n0swz5yf9kjy5ahp6i5qrnb39hkrsqgf0682i9nk1";
};
in
{
walint = drvs.walint.components.exes.walint;
walint-server = pkgs.stdenvNoCC.mkDerivation {
name = "walint-server-with-assets";
src = drvs.walint.components.exes.walint-mapserver;
phases = [ "buildPhase" ];
buildPhase = ''
mkdir -p $out
cp -r $src/* $out
cp -r ${pkgs.copyPathToStore ./static} $out/static
cp ${pkgs.copyPathToStore ./config.json} $out/config.json
cp ${pkgs.copyPathToStore ./config.toml} $out/config.toml
'';
};
}
{ nixpkgs ? import <nixpkgs> {} }:
with nixpkgs;
stdenv.mkDerivation {
name = "walint-fixed";
buildInputs = [ ghc stack zlib zlib.dev git openssl cacert ];
src = ./.;
buildPhase = ''
cp -r $src .
mkdir /tmp/stack-home
HOME=/tmp/stack-home stack build --no-nix --system-ghc
'';
installPhase = ''
HOME=/tmp/stack-home stack install --local-bin-path $out --no-nix --system-ghc
mkdir -p $out/share/walint
cp -r static $out/share/walint
cp config.json $out/share/walint
cp config.toml $out/share/walint
'';
outputHashAlgo = "sha256";
outputHashMode = "recursive";
# replace this with the correct SHA256
outputHash = "sha256-Qd7MDGslrS6zs6WWI9sjzDous0nUbrjdK2fF747KLq8=";
dontShrink = true;
}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
-- | module defining Badge types and utility functions
module Badges where
import Universum
import Data.Aeson (Options (fieldLabelModifier, sumEncoding),
SumEncoding (UntaggedValue), ToJSON (toJSON),
defaultOptions, genericToJSON, (.=))
import qualified Data.Aeson as A
import Data.Char (toLower)
import Data.Text (Text)
import GHC.Generics (Generic)
import Text.Regex.TDFA ((=~))
......@@ -27,10 +29,10 @@ data BadgeArea =
, areaWidth :: Double
, areaHeight :: Double
}
deriving (Ord, Eq, Generic, Show)
deriving (Ord, Eq, Generic, Show, NFData)
newtype BadgeToken = BadgeToken Text
deriving (Eq, Ord, Show)
deriving newtype (Eq, Ord, Show, NFData)
instance ToJSON BadgeArea where
toJSON = genericToJSON defaultOptions
......@@ -46,7 +48,7 @@ parseToken text = if text =~ ("^[a-zA-Z0-9]{50}$" :: Text)
else Nothing
data Badge = Badge BadgeToken BadgeArea
deriving (Ord, Eq, Generic, Show)
deriving (Ord, Eq, Generic, Show, NFData)
instance ToJSON Badge where
toJSON (Badge token area) = A.object $ case area of
......
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
-- | Module that contains high-level checking for an entire directory
module CheckDir (recursiveCheckDir, DirResult(..), resultIsFatal) where
import CheckMap (MapResult (..), loadAndLintMap)
import Control.Monad (void)
module CheckDir ( maximumLintLevel
, recursiveCheckDir
, DirResult (..)
, MissingAsset(..)
, MissingDep(..)
, resultIsFatal
,shrinkDirResult) where
import Universum hiding (Set)
import CheckMap (MapResult (..), Optional,
ResultKind (..), loadAndLintMap,
shrinkMapResult)
import Control.Monad.Extra (mapMaybeM)
import Data.Aeson (ToJSON, (.=))
import qualified Data.Aeson as A
import Data.Bifunctor (first)
import Data.Foldable (fold)
import Data.Functor ((<&>))
import Data.List (partition)
import Data.Map (Map, elems, keys)
import qualified Data.Map as M
import Data.Map.Strict (mapKeys, mapWithKey, (\\))
import Data.Maybe (isJust, mapMaybe)
import Data.Text (Text, isInfixOf)
import Data.Text (isInfixOf)
import qualified Data.Text as T
import Data.Tiled (Tiledmap)
import Dirgraph (graphToDot, invertGraph, resultToGraph,
takeSubGraph, unreachableFrom)
import GHC.Generics (Generic)
......@@ -33,7 +42,7 @@ import System.Directory.Extra (doesFileExist)
import System.FilePath (splitPath, (</>))
import qualified System.FilePath as FP
import System.FilePath.Posix (takeDirectory)
import Text.Dot (Dot, showDot)
import Text.Dot (showDot)
import Types (Dep (Local, LocalMap), Hint (Hint),
Level (..), hintLevel)
import Util (PrettyPrint (prettyprint), ellipsis)
......@@ -48,29 +57,39 @@ listFromSet :: Set a -> [a]
listFromSet = map fst . M.toList
-- | Result of linting an entire directory / repository
data DirResult = DirResult
{ dirresultMaps :: Map FilePath MapResult
data DirResult (complete :: ResultKind) = DirResult
{ dirresultMaps :: Map FilePath (MapResult complete)
-- ^ all maps of this respository, by (local) filepath
, dirresultDeps :: [MissingDep]
-- ^ all dependencies to things outside this repository
, dirresultMissingAssets :: [MissingAsset]
-- ^ entrypoints of maps which are referred to but missing
, dirresultGraph :: Dot ()
, dirresultGraph :: Text
} deriving (Generic)
instance NFData (Optional a (Maybe Tiledmap)) => NFData (DirResult a)
data MissingDep = MissingDep
{ depFatal :: Maybe Bool
, entrypoint :: Text
, neededBy :: [FilePath]
} deriving (Generic, ToJSON)
} deriving (Generic, ToJSON, NFData)
-- | Missing assets are the same thing as missing dependencies,
-- but should not be confused (and also serialise differently
-- to json)
newtype MissingAsset = MissingAsset MissingDep
deriving (Generic, NFData)
-- | "shrink" the result by throwing the adjusted tiledmaps away
shrinkDirResult :: DirResult Full -> DirResult Shrunk
shrinkDirResult !res =
res { dirresultMaps = fmap shrinkMapResult (dirresultMaps res) }
-- | given this config, should the result be considered to have failed?
resultIsFatal :: LintConfig' -> DirResult -> Bool
resultIsFatal :: LintConfig' -> DirResult Full -> Bool
resultIsFatal config res =
not (null (dirresultMissingAssets res) || not (any (isJust . depFatal) (dirresultDeps res)))
|| maximumLintLevel res > configMaxLintLevel config
......@@ -78,11 +97,11 @@ resultIsFatal config res =
-- | maximum lint level that was observed anywhere in any map.
-- note that it really does go through all lints, so don't
-- call it too often
maximumLintLevel :: DirResult -> Level
maximumLintLevel :: DirResult a -> Level
maximumLintLevel res
| not (null (dirresultMissingAssets res)) = Fatal
| otherwise =
(\t -> if null t then Info else maximum t)
(maybe Info maximum . nonEmpty)
. map hintLevel
. concatMap (\map -> keys (mapresultLayer map)
<> keys (mapresultTileset map)
......@@ -94,7 +113,7 @@ maximumLintLevel res
instance ToJSON DirResult where
instance ToJSON (DirResult a) where
toJSON res = A.object [
"result" .= A.object
[ "missingDeps" .= dirresultDeps res
......@@ -106,8 +125,7 @@ instance ToJSON DirResult where
. foldr aggregateSameResults []
. M.toList
$ dirresultMaps res)
-- unused in the hub, temporarily removed to make the output smaller
, "exitGraph" .= showDot (dirresultGraph res)
, "exitGraph" .= dirresultGraph res
]
, "severity" .= maximumLintLevel res
, "mapInfo" .= fmap (\tm -> A.object [ "badges" .= mapresultBadges tm ])
......@@ -116,8 +134,8 @@ instance ToJSON DirResult where
where
aggregateSameResults (path,res) acc =
case partition (\(_,res') -> res == res') acc of
([],_) -> ([T.pack path], res):acc
((paths,_):_,acc') -> (T.pack path:paths, res) : acc'
([],_) -> ([toText path], res):acc
((paths,_):_,acc') -> (toText path:paths, res) : acc'
instance ToJSON MissingAsset where
toJSON (MissingAsset md) = A.object
......@@ -126,7 +144,7 @@ instance ToJSON MissingAsset where
]
instance PrettyPrint (Level, DirResult) where
instance PrettyPrint (Level, DirResult a) where
prettyprint (level, res) = prettyMapLints <> prettyMissingDeps
where
prettyMissingDeps = if not (null (dirresultDeps res))
......@@ -134,9 +152,9 @@ instance PrettyPrint (Level, DirResult) where
else ""
prettyMapLints = T.concat
(map prettyLint $ M.toList $ dirresultMaps res)
prettyLint :: (FilePath, MapResult) -> Text
prettyLint :: (FilePath, MapResult a) -> Text
prettyLint (p, lint) =
"\nin " <> T.pack p <> ":\n" <> prettyprint (level, lint)
"\nin " <> toText p <> ":\n" <> prettyprint (level, lint)
instance PrettyPrint MissingDep where
prettyprint (MissingDep _ f n) =
......@@ -144,7 +162,7 @@ instance PrettyPrint MissingDep where
<> prettyDependents <> "\n"
where
prettyDependents =
T.intercalate "," $ map T.pack n
T.intercalate "," $ map toText n
-- | check an entire repository
......@@ -154,7 +172,7 @@ recursiveCheckDir
-- ^ the repository's prefix (i.e. path to its directory)
-> FilePath
-- ^ the repository's entrypoint (filename of a map, from the repo's root)
-> IO DirResult
-> IO (DirResult Full)
recursiveCheckDir config prefix root = do
maps <- recursiveCheckDir' config prefix [root] mempty
......@@ -169,7 +187,7 @@ recursiveCheckDir config prefix root = do
let maps' = flip mapWithKey maps $ \path res ->
if path `elem` nowayback
then res { mapresultGeneral =
Hint Warning ("Cannot go back to " <> T.pack root <> " from this map.")
Hint Warning ("Cannot go back to " <> toText root <> " from this map.")
: mapresultGeneral res
}
else res
......@@ -179,7 +197,9 @@ recursiveCheckDir config prefix root = do
, dirresultMissingAssets = mAssets
, dirresultMaps = maps'
, dirresultGraph =
graphToDot
toText
. showDot
. graphToDot
. takeSubGraph 7 root
$ exitGraph
}
......@@ -187,9 +207,9 @@ recursiveCheckDir config prefix root = do
-- | Given a (partially) completed DirResult, check which local
-- maps are referenced but do not actually exist.
missingDeps :: FilePath -> Map FilePath MapResult -> [MissingDep]
missingDeps :: FilePath -> Map FilePath (MapResult a) -> [MissingDep]
missingDeps entrypoint maps =
let simple = M.insert (T.pack entrypoint) [] used \\ M.union defined trivial
let simple = M.insert (toText entrypoint) [] used \\ M.union defined trivial
in M.foldMapWithKey (\f n -> [MissingDep (Just $ not ("#" `isInfixOf` f)) f n]) simple
where
-- which maps are linked somewhere?
......@@ -199,19 +219,19 @@ missingDeps entrypoint maps =
(\path v -> map (, [path]) . mapMaybe (extractLocalDeps path) . mapresultDepends $ v)
maps
where extractLocalDeps prefix = \case
LocalMap name -> Just $ T.pack $ normaliseWithFrag prefix name
LocalMap name -> Just $ toText $ normaliseWithFrag prefix name
_ -> Nothing
-- which are defined using startLayer?
defined :: Set Text
defined = setFromList
$ M.foldMapWithKey
(\k v -> map ((T.pack k <> "#") <>) . mapresultProvides $ v)
(\k v -> map ((toText k <> "#") <>) . mapresultProvides $ v)
maps
-- each map file is an entrypoint by itself
trivial = mapKeys T.pack $ void maps
trivial = mapKeys toText $ void maps
-- | Checks if all assets referenced in the result actually exist as files
missingAssets :: FilePath -> Map FilePath MapResult -> IO [MissingAsset]
missingAssets :: FilePath -> Map FilePath (MapResult a) -> IO [MissingAsset]
missingAssets prefix maps =
mapM (fmap (fmap (fmap MissingAsset)) missingOfMap) (M.toList maps) <&> fold
where missingOfMap (path, mapres) = mapMaybeM
......@@ -219,7 +239,7 @@ missingAssets prefix maps =
let asset = normalise (takeDirectory path) relpath
in doesFileExist (prefix </> asset) <&>
\case True -> Nothing
False -> Just $ MissingDep Nothing (T.pack asset) [path]
False -> Just $ MissingDep Nothing (toText asset) [path]
_ -> pure Nothing)
(mapresultDepends mapres)
......@@ -231,10 +251,10 @@ recursiveCheckDir'
-- ^ the repo's directory
-> [FilePath]
-- ^ paths of maps yet to check
-> Map FilePath MapResult
-> Map FilePath (MapResult Full)
-- ^ accumulator for map results
-> IO (Map FilePath MapResult)
recursiveCheckDir' config prefix paths acc = do
-> IO (Map FilePath (MapResult Full))
recursiveCheckDir' config prefix paths !acc = do
-- lint all maps in paths. The double fmap skips maps which cause IO errors
-- (in which case loadAndLintMap returns Nothing); appropriate warnings will
......@@ -242,7 +262,7 @@ recursiveCheckDir' config prefix paths acc = do
lints <-
let lintPath p = fmap (fmap (p,)) (loadAndLintMap config (prefix </> p) depth)
where depth = length (splitPath p) - 1
in mapMaybeM lintPath paths
in mapMaybeM lintPath paths >>= evaluateNF
let mapdeps = setFromList (concatMap extractDeps lints)
......
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
-- | Module that contains the high-level checking functions
module CheckMap (loadAndLintMap, MapResult(..)) where
module CheckMap (loadAndLintMap, MapResult(..), ResultKind(..), Optional,shrinkMapResult) where
import Universum
import Data.Aeson (ToJSON (toJSON))
import qualified Data.Aeson as A
import Data.Aeson.Types ((.=))
import Data.Functor ((<&>))
import Data.Map (Map, toList)
import qualified Data.Map as M
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Vector as V
import GHC.Generics (Generic)
import Badges (Badge)
import LintConfig (LintConfig (configAssemblyTag), LintConfig')
import Data.Tiled (Layer (layerLayers, layerName),
Tiledmap (tiledmapLayers, tiledmapTilesets),
loadTiledmap)
import LintConfig (LintConfig (..), LintConfig')
import LintWriter (LintResult, invertLintResult,
resultToAdjusted, resultToBadges,
resultToDeps, resultToLints, resultToOffers,
runLintWriter)
runLintWriter, resultToCWs)
import Properties (checkLayer, checkMap, checkTileset)
import System.FilePath (takeFileName)
import Tiled (Layer (layerLayers, layerName),
LoadResult (..),
Tiledmap (tiledmapLayers, tiledmapTilesets),
loadTiledmap)
import Types (Dep (MapLink),
Hint (Hint, hintLevel, hintMsg), Level (..),
lintsToHints)
import Util (PrettyPrint (prettyprint), prettyprint)
data ResultKind = Full | Shrunk
type family Optional (a :: ResultKind) (b :: *) where
Optional Full b = b
Optional Shrunk b = ()
-- | What this linter produces: lints for a single map
data MapResult = MapResult
data MapResult (kind :: ResultKind) = MapResult
{ mapresultLayer :: Map Hint [Text]
-- ^ lints that occurred in one or more layers
, mapresultTileset :: Map Hint [Text]
......@@ -49,16 +58,20 @@ data MapResult = MapResult
-- ^ (external and local) dependencies of this map
, mapresultProvides :: [Text]
-- ^ entrypoints provided by this map (needed for dependency checking)
, mapresultAdjusted :: Maybe Tiledmap
, mapresultAdjusted :: Optional kind (Maybe Tiledmap)
-- ^ the loaded map, with adjustments by the linter
, mapresultBadges :: [Badge]
-- ^ badges that can be found on this map
, mapresultCWs :: [Text]
-- ^ collected CWs that apply to this map
, mapresultGeneral :: [Hint]
-- ^ general-purpose lints that didn't fit anywhere else
} deriving (Generic)
instance NFData (Optional a (Maybe Tiledmap)) => NFData (MapResult a)
instance Eq MapResult where
instance Eq (MapResult a) where
a == b =
mapresultLayer a == mapresultLayer b &&
mapresultTileset a == mapresultTileset b &&
......@@ -66,7 +79,7 @@ instance Eq MapResult where
mapresultGeneral a == mapresultGeneral b
instance ToJSON MapResult where
instance ToJSON (MapResult a) where
toJSON res = A.object
[ "layer" .= CollectedLints (mapresultLayer res)
, "tileset" .= CollectedLints (mapresultTileset res)
......@@ -84,40 +97,45 @@ instance ToJSON CollectedLints where
else cs
shrinkMapResult :: MapResult Full -> MapResult Shrunk
shrinkMapResult !res = res { mapresultAdjusted = () }
-- | this module's raison d'être
-- Lints the map at `path`, and limits local links to at most `depth`
-- layers upwards in the file hierarchy
loadAndLintMap :: LintConfig' -> FilePath -> Int -> IO (Maybe MapResult)
loadAndLintMap config path depth = loadTiledmap path <&> (\case
DecodeErr err -> Just (MapResult mempty mempty mempty mempty Nothing mempty
[ Hint Fatal . T.pack $
loadAndLintMap :: LintConfig' -> FilePath -> Int -> IO (Maybe (MapResult Full))
loadAndLintMap config path depth = loadTiledmap path <&> \case
Left err -> Just (MapResult mempty mempty mempty mempty Nothing mempty mempty
[ Hint Fatal . toText $
path <> ": Fatal: " <> err
])
IOErr _ -> Nothing
Loaded waMap ->
Just (runLinter (takeFileName path == "main.json") config waMap depth))
Right waMap ->
Just (runLinter (takeFileName path == "main.json") config waMap depth)
-- | lint a loaded map
runLinter :: Bool -> LintConfig' -> Tiledmap -> Int -> MapResult
runLinter isMain config tiledmap depth = MapResult
runLinter :: Bool -> LintConfig' -> Tiledmap -> Int -> MapResult Full
runLinter isMain config@LintConfig{..} tiledmap depth = MapResult
{ mapresultLayer = invertThing layer
, mapresultTileset = invertThing tileset
, mapresultGeneral =
([Hint Error "main.json should link back to the lobby" | isMain && not (any linksLobby layerDeps)])
[Hint Warning "main.json should link back to the lobby"
| isMain && not (any linksLobby layerDeps)]
<> lintsToHints (resultToLints generalResult)
, mapresultDepends = resultToDeps generalResult
<> layerDeps
<> concatMap resultToDeps tileset
, mapresultProvides = concatMap resultToOffers layer
, mapresultAdjusted = Just adjustedMap
, mapresultCWs = resultToCWs generalResult
, mapresultBadges = concatMap resultToBadges layer
<> resultToBadges generalResult
}
where
linksLobby = \case
MapLink link -> "/@/rc3_21/lobby" `T.isPrefixOf` link
|| configAssemblyTag config == "lobby"
_ -> False
MapLink link ->
("/@/"<>configEventSlug<>"/lobby") `T.isPrefixOf` link
|| configAssemblyTag == "lobby"
_ -> False
layerDeps = concatMap resultToDeps layer
layer = checkLayerRec config depth (V.toList $ tiledmapLayers tiledmap)
tileset = checkThing tiledmapTilesets checkTileset
......@@ -183,7 +201,7 @@ checkLayerRec config depth layers =
-- human-readable lint output, e.g. for consoles
instance PrettyPrint (Level, MapResult) where
instance PrettyPrint (Level, MapResult a) where
prettyprint (_, mapResult) = if complete == ""
then " all good!\n" else complete
where
......@@ -194,11 +212,10 @@ instance PrettyPrint (Level, MapResult) where
-- | pretty-prints a collection of Hints, printing each
-- Hint only once, then a list of its occurences line-wrapped
-- to fit onto a decent-sized terminal
prettyLints :: (MapResult -> Map Hint [Text]) -> [Text]
prettyLints :: (MapResult a -> Map Hint [Text]) -> [Text]
prettyLints getter = fmap
(\(h, cs) -> prettyprint h
<> "\n (in "
-- foldl :: ((length of current line, acc) -> next ctxt -> list) -> ...
<> snd (foldl (\(l,a) c -> case l of
0 -> (T.length c, c)
_ | l < 70 -> (l+2+T.length c, a <> ", " <> c)
......@@ -206,7 +223,7 @@ instance PrettyPrint (Level, MapResult) where
)
(0, "") cs)
<> ")\n")
(toList . getter $ mapResult)
(M.toList . getter $ mapResult)
prettyGeneral :: [Text]
prettyGeneral = map
......
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TupleSections #-}
-- | Simple directed graphs, for dependency checking
module Dirgraph where
import Universum
import CheckMap (MapResult (mapresultDepends))
import Control.Monad (forM_, unless)
import Data.Functor ((<&>))
import Data.Map.Strict (Map, mapMaybeWithKey, mapWithKey,
traverseMaybeWithKey, traverseWithKey)
import qualified Data.Map.Strict as M
import Data.Maybe (fromMaybe)
import Data.Set (Set, (\\))
import qualified Data.Set as S
import Paths (normalise)
import qualified System.FilePath as FP
import System.FilePath.Posix (takeDirectory, (</>))
import Text.Dot (Dot, (.->.))
import qualified Text.Dot as D
import Types (Dep (LocalMap))
import Witherable (mapMaybe)
import CheckMap (MapResult (mapresultDepends))
import Data.Map.Strict (mapMaybeWithKey, mapWithKey, traverseWithKey)
import qualified Data.Map.Strict as M
import Data.Set ((\\))
import qualified Data.Set as S
import Paths (normalise)
import Text.Dot (Dot, (.->.))
import qualified Text.Dot as D
import Types (Dep (LocalMap))
-- | a simple directed graph
type Graph a = Map a (Set a)
......@@ -29,18 +24,16 @@ nodes :: Graph a -> Set a
nodes = M.keysSet
-- | simple directed graph of exits
resultToGraph :: Map FilePath MapResult -> Graph FilePath
resultToGraph = mapWithKey (\p r -> S.fromList
. mapMaybe (onlyLocalMaps (takeDirectory p))
. mapresultDepends $ r)
where onlyLocalMaps prefix = \case
LocalMap path -> Just (FP.normalise (prefix </> normalise "" path))
resultToGraph :: Map FilePath (MapResult a) -> Graph FilePath
resultToGraph = fmap (S.fromList . mapMaybe onlyLocalMaps . mapresultDepends)
where onlyLocalMaps = \case
LocalMap path -> Just (normalise "" path)
_ -> Nothing
-- | invert edges of a directed graph
invertGraph :: (Eq a, Ord a) => Graph a -> Graph a
invertGraph graph = mapWithKey collectFroms graph
where collectFroms to _ = S.fromList . M.elems . mapMaybeWithKey (select to) $ graph
where collectFroms to _ = S.fromList . elems . mapMaybeWithKey (select to) $ graph
select to from elems = if to `elem` elems then Just from else Nothing
-- | all nodes reachable from some entrypoint
......@@ -75,7 +68,7 @@ takeSubGraph i start graph
graphToDot :: Graph FilePath -> Dot ()
graphToDot graph = do
main <- D.node [("label","main.json")]
nodes' <- traverseMaybeWithKey
nodes' <- M.traverseMaybeWithKey
(\name edges -> if name /= "main.json"
then D.node [("label",name)] <&> (, edges) <&> Just
else pure Nothing
......
......@@ -2,13 +2,13 @@
module LayerData where
import Universum hiding (maximum, uncons)
import Control.Monad.Zip (mzipWith)
import Data.Set (Set, insert)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Vector (Vector, uncons)
import Tiled (GlobalId (unGlobalId), Layer (..))
import Data.Set (insert)
import Data.Tiled (GlobalId (unGlobalId), Layer (..))
import Data.Vector (maximum, uncons)
import qualified Text.Show as TS
import Util (PrettyPrint (..))
-- | A collision between two layers of the given names.
......@@ -22,8 +22,8 @@ instance Eq Collision where
instance PrettyPrint Collision where
prettyprint (Collision (a,b)) = a <> " and " <> b
instance Show Collision where
show c = T.unpack $ prettyprint c
instance TS.Show Collision where
show c = toString $ prettyprint c
-- | Finds pairwise tile collisions between the given layers.
layerOverlaps :: Vector Layer -> Set Collision
......
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
-- | Module that deals with handling config options
module LintConfig where
import Control.Monad.Identity (Identity)
import Data.Aeson (FromJSON (parseJSON), Options (..),
defaultOptions, eitherDecode)
import Data.Aeson.Types (genericParseJSON)
import qualified Data.ByteString.Char8 as C8
import qualified Data.ByteString.Lazy as LB
import qualified Data.Map.Strict as M
import Data.Text (Text)
import GHC.Generics (Generic (Rep, from, to), K1 (..),
M1 (..), (:*:) (..))
import Types (Level)
import Uris (SchemaSet,
Substitution (DomainSubstitution))
import WithCli (Proxy (..))
import WithCli.Pure (Argument (argumentType, parseArgument))
type family HKD f a where
HKD Identity a = a
HKD f a = f a
data LintConfig f = LintConfig
{ configScriptInject :: HKD f (Maybe Text)
module LintConfig (LintConfig(..), LintConfig', ConfigKind (..), patchConfig,stuffConfig,feedConfig) where
import Universum
import Data.Aeson (FromJSON (parseJSON), Options (..),
defaultOptions, eitherDecode)
import Data.Aeson.Types (genericParseJSON)
import qualified Data.ByteString.Char8 as C8
import qualified Data.ByteString.Lazy as LB
import qualified Data.Map.Strict as M
import GHC.Generics (Generic (Rep, from, to), K1 (..),
M1 (..), (:*:) (..))
import Types (Level)
import Uris (SchemaSet,
Substitution (DomainSubstitution))
import WithCli.Pure (Argument (argumentType, parseArgument))
data ConfigKind = Complete | Basic | Skeleton | Patch
-- | a field that must be given in configs for both server & standalone linter
type family ConfigField (f::ConfigKind) a where
ConfigField Patch a = Maybe a
ConfigField _ a = a
-- | a field that must be given for the standalone linter, but not the server
-- (usually because the server will infer them from its own config)
type family StandaloneField (f :: ConfigKind) a where
StandaloneField Complete a = a
StandaloneField Skeleton a = a
StandaloneField _ a = Maybe a
-- | a field specific to a single world / assembly
type family WorldField (f :: ConfigKind) a where
WorldField Complete a = a
WorldField _ a = Maybe a
data LintConfig (f :: ConfigKind) = LintConfig
{ configScriptInject :: ConfigField f (Maybe Text)
-- ^ Link to Script that should be injected
, configAssemblyTag :: HKD f Text
, configAssemblyTag :: WorldField f Text
-- ^ Assembly name (used for jitsiRoomAdminTag)
, configAssemblies :: HKD f [Text]
, configAssemblies :: StandaloneField f [Text]
-- ^ list of all assembly slugs (used to lint e.g. world:// links)
, configMaxLintLevel :: HKD f Level
, configEventSlug :: StandaloneField f Text
-- ^ slug of this event (used e.g. to resolve world:// links)
, configMaxLintLevel :: ConfigField f Level
-- ^ Maximum warn level allowed before the lint fails
, configDontCopyAssets :: HKD f Bool
, configDontCopyAssets :: ConfigField f Bool
-- ^ Don't copy map assets (mostly useful for development)
, configAllowScripts :: HKD f Bool
, configAllowScripts :: ConfigField f Bool
-- ^ Allow defining custom scripts in maps
, configUriSchemas :: HKD f SchemaSet
, configUriSchemas :: ConfigField f SchemaSet
} deriving (Generic)
type LintConfig' = LintConfig Identity
type LintConfig' = LintConfig Complete
-- TODO: should probably find a way to write these constraints nicer ...
deriving instance
( Show (HKD a (Maybe Text))
, Show (HKD a Text)
, Show (HKD a Level)
, Show (HKD a [Text])
, Show (HKD a Bool)
, Show (HKD a SchemaSet)
)
=> Show (LintConfig a)
deriving instance Show (LintConfig Complete)
deriving instance Show (LintConfig Skeleton)
deriving instance Show (LintConfig Patch)
instance NFData (LintConfig Basic)
aesonOptions :: Options
aesonOptions = defaultOptions
......@@ -68,23 +83,13 @@ aesonOptions = defaultOptions
, fieldLabelModifier = drop 6
}
instance
( FromJSON (HKD a (Maybe Text))
, FromJSON (HKD a [Text])
, FromJSON (HKD a Text)
, FromJSON (HKD a Level)
, FromJSON (HKD a Bool)
, FromJSON (HKD a SchemaSet)
)
=> FromJSON (LintConfig a)
where
parseJSON = genericParseJSON aesonOptions
instance FromJSON (LintConfig Complete) where
parseJSON = genericParseJSON aesonOptions
-- need to define this one extra, since Aeson will not make
-- Maybe fields optional if the type isn't given explicitly.
--
-- Whoever said instances had confusing semantics?
instance {-# Overlapping #-} FromJSON (LintConfig Maybe) where
instance FromJSON (LintConfig Patch) where
parseJSON = genericParseJSON aesonOptions
instance FromJSON (LintConfig Basic) where
parseJSON = genericParseJSON aesonOptions
......@@ -118,30 +123,66 @@ instance GPatch i o
-- abstract, I just wanted to play around with higher kinded types for
-- a bit.
patch ::
( Generic (f Maybe)
, Generic (f Identity)
, GPatch (Rep (f Identity))
(Rep (f Maybe))
( Generic (f Patch)
, Generic (f Complete)
, GPatch (Rep (f Complete))
(Rep (f Patch))
)
=> f Identity
-> f Maybe
-> f Identity
=> f Complete
-> f Patch
-> f Complete
patch x y = to (gappend (from x) (from y))
patchConfig :: LintConfig Identity -> Maybe (LintConfig Maybe) -> LintConfig Identity
patchConfig config p = config'
{ configUriSchemas = ("world", assemblysubsts) : configUriSchemas config'}
where config' = case p of
Just p -> patch config p
Nothing -> config
assemblysubsts =
DomainSubstitution (M.fromList generated) scope
where generated = (\slug -> (slug, "/@/rc3_21/"<>slug)) <$> configAssemblies config'
scope = (\(DomainSubstitution _ s) -> s)
. snd . head
. filter ((==) "world" . fst)
$ configUriSchemas config'
patchConfig
:: LintConfig Complete
-> Maybe (LintConfig Patch)
-> LintConfig Complete
patchConfig config p = expandWorlds config'
where
config' = case p of
Just p -> patch config p
Nothing -> config
-- | feed a basic server config
feedConfig
:: LintConfig Basic
-> [Text]
-> Text
-> LintConfig Skeleton
feedConfig LintConfig{..} worlds eventslug = expandWorlds $
LintConfig
{ configAssemblies = worlds
, configEventSlug = eventslug
, .. }
-- | stuff a
stuffConfig :: LintConfig Skeleton -> Text -> LintConfig Complete
stuffConfig LintConfig{..} assemblyslug =
LintConfig
{ configAssemblyTag = assemblyslug
, ..}
class HasWorldList (a :: ConfigKind)
instance HasWorldList 'Complete
instance HasWorldList 'Skeleton
-- kinda sad that ghc can't solve these contraints automatically,
-- though i guess it also makes sense …
expandWorlds
:: ( ConfigField a SchemaSet ~ SchemaSet
, StandaloneField a [Text] ~ [Text]
, StandaloneField a Text ~ Text
, HasWorldList a)
=> LintConfig a -> LintConfig a
expandWorlds config = config { configUriSchemas = configUriSchemas' }
where
configUriSchemas' =
M.insert "world:" [assemblysubsts] (configUriSchemas config)
assemblysubsts =
DomainSubstitution (M.fromList generated) ["map"]
where generated = configAssemblies config
<&> \slug -> (slug, "/@/"<>configEventSlug config<>"/"<>slug)
instance (FromJSON (LintConfig a)) => Argument (LintConfig a) where
parseArgument str =
......
......@@ -40,23 +40,17 @@ module LintWriter
, lintConfig
-- * adjust the linter's context
, adjust
) where
,offersCWs,resultToCWs) where
import Data.Text (Text)
import Universum
import Badges (Badge)
import Control.Monad.State (MonadState (put), StateT, modify)
import Control.Monad.Trans.Reader (Reader, asks, runReader)
import Control.Monad.Trans.State (get, runStateT)
import Control.Monad.Writer.Lazy (lift)
import Data.Bifunctor (Bifunctor (second))
import Data.Map (Map, fromListWith)
import Data.Maybe (mapMaybe)
import qualified Data.Set as S
import LintConfig (LintConfig')
import TiledAbstract (HasName (getName))
import Types (Dep, Hint, Level (..), Lint (..),
hint, lintsToHints)
import Badges (Badge)
import Data.Map (fromListWith)
import Data.Tiled.Abstract (HasName (getName))
import LintConfig (LintConfig')
import Types (Dep, Hint, Level (..), Lint (..), hint,
lintsToHints)
-- | A monad modelling the main linter features
......@@ -109,7 +103,7 @@ zoom embed extract operation = do
-- | "invert" a linter's result, grouping lints by their messages
invertLintResult :: HasName ctxt => LintResult ctxt -> Map Hint [Text]
invertLintResult (LinterState (lints, ctxt)) =
fmap (S.toList . S.fromList . fmap getName) . fromListWith (<>) $ (, [ctxt]) <$> lintsToHints lints
fmap (sortNub . map getName) . fromListWith (<>) $ (, [ctxt]) <$> lintsToHints lints
resultToDeps :: LintResult a -> [Dep]
resultToDeps (LinterState (lints,_)) = mapMaybe lintToDep lints
......@@ -128,6 +122,11 @@ resultToBadges (LinterState a) = mapMaybe lintToBadge $ fst a
where lintToBadge (Badge badge) = Just badge
lintToBadge _ = Nothing
resultToCWs :: LintResult a -> [Text]
resultToCWs (LinterState a) = fold $ mapMaybe lintToCW $ fst a
where lintToCW = \case (CW cw) -> Just cw; _ -> Nothing
-- | convert a lint result into a flat list of lints
resultToLints :: LintResult a -> [Lint]
resultToLints (LinterState res) = fst res
......@@ -158,6 +157,8 @@ offersEntrypoint text = tell' $ Offers text
offersBadge :: Badge -> LintWriter a
offersBadge badge = tell' $ Badge badge
offersCWs :: [Text] -> LintWriter a
offersCWs = tell' . CW
-- | get the context as it was initially, without any modifications
......
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
......@@ -5,18 +7,21 @@
-- I just hope you are running this on some kind of Unix
module Paths where
import Data.Text (Text, isPrefixOf)
import Universum
import qualified Universum.Unsafe as Unsafe
import qualified Data.Text as T
import System.FilePath (splitPath)
import System.FilePath.Posix ((</>))
import Text.Regex.TDFA
import Util (PrettyPrint (prettyprint))
-- | a normalised path: a number of "upwards" steps, and
-- a path without any . or .. in it. Also possibly a
-- fragment, mostly for map links.
data RelPath = Path Int Text (Maybe Text)
deriving (Show, Eq, Ord)
deriving (Show, Eq, Ord, NFData, Generic)
......@@ -32,9 +37,9 @@ parsePath :: Text -> PathResult
parsePath text =
if | T.isInfixOf "{{" text || T.isInfixOf "}}" text -> PathVarsDisallowed
| rest =~ ("^([^/]*[^\\./]/)*[^/]*[^\\./]$" :: Text) -> OkRelPath (Path up path fragment)
| "/_/" `isPrefixOf` text -> UnderscoreMapLink
| "/@/" `isPrefixOf` text -> AtMapLink
| "/" `isPrefixOf` text -> AbsolutePath
| "/_/" `T.isPrefixOf` text -> UnderscoreMapLink
| "/@/" `T.isPrefixOf` text -> AtMapLink
| "/" `T.isPrefixOf` text -> AbsolutePath
| otherwise -> NotAPath
where
(_, prefix, rest, _) =
......@@ -43,10 +48,10 @@ parsePath text =
up = length . filter (".." ==) . T.splitOn "/" $ prefix
parts = T.splitOn "#" rest
-- `head` is unsafe, but splitOn will always produce lists with at least one element
path = head parts
fragment = if length parts >= 2
then Just $ T.concat $ tail parts
else Nothing
path = Unsafe.head parts
fragment = case nonEmpty parts of
Nothing -> Nothing
Just p -> Just $ T.concat $ tail p
instance PrettyPrint RelPath where
prettyprint (Path up rest frag) = ups <> rest <> fragment
......@@ -59,14 +64,14 @@ instance PrettyPrint RelPath where
-- at the end of the prefix, i.e. it will never return paths
-- that lie (naïvely) outside of the prefix.
normalise :: FilePath -> RelPath -> FilePath
normalise prefix (Path 0 path _) = prefix </> T.unpack path
normalise prefix (Path 0 path _) = prefix </> toString path
normalise prefix (Path i path _) =
concat (take (length dirs - i) dirs) </> T.unpack path
concat (take (length dirs - i) dirs) </> toString path
where dirs = splitPath prefix
normaliseWithFrag :: FilePath -> RelPath -> FilePath
normaliseWithFrag prefix (Path i path frag) =
normalise prefix (Path (i+1) path frag) <> T.unpack (maybe mempty ("#" <>) frag)
normalise prefix (Path (i+1) path frag) <> toString (maybe mempty ("#" <>) frag)
-- | does this path contain an old-style pattern for inter-repository
-- links as was used at rc3 in 2020?
......@@ -77,7 +82,5 @@ isOldStyle (Path _ text frag) = path =~ ("{<.+>*}" :: Text)
_ -> text
getExtension :: RelPath -> Text
getExtension (Path _ text _) = case length splitted of
0 -> ""
_ -> last splitted
getExtension (Path _ text _) = maybe "" last (nonEmpty splitted)
where splitted = T.splitOn "." text
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
-- | Contains checks for custom ties of the map json
module Properties (checkMap, checkTileset, checkLayer) where
import Control.Monad (forM, forM_, unless, when)
import Data.Text (Text, intercalate, isInfixOf, isPrefixOf)
import qualified Data.Text as T
import qualified Data.Vector as V
import Tiled (Layer (..), Object (..), Property (..),
PropertyValue (..), Tile (..),
Tiledmap (..), Tileset (..))
import TiledAbstract (HasData (..), HasName (..),
HasProperties (..), HasTypeName (..),
IsProperty (..), layerIsEmpty)
import Util (mkProxy, naiveEscapeHTML, prettyprint,
showText)
import Badges (Badge (Badge),
BadgeArea (BadgePoint, BadgeRect),
BadgeToken, parseToken)
import Data.Data (Proxy (Proxy))
import Data.Functor ((<&>))
import Data.List ((\\))
import Data.Maybe (fromMaybe, isJust)
import Data.Set (Set)
import qualified Data.Set as S
import Data.Text.Metrics (damerauLevenshtein)
import Data.Vector (Vector)
import GHC.TypeLits (KnownSymbol)
import LayerData (Collision, layerOverlaps)
import LintConfig (LintConfig (..))
import LintWriter (LintWriter, adjust, askContext,
askFileDepth, complain, dependsOn, forbid,
lintConfig, offersBadge, offersEntrypoint,
suggest, warn, zoom)
import Paths (PathResult (..), RelPath (..), getExtension,
isOldStyle, parsePath)
import Types (Dep (Link, Local, LocalMap, MapLink))
import Uris (SubstError (..), applySubsts, parseUri)
import Universum hiding (intercalate, isPrefixOf)
import Data.Text (intercalate, isPrefixOf)
import qualified Data.Text as T
import Data.Tiled (Layer (..), Object (..), Property (..),
PropertyValue (..), Tile (..),
Tiledmap (..), Tileset (..))
import Data.Tiled.Abstract (HasData (..), HasName (..),
HasProperties (..), HasTypeName (..),
IsProperty (..), layerIsEmpty)
import qualified Data.Vector as V
import Util (mkProxy, naiveEscapeHTML, prettyprint)
import Badges (Badge (Badge),
BadgeArea (BadgePoint, BadgeRect),
BadgeToken, parseToken)
import Data.List ((\\))
import qualified Data.Set as S
import Data.Text.Metrics (damerauLevenshtein)
import GHC.TypeLits (KnownSymbol)
import LayerData (Collision, layerOverlaps)
import LintConfig (LintConfig (..))
import LintWriter (LintWriter, adjust, askContext,
askFileDepth, complain, dependsOn, forbid,
lintConfig, offersBadge, offersEntrypoint,
suggest, warn, zoom, offersCWs)
import Paths (PathResult (..), RelPath (..),
getExtension, isOldStyle, parsePath)
import Types (Dep (Link, Local, LocalMap, MapLink))
import Uris (SubstError (..), applySubsts)
knownMapProperties :: Vector Text
knownMapProperties = V.fromList
[ "mapName", "mapDescription", "mapCopyright", "mapLink", "script" ]
[ "mapName", "mapDescription", "mapCopyright", "mapLink", "script"
, "contentWarnings" ]
knownTilesetProperties :: Vector Text
knownTilesetProperties = V.fromList
......@@ -65,8 +62,8 @@ knownObjectProperties = V.fromList
knownTileLayerProperites :: Vector Text
knownTileLayerProperites = V.fromList
[ "jitsiRoom", "jitsiTrigger", "jitsiTriggerMessage", "jitsiWidth", "bbbRoom"
, "bbbTrigger", "bbbTriggerMessage", "playAudio", "audioLoop", "audioVolumne"
[ "jitsiRoom", "jitsiTrigger", "jitsiTriggerMessage", "jitsiWidth"
, "playAudio", "audioLoop", "audioVolumne"
, "openWebsite", "openWebsiteTrigger", "openWebsiteTriggerMessage", "openTag"
, "exitUrl", "startLayer", "silent", "getBadge", "zone", "name", "doorVariable"
, "bindVariable", "bellVariable", "code", "openTriggerMessage"
......@@ -85,7 +82,7 @@ checkMap = do
let unlessLayer = unlessElement layers
-- test custom map properties
mapM_ checkMapProperty (fromMaybe mempty $ tiledmapProperties tiledmap)
mapM_ checkMapProperty (maybeToMonoid $ tiledmapProperties tiledmap)
-- can't have these with the rest of layer/tileset lints since they're
-- not specific to any one of them
......@@ -110,13 +107,16 @@ checkMap = do
unlessHasProperty "mapCopyright"
$ suggest "document the map's copyright via the \"mapCopyright\" property."
unlessHasProperty "contentWarnings"
$ suggest "set content warnings for your map via the \"contentWarnings\" property."
-- TODO: this doesn't catch collisions with the default start layer!
whenLayerCollisions layers (\(Property name _) -> name == "exitUrl" || name == "startLayer")
$ \cols -> warn $ "collisions between entry and / or exit layers: " <> prettyprint cols
let missingMetaInfo =
["mapName","mapDescription","mapLink"]
\\ fmap getName (getProperties tiledmap)
\\ map getName (getProperties tiledmap)
unless (null missingMetaInfo)
$ suggest $ "consider adding meta information to your map using the "
......@@ -138,25 +138,25 @@ checkMapProperty p@(Property name _) = case name of
"mapDescription" -> naiveEscapeProperty p
"mapCopyright" -> naiveEscapeProperty p
"mapLink" -> pure ()
"contentWarnings" ->
unwrapString p $ \str -> do
offersCWs (T.splitOn "," str)
-- usually the linter will complain if names aren't in their
-- "canonical" form, but allowing that here so that multiple
-- scripts can be used by one map
_ | T.toLower name == "script" ->
unwrapString p $ \str ->
unless (("https://static.rc3.world/scripts" `isPrefixOf` str) &&
(not $ "/../" `isInfixOf` str) &&
(not $ "%" `isInfixOf` str) &&
(not $ "@" `isInfixOf` str))
$ forbid "only scripts hosted on static.rc3.world are allowed."
| name `elem` ["jitsiRoom", "bbbRoom", "playAudio", "openWebsite"
unwrapURI (Proxy @"script") p
(dependsOn . Link)
(const $ forbid "scripts loaded from local files are disallowed")
| name `elem` ["jitsiRoom", "playAudio", "openWebsite"
, "url", "exitUrl", "silent", "getBadge"]
-> complain $ "property " <> name
-> complain $ "property " <> name
<> " should be set on layers, not the map directly"
| otherwise
-> warnUnknown p knownMapProperties
-> warnUnknown p knownMapProperties
-- | check an embedded tile set.
-- | check an embedded tileset.
--
-- Important to collect dependency files
checkTileset :: LintWriter Tileset
......@@ -184,21 +184,22 @@ checkTileset = do
when (isJust (tilesetFileName tileset))
$ complain "The \"filename\" property on tilesets was removed; use \"image\" instead (and perhaps a newer version of the Tiled Editor)."
-- check properties of individual tiles
tiles' <- forM (tilesetTiles tileset) $ mapM $ \tile -> do
mapM_ (checkTileProperty tile) (getProperties tile)
zoom (const tileset) (const tile) $ mapM_ checkTileThing' (getProperties tile)
zoom (const tileset) (const tile) $ mapM_ (checkTileThing True) (getProperties tile)
adjust (\t -> t { tilesetTiles = tiles' })
-- check individual tileset properties
mapM_ checkTilesetProperty (fromMaybe mempty $ tilesetProperties tileset)
mapM_ checkTilesetProperty (maybeToMonoid $ tilesetProperties tileset)
case tilesetTiles tileset of
Nothing -> pure ()
Just tiles -> refuseDoubledThings tileId
Just tiles -> ifDoubledThings tileId
-- can't set properties on the same tile twice
(\tile -> complain $ "cannot set properties on the \
\tile with the id" <> showText (tileId tile) <> "twice.")
\tile with the id" <> show (tileId tile) <> "twice.")
tiles
where
......@@ -208,9 +209,6 @@ checkTileset = do
"collides" -> warn "property \"collides\" should be set on individual tiles, not the tileset"
_ -> warn $ "unknown tileset property " <> prettyprint name
checkTileThing' :: Property -> LintWriter Tile
checkTileThing' = checkTileThing True
checkTileProperty :: Tile -> Property -> LintWriter Tileset
checkTileProperty tile p@(Property name _) =
case name of
......@@ -219,10 +217,9 @@ checkTileset = do
"name" -> isString p
"tilesetCopyright" -> warn "the \"tilesetCopyright\" property should be set on the entire tileset, \
\not an individual tile."
-- _ -> warnUnknown' ("unknown tile property "
-- <> prettyprint name <> " in tile with global id "
-- <> showText (tileId tile)) p knownTilesetProperties
_ -> pure ()
_ -> warnUnknown' ("unknown tile property "
<> prettyprint name <> " in tile with global id "
<> show (tileId tile)) p knownTilesetProperties
-- | collect lints on a single map layer
......@@ -247,7 +244,7 @@ checkLayer = do
adjust (\l -> l { layerObjects = objs })
-- all objects which don't define badges
let publicObjects = fmap (V.filter (not . (`containsProperty` "getBadge"))) objs
let publicObjects = map (V.filter (not . (`containsProperty` "getBadge"))) objs
-- remove badges from output
adjust $ \l -> l { layerObjects = publicObjects
......@@ -257,14 +254,14 @@ checkLayer = do
forM_ (getProperties layer) checkObjectGroupProperty
unless (layerName layer == "floorLayer") $
when (null (layerObjects layer) || layerObjects layer == Just mempty) $
when (isNothing (layerObjects layer) || layerObjects layer == Just mempty) $
warn "objectgroup layer (which aren't the floorLayer) \
\are useless if they are empty."
ty -> complain $ "unsupported layer type " <> prettyprint ty <> "."
if layerType layer == "group"
then when (null (layerLayers layer))
then when (isNothing (layerLayers layer))
$ warn "Empty group layers are pointless."
else when (isJust (layerLayers layer))
$ complain "Layer is not of type \"group\", but has sublayers."
......@@ -315,7 +312,7 @@ checkObjectProperty p@(Property name _) = do
unless (objectType obj == "variable") $
complain $ "the "<>prettyprint name<>" property should only be set \
\on objects of type \"variable\""
when (null (objectName obj) || objectName obj == Just mempty) $
when (isNothing (objectName obj) || objectName obj == Just mempty) $
complain $ "Objects with the property "<>prettyprint name<>" set must \
\be named."
| name `elem` [ "openSound", "closeSound", "bellSound", "loadSound" ] -> do
......@@ -332,7 +329,7 @@ checkObjectProperty p@(Property name _) = do
suggestPropertyName' "door"
suggestPropertyName "soundRadius"
"set \"soundRadius\" to limit the door sound to a certain area."
| T.toLower name `elem` [ "allowapi" ]
| T.toLower name == "allowapi"
-> forbidProperty name
| otherwise ->
warnUnknown p knownObjectProperties
......@@ -345,18 +342,27 @@ checkObjectGroupProperty (Property name _) = case name of
_ -> warn $ "unknown property " <> prettyprint name <> " for objectgroup layers"
-- | Checks a single (custom) property of a "normal" tile layer
checkTileThing :: (HasProperties a, HasName a, HasData a) => Bool -> Property -> LintWriter a
-- | Checks a single (custom) property. Since almost all properties
-- can be set on tile layer AND on tilesets, this function aims to
-- be generic over both — the only difference is that tilesets can't
-- have exits, which is specified by the sole boolean argument
checkTileThing
:: (HasProperties a, HasName a, HasData a)
=> Bool -> Property -> LintWriter a
checkTileThing removeExits p@(Property name _value) = case name of
"jitsiRoom" -> do
uselessEmptyLayer
-- members of an assembly should automatically get
-- admin rights in jitsi (prepending "assembly-" here
-- to avoid namespace clashes with other admins)
lintConfig configAssemblyTag
>>= setProperty "jitsiRoomAdminTag"
. ("assembly-" <>) -- prepend "assembly-" to avoid namespace clashes
uselessEmptyLayer
. ("assembly-" <>)
unwrapString p $ \jitsiRoom -> do
suggestProperty $ Property "jitsiTrigger" "onaction"
-- prepend jitsi room names to avoid name clashes
-- prevents namespace clashes for jitsi room names
unless ("shared" `isPrefixOf` jitsiRoom) $ do
assemblyname <- lintConfig configAssemblyTag
setProperty "jitsiRoom" (assemblyname <> "-" <> jitsiRoom)
......@@ -371,34 +377,6 @@ checkTileThing removeExits p@(Property name _value) = case name of
requireProperty "jitsiTrigger"
"jitsiWidth" ->
isIntInRange 0 100 p
"bbbRoom" -> do
removeProperty "bbbRoom"
unwrapString p $ \str -> case parseUri str of
Just ("bbb",assembly_slug, room_slug)
| "/" `isPrefixOf` room_slug
&& T.length room_slug >= 2 -> do
let link = "https://rc3.world/2021/room"<>room_slug
dependsOn (Link link)
setProperty "openTab" link
setProperty "silent" (BoolProp True)
setProperty "openWebsitePolicy"
("fullscreen;camera;microphone;display-capture" :: Text)
_ -> complain "the \"bbbRoom\" property must take a link of the form bbb://assembly_slug/room_slug."
"bbbTrigger" -> do
removeProperty "bbbTrigger"
requireProperty "bbbRoom"
unwrapString p
(setProperty "openWebsiteTrigger")
unlessHasProperty "bbbTriggerMessage" $ do
setProperty "openWebsiteTriggerMessage"
("press SPACE to enter bbb room in a new tab" :: Text)
suggest "set \"bbbTriggerMessage\" to a custom message to overwrite the\
\default \"press SPACE to enter the bbb room\""
"bbbTriggerMessage" -> do
removeProperty "bbbTriggerMessage"
requireProperty "bbbRoom"
unwrapString p
(setProperty "openWebsiteTriggerMessage")
"playAudio" -> do
uselessEmptyLayer
unwrapURI (Proxy @"audio") p
......@@ -429,7 +407,8 @@ checkTileThing removeExits p@(Property name _value) = case name of
unwrapURI (Proxy @"map") p
(\link -> do
assemblyslug <- lintConfig configAssemblyTag
case T.stripPrefix ("/@/rc3_21/"<>assemblyslug<>"/") link of
eventslug <- lintConfig configEventSlug
case T.stripPrefix ("/@/"<>eventslug<>"/"<>assemblyslug<>"/") link of
Nothing -> do
dependsOn (MapLink link)
setProperty "exitUrl" link
......@@ -444,21 +423,23 @@ checkTileThing removeExits p@(Property name _value) = case name of
_ -> complain "There's a path I don't understand here. Perhaps try \
\asking a human?"
)
$ \path ->
( \path ->
let ext = getExtension path in
if | isOldStyle path ->
complain "Old-Style inter-repository links (using {<placeholder>}) \
\cannot be used at rC3 2021; please use world:// instead \
\(see howto.rc3.world)."
if | isOldStyle path -> do
eventslug <- lintConfig configEventSlug
complain $
"Old-Style inter-repository links (using {<placeholder>}) \
\cannot be used at "<>eventslug<>"; please use world:// \
\instead (see https://di.c3voc.de/howto:world)."
| ext == "tmx" ->
complain "Cannot use .tmx map format; use Tiled's json export instead."
| ext /= "json" ->
complain "All exit links must link to .json files."
| otherwise -> dependsOn . LocalMap $ path
)
else do
removeProperty "exitUrl"
warn "exitUrls in Tilesets are not properly supported; if you want to add an \
\exit, please use a tile layer instead."
warn "exitUrls in Tilesets are not unsupported; if you want to \
\add an exit, please use a tile layer instead."
"exitSceneUrl" ->
deprecatedUseInstead "exitUrl"
"exitInstance" ->
......@@ -495,15 +476,21 @@ checkTileThing removeExits p@(Property name _value) = case name of
, "jitsiroomadmintag", "jitsiinterfaceconfig"
, "openwebsitepolicy", "allowapi" ]
-> forbidProperty name
-- the openWebsite Api can only be allowed if the website is on static.rc3.world
| T.toLower name == "openwebsiteallowapi"
-> forbid "\"openWebsiteAllowApi\" is disallowed."
| name `elem` [ "openWebsite", "openTab" ] -> do
uselessEmptyLayer
suggestProperty $ Property "openWebsiteTrigger" (StrProp "onaction")
unwrapURI (Proxy @"website") p
(dependsOn . Link)
(const $ forbid "accessing local html files is disallowed.")
suggestProperty $ Property "openWebsiteTrigger" "onaction"
properties <- askContext <&> getProperties
let isScript = any (\(Property name _) ->
T.toLower name == "openwebsiteallowapi")
properties
if isScript
then unwrapURI (Proxy @"script") p
(dependsOn . Link)
(const $ forbid "accessing local html files is disallowed")
else unwrapURI (Proxy @"website") p
(dependsOn . Link)
(const $ forbid "accessing local html files is disallowed.")
| otherwise ->
when (not removeExits || name `notElem` [ "collides", "name", "tilesetCopyright" ]) $ do
warnUnknown p knownTileLayerProperites
......@@ -511,7 +498,7 @@ checkTileThing removeExits p@(Property name _value) = case name of
requireProperty req = propertyRequiredBy req name
requireOneOf names = do
context <- askContext
when (all (not . containsProperty context) names)
unless (any (containsProperty context) names)
$ complain $ "property " <> prettyprint name <> " requires one of "
<> prettyprint names
......@@ -535,23 +522,22 @@ checkTileThing removeExits p@(Property name _value) = case name of
-- | refuse doubled names in everything that's somehow a collection of names
refuseDoubledNames
:: (HasName a, HasTypeName a)
=> (Foldable t, Functor t)
=> t a
:: (Container t, HasName (Element t), HasTypeName (Element t))
=> t
-> LintWriter b
refuseDoubledNames = refuseDoubledThings
getName
refuseDoubledNames = ifDoubledThings getName
(\thing -> complain $ "cannot use " <> typeName (mkProxy thing) <> " name "
<> getName thing <> " multiple times.")
-- | refuse doubled things via equality on after applying some function
refuseDoubledThings
:: (Eq a, Ord a, Foldable t, Functor t)
=> (a' -> a)
-> (a' -> LintWriter b)
-> t a'
-- | do `ifDouble` if any element of `things` occurs more than once under
-- the function `f`
ifDoubledThings
:: (Eq a, Ord a, Container t)
=> (Element t -> a)
-> (Element t -> LintWriter b)
-> t
-> LintWriter b
refuseDoubledThings f ifDouble things = foldr folding base things (mempty, mempty)
ifDoubledThings f ifDouble things = foldr folding base things (mempty, mempty)
where
folding thing cont (seen, twice)
| f thing `elem` seen && f thing `notElem` twice = do
......@@ -561,7 +547,7 @@ refuseDoubledThings f ifDouble things = foldr folding base things (mempty, mempt
cont (S.insert (f thing) seen, twice)
base _ = pure ()
-- | we don't know this property; give suggestions for ones with similar names
warnUnknown' :: Text -> Property -> Vector Text -> LintWriter a
warnUnknown' msg (Property name _) knowns =
if snd minDist < 4
......@@ -577,15 +563,15 @@ warnUnknown p@(Property name _) =
---- General functions ----
unlessElement
:: Foldable f
=> f a
-> (a -> Bool)
:: Container f
=> f
-> (Element f -> Bool)
-> LintWriter b
-> LintWriter b
unlessElement things op = unless (any op things)
unlessElementNamed :: (HasName a, Foldable f)
=> f a -> Text -> LintWriter b -> LintWriter b
unlessElementNamed :: (HasName (Element f), Container f)
=> f -> Text -> LintWriter b -> LintWriter b
unlessElementNamed things name =
unlessElement things ((==) name . getName)
......@@ -652,11 +638,6 @@ setProperty name value = adjust $ \ctxt ->
$ \ps -> Just $ Property name (asProperty value) : filter sameName ps
where sameName (Property name' _) = name /= name'
removeProperty :: HasProperties ctxt => Text -> LintWriter ctxt
removeProperty name = adjust $ \ctxt ->
flip adjustProperties ctxt
$ \ps -> Just $ filter (\(Property name' _) -> name' /= name) ps
naiveEscapeProperty :: HasProperties a => Property -> LintWriter a
naiveEscapeProperty prop@(Property name _) =
unwrapString prop (setProperty name . naiveEscapeHTML)
......@@ -709,7 +690,9 @@ unwrapBadgeToken str f = case parseToken str of
Nothing -> complain "invalid badge token."
-- | unwraps a URI
-- | unwraps a link, giving two cases:
-- - the link might be an (allowed) remote URI
-- - the link might be relative to this map (i.e. just a filepath)
unwrapURI :: (KnownSymbol s, HasProperties a)
=> Proxy s
-> Property
......@@ -733,12 +716,12 @@ unwrapURI sym p@(Property name _) f g = unwrapString p $ \link -> do
DomainDoesNotExist domain -> "The domain " <> domain <> " does not exist; \
\please make sure it is spelled correctly."
SchemaDoesNotExist schema ->
"the URI schema " <> schema <> ":// cannot be used."
"the URI schema " <> schema <> "// cannot be used."
WrongScope schema allowed ->
"the URI schema " <> schema <> ":// cannot be used in property \
"the URI schema " <> schema <> "// cannot be used in property \
\\"" <> name <> "\"; allowed "
<> (if length allowed == 1 then "is " else "are ")
<> intercalate ", " (fmap (<> "://") allowed) <> "."
<> intercalate ", " (map (<> "//") allowed) <> "."
VarsDisallowed -> "extended API links are disallowed in links"
......@@ -763,4 +746,4 @@ isOrdInRange :: (Ord a, Show a)
isOrdInRange unwrapa l r p@(Property name _) = unwrapa p $ \int ->
if l < int && int < r then pure ()
else complain $ "Property " <> prettyprint name <> " should be between "
<> showText l <> " and " <> showText r<>"."
<> show l <> " and " <> show r<>"."