77
88module Xrefcheck.Config where
99
10+ import qualified Unsafe
11+
12+ import Control.Exception (assert )
1013import Control.Lens (makeLensesWith )
1114import Data.Aeson.TH (deriveFromJSON )
15+ import qualified Data.ByteString as BS
16+ import qualified Data.Map as Map
1217import Data.Yaml (FromJSON (.. ), decodeEither' , prettyPrintParseException , withText )
1318import Instances.TH.Lift ()
1419import Text.Regex.TDFA (CompOption (.. ), ExecOption (.. ), Regex )
15- import Text.Regex.TDFA.Text (compile )
20+ import qualified Text.Regex.TDFA as R
21+ import Text.Regex.TDFA.ByteString ()
22+ import qualified Text.Regex.TDFA.Text as R
1623
1724-- FIXME: Use </> from System.FilePath
1825-- </> from Posix is used only because we cross-compile to Windows and \ doesn't work on Linux
1926import Data.FileEmbed (embedFile )
2027import System.FilePath.Posix ((</>) )
2128import Time (KnownRatName , Second , Time , unitsP )
2229
30+ import Xrefcheck.Core
2331import Xrefcheck.Scan
2432import Xrefcheck.Scanners.Markdown
2533import Xrefcheck.System (RelGlobPattern )
26- import Xrefcheck.Util (aesonConfigOption , postfixFields )
34+ import Xrefcheck.Util (aesonConfigOption , postfixFields , (-:) )
2735
2836-- | Overall config.
2937data Config = Config
@@ -56,18 +64,119 @@ makeLensesWith postfixFields ''VerifyConfig
5664-- Default config
5765-----------------------------------------------------------
5866
67+ defConfigUnfilled :: ByteString
68+ defConfigUnfilled =
69+ $ (embedFile (" src-files" </> " def-config.yaml" ))
70+
71+ -- | Picks raw config with @:PLACEHOLDER:<key>:@ and fills the specified fields
72+ -- in it, picking a replacement suitable for the given key. Only strings and lists
73+ -- of strings can be filled this way.
74+ --
75+ -- This will fail if any placeholder is left unreplaced, however extra keys in
76+ -- the provided replacement won't cause any warnings or failures.
77+ fillHoles
78+ :: HasCallStack
79+ => [(ByteString , Either ByteString [ByteString ])] -> ByteString -> ByteString
80+ fillHoles allReplacements rawConfig =
81+ let holesLocs = R. getAllMatches $ holeLineRegex `R.match` rawConfig
82+ in mconcat $ replaceHoles 0 holesLocs
83+ where
84+ showBs :: ByteString -> Text
85+ showBs = show @ _ @ Text . decodeUtf8
86+
87+ holeLineRegex :: R. Regex
88+ holeLineRegex = R. makeRegex (" [ -]+:PLACEHOLDER:[^:]+:" :: Text )
89+
90+ replacementsMap = Map. fromList allReplacements
91+ getReplacement key =
92+ Map. lookup key replacementsMap
93+ ?: error (" Replacement for key " <> showBs key <> " is not specified" )
94+
95+ pickConfigSubstring :: Int -> Int -> ByteString
96+ pickConfigSubstring from len = BS. take len $ BS. drop from rawConfig
97+
98+ replaceHoles :: Int -> [(R. MatchOffset , R. MatchLength )] -> [ByteString ]
99+ replaceHoles processedLen [] = one $ BS. drop processedLen rawConfig
100+ replaceHoles processedLen ((off, len) : locs) =
101+ -- in our case matches here should not overlap
102+ assert (off > processedLen) $
103+ pickConfigSubstring processedLen (off - processedLen) :
104+ replaceHole (pickConfigSubstring off len) ++
105+ replaceHoles (off + len) locs
106+
107+ holeItemRegex :: R. Regex
108+ holeItemRegex = R. makeRegex (" (^|:)([ ]*):PLACEHOLDER:([^:]+):" :: Text )
109+
110+ holeListRegex :: R. Regex
111+ holeListRegex = R. makeRegex (" ^([ ]*-[ ]*):PLACEHOLDER:([^:]+):" :: Text )
112+
113+ replaceHole :: ByteString -> [ByteString ]
114+ replaceHole holeLine = if
115+ | Just [_wholeMatch, _beginning, leadingSpaces, key] <-
116+ R. getAllTextSubmatches <$> (holeItemRegex `R.matchM` holeLine) ->
117+ case getReplacement key of
118+ Left replacement -> [leadingSpaces, replacement]
119+ Right _ -> error $
120+ " Key " <> showBs key <> " requires replacement with an item, \
121+ \but list was given"
122+
123+ | Just [_wholeMatch, leadingChars, key] <-
124+ R. getAllTextSubmatches <$> (holeListRegex `R.matchM` holeLine) ->
125+ case getReplacement key of
126+ Left _ -> error $
127+ " Key " <> showBs key <> " requires replacement with a list, \
128+ \but an item was given"
129+ Right [] ->
130+ [" []" ]
131+ Right replacements@ (_ : _) ->
132+ Unsafe. init $ do
133+ replacement <- replacements
134+ [leadingChars, replacement, " \n " ]
135+
136+ | otherwise ->
137+ error $ " Unrecognized placeholder pattern " <> showBs holeLine
138+
59139-- | Default config in textual representation.
60140--
61141-- Sometimes you cannot just use 'defConfig' because clarifying comments
62142-- would be lost.
63- defConfigText :: ByteString
64- defConfigText =
65- $ (embedFile (" src-files" </> " def-config.yaml" ))
66-
67- defConfig :: HasCallStack => Config
68- defConfig =
143+ defConfigText :: Flavor -> ByteString
144+ defConfigText flavor =
145+ flip fillHoles defConfigUnfilled
146+ [
147+ " flavor" -: Left (show flavor)
148+
149+ , " notScanned" -: Right $ case flavor of
150+ GitHub ->
151+ [ " .github/pull_request_template.md"
152+ , " .github/issue_template.md"
153+ , " .github/PULL_REQUEST_TEMPLATE"
154+ , " .github/ISSUE_TEMPLATE"
155+ ]
156+ GitLab ->
157+ [ " .gitlab/merge_request_templates/"
158+ , " .gitlab/issue_templates/"
159+ ]
160+
161+ , " virtualFiles" -: Right $ case flavor of
162+ GitHub ->
163+ [ " ../../../issues"
164+ , " ../../../issues/*"
165+ , " ../../../pulls"
166+ , " ../../../pulls/*"
167+ ]
168+ GitLab ->
169+ [ " ../../issues"
170+ , " ../../issues/*"
171+ , " ../../merge_requests"
172+ , " ../../merge_requests/*"
173+ ]
174+ ]
175+
176+ defConfig :: HasCallStack => Flavor -> Config
177+ defConfig flavor =
69178 either (error . toText . prettyPrintParseException) id $
70- decodeEither' defConfigText
179+ decodeEither' ( defConfigText flavor)
71180
72181-----------------------------------------------------------
73182-- Yaml instances
@@ -84,7 +193,7 @@ instance KnownRatName unit => FromJSON (Time unit) where
84193instance FromJSON Regex where
85194 parseJSON = withText " regex" $ \ val -> do
86195 let errOrRegex =
87- compile defaultCompOption defaultExecOption val
196+ R. compile defaultCompOption defaultExecOption val
88197 either (error . show ) return errOrRegex
89198
90199-- Default boolean values according to
0 commit comments