{-# Language PatternSynonyms #-} {-# Language ViewPatterns #-} module Fixme.Run.Internal where import Prelude hiding (init) import Fixme.Prelude hiding (indent) import Fixme.Types import Fixme.Config import Fixme.State import Fixme.Scan.Git.Local as Git import Fixme.Scan as Scan import Fixme.Log import HBS2.Git.Local.CLI import HBS2.Base58 import HBS2.Merkle import HBS2.Data.Types.Refs import HBS2.Storage import HBS2.Storage.Compact import HBS2.System.Dir import DBPipe.SQLite hiding (field) import Data.Config.Suckless import Data.Config.Suckless.Script.File import Control.Applicative import Data.Aeson.Encode.Pretty as Aeson import Data.ByteString (ByteString) import Data.ByteString.Lazy qualified as LBS import Data.ByteString.Lazy.Char8 qualified as LBS8 import Data.Either import Data.Maybe import Data.HashSet qualified as HS import Data.HashMap.Strict (HashMap) import Data.HashMap.Strict qualified as HM import Data.HashSet (HashSet) import Data.Set qualified as Set import Data.Generics.Product.Fields (field) import Data.List qualified as List import Data.Text qualified as Text import Data.Text.IO qualified as Text import Data.Text.Encoding (encodeUtf8) import Text.InterpolatedString.Perl6 (qc) import Data.Coerce import Control.Monad.Identity import Lens.Micro.Platform import System.Process.Typed import Control.Monad.Trans.Cont import Control.Monad.Trans.Maybe import System.IO.Temp as Temp import System.IO qualified as IO import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds) import System.Directory (getModificationTime) import Streaming.Prelude qualified as S pattern IsSimpleTemplate :: forall {c} . [Syntax c] -> Syntax c pattern IsSimpleTemplate xs <- ListVal (SymbolVal "simple" : xs) {- HLINT ignore "Functor law" -} defaultTemplate :: HashMap Id FixmeTemplate defaultTemplate = HM.fromList [ ("default", Simple (SimpleTemplate short)) ] where short = parseTop s & fromRight mempty s = [qc| (trim 10 $fixme-key) " " (align 6 $fixme-tag) " " (trim 50 ($fixme-title)) (nl) |] init :: FixmePerks m => FixmeM m () init = do lo <- localConfigDir let lo0 = takeFileName lo mkdir lo touch (lo "config") let gitignore = lo ".gitignore" here <- doesPathExist gitignore unless here do liftIO $ writeFile gitignore $ show $ vcat [ pretty ("." localDBName) ] notice $ yellow "run" <> line <> vcat [ "git add" <+> pretty (lo0 ".gitignore") , "git add" <+> pretty (lo0 "config") ] printEnv :: FixmePerks m => FixmeM m () printEnv = do g <- asks fixmeEnvGitDir >>= readTVarIO masks <- asks fixmeEnvFileMask >>= readTVarIO excl <- asks fixmeEnvFileExclude >>= readTVarIO tags <- asks fixmeEnvTags >>= readTVarIO days <- asks fixmeEnvGitScanDays >>= readTVarIO comments1 <- asks fixmeEnvDefComments >>= readTVarIO <&> HS.toList comments2 <- asks fixmeEnvFileComments >>= readTVarIO <&> HM.toList <&> fmap (over _2 HS.toList) attr <- asks fixmeEnvAttribs >>= readTVarIO <&> HS.toList vals <- asks fixmeEnvAttribValues >>= readTVarIO <&> HM.toList for_ tags $ \m -> do liftIO $ print $ "fixme-prefix" <+> pretty m for_ masks $ \m -> do liftIO $ print $ "fixme-files" <+> dquotes (pretty m) for_ excl $ \m -> do liftIO $ print $ "fixme-exclude" <+> dquotes (pretty m) for_ days $ \d -> do liftIO $ print $ "fixme-git-scan-filter-days" <+> pretty d for_ comments1 $ \d -> do liftIO $ print $ "fixme-comments" <+> dquotes (pretty d) for_ comments2 $ \(ft, comm') -> do for_ comm' $ \comm -> do liftIO $ print $ "fixme-file-comments" <+> dquotes (pretty ft) <+> dquotes (pretty comm) for_ attr $ \a -> do liftIO $ print $ "fixme-attribs" <+> pretty a for_ vals$ \(v, vs) -> do liftIO $ print $ "fixme-value-set" <+> pretty v <+> hsep (fmap pretty (HS.toList vs)) for_ g $ \git -> do liftIO $ print $ "fixme-git-dir" <+> dquotes (pretty git) dbPath <- asks fixmeEnvDbPath >>= readTVarIO liftIO $ print $ "fixme-state-path" <+> dquotes (pretty dbPath) (before,after) <- asks fixmeEnvCatContext >>= readTVarIO liftIO $ print $ "fixme-def-context" <+> pretty before <+> pretty after ma <- asks fixmeEnvMacro >>= readTVarIO <&> HM.toList for_ ma $ \(n, syn) -> do liftIO $ print $ parens ("define-macro" <+> pretty n <+> pretty syn) scanFiles :: FixmePerks m => FixmeM m [Fixme] scanFiles = do w <- fixmeWorkDir incl <- asks fixmeEnvFileMask >>= readTVarIO excl <- asks fixmeEnvFileExclude >>= readTVarIO keys <- newTVarIO (mempty :: HashMap Text Integer) S.toList_ do glob incl excl w $ \fn -> do ts <- liftIO $ getModificationTime fn <&> round . utcTimeToPOSIXSeconds let fnShort = makeRelative w fn lbs <- liftIO (try @_ @IOException $ LBS.readFile fn) <&> fromRight mempty fxs0 <- lift $ scanBlob (Just fn) lbs for_ fxs0 $ \fme -> do let key = fromString (fnShort <> "#") <> coerce (fixmeTitle fme) <> ":" :: Text atomically $ modifyTVar keys (HM.insertWith (+) key 1) no <- readTVarIO keys <&> HM.lookup key <&> fromMaybe 0 let keyText = key <> fromString (show no) let keyHash = FixmeKey $ fromString $ show $ pretty $ hashObject @HbSync (serialise keyText) let f2 = mempty { fixmeTs = Just (fromIntegral ts) , fixmeKey = keyHash , fixmeAttr = HM.fromList [ ( "fixme-key-string", FixmeAttrVal keyText) , ( "file", FixmeAttrVal (fromString fnShort)) ] , fixmePlain = fixmePlain fme } let fmeNew = (fme <> f2) & fixmeDerivedFields S.yield fmeNew pure True report :: (FixmePerks m, HasPredicate q) => Maybe FilePath -> q -> FixmeM m () report t q = do tpl <- asks fixmeEnvTemplates >>= readTVarIO <&> HM.lookup (maybe "default" fromString t) fxs <- listFixme q case tpl of Nothing -> liftIO $ LBS.putStr $ Aeson.encodePretty (fmap fixmeAttr fxs) Just (Simple (SimpleTemplate simple)) -> do for_ fxs $ \(Fixme{..}) -> do let subst = [ (mkId k, mkStr @C v) | (k,v) <- HM.toList fixmeAttr ] let what = render (SimpleTemplate (inject subst simple)) & fromRight "render error" liftIO $ hPutDoc stdout what import_ :: FixmePerks m => FixmeM m () import_ = do fxs0 <- scanFiles fxs <- flip filterM fxs0 $ \fme -> do let fn = fixmeGet "file" fme <&> Text.unpack . coerce seen <- maybe1 fn (pure False) selectIsAlreadyScanned pure (not seen) hashes <- catMaybes <$> flip runContT pure do p <- ContT $ bracket startGitHash stopProcess let files = mapMaybe (fixmeGet "file") fxs & HS.fromList & HS.toList & fmap (Text.unpack . coerce) for files $ \f -> do mbHash <- lift $ gitHashPathStdin p f case mbHash of Just ha -> pure $ Just (f, ha) Nothing -> pure Nothing versioned <- listBlobs Nothing <&> HM.fromList let commited = HM.elems versioned & HS.fromList let blobs = HM.fromList hashes let isVersioned = maybe False (`HM.member` versioned) withState $ transactional do for_ fxs $ \fme -> do let fn = fixmeGet "file" fme <&> Text.unpack . coerce fmeRich <- lift $ maybe1 fn (pure mempty) (`getMetaDataFromGitBlame` fme) let blob = fn >>= flip HM.lookup blobs >>= \b -> pure (fixmeSet "blob" (fromString (show $ pretty $ b)) mempty) notice $ "fixme" <+> pretty (fixmeKey fme) <+> pretty fn insertFixme (fromMaybe mempty blob <> fmeRich <> fme) -- TODO: add-scanned-only-on-commited -- $workflow: test -- поведение: если файл в гите И закоммичен -- то -- добавляем в сканированные. -- -- если не в гите -- то добавляем в сканированные -- for_ fn $ \f -> do let add = not (isVersioned fn) || maybe False (`HS.member` commited) (HM.lookup f blobs) when add do notice $ red "SCANNED" <+> pretty f insertScanned f cat_ :: FixmePerks m => Text -> FixmeM m () cat_ hash = do (before,after) <- asks fixmeEnvCatContext >>= readTVarIO gd <- fixmeGetGitDirCLIOpt CatAction action <- asks fixmeEnvCatAction >>= readTVarIO void $ flip runContT pure do callCC \exit -> do mha <- lift $ selectFixmeKey hash ha <- ContT $ maybe1 mha (pure ()) fme' <- lift $ getFixme ha Fixme{..} <- ContT $ maybe1 fme' (pure ()) let gh' = HM.lookup "blob" fixmeAttr -- FIXME: define-fallback-action gh <- ContT $ maybe1 gh' none let cmd = [qc|git {gd} cat-file blob {pretty gh}|] :: String let start = fromMaybe 0 fixmeStart & fromIntegral & (\x -> x - before) & max 0 let bbefore = if start > before then before + 1 else 1 let origLen = maybe 0 fromIntegral fixmeEnd - maybe 0 fromIntegral fixmeStart & max 1 let lno = max 1 $ origLen + after + before let dict = [ (mkId k, mkStr @C v) | (k,v) <- HM.toList fixmeAttr ] <> [ (mkId (FixmeAttrName "before"), mkStr @C (FixmeAttrVal $ Text.pack $ show bbefore)) ] debug (pretty cmd) let text = fixmePlain & LBS.fromStrict . encodeUtf8 . Text.unlines . fmap coerce w <- gitRunCommand cmd <&> fromRight text <&> LBS8.lines <&> drop start <&> take lno liftIO $ action dict (LBS8.unlines w)