packages feed

miv (empty) → 0.3.0

raw patch · 14 files changed

+1267/−0 lines, 14 filesdep +aesondep +asyncdep +basesetup-changed

Dependencies added: aeson, async, base, concurrent-output, directory, ghc-prim, hashable, hspec, monad-parallel, process, text, time, unordered-containers, yaml

Files

+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License (MIT)++Copyright (c) 2014-2016 itchyny <https://github.com/itchyny>++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ miv.cabal view
@@ -0,0 +1,60 @@+name:                   miv+version:                0.3.0+author:                 itchyny <https://github.com/itchyny>+maintainer:             itchyny <https://github.com/itchyny>+license:                MIT+license-file:           LICENSE+category:               Compiler+build-type:             Simple+cabal-version:          >=1.8+synopsis:               Manage Vim plugins with command+description:            The miv command is a cli tool to manage Vim plugins.++executable miv+  hs-source-dirs:       src+  main-is:              Main.hs+  ghc-options:          -threaded -Wall+  other-modules:        Plugin+                      , Setting+                      , Mode+                      , Command+                      , Mapping+                      , ShowText+                      , ReadText+                      , VimScript+                      , Git+                      , Paths_miv+  build-depends:        base >= 4.9 && < 5+                      , ghc-prim+                      , process+                      , async+                      , concurrent-output+                      , time+                      , directory+                      , hashable+                      , aeson+                      , yaml+                      , text+                      , unordered-containers+                      , monad-parallel++test-suite spec+  hs-source-dirs:       test+  main-is:              Spec.hs+  type:                 exitcode-stdio-1.0+  build-depends:        base >= 4.9 && < 5+                      , ghc-prim+                      , process+                      , time+                      , directory+                      , hashable+                      , hspec+                      , aeson+                      , yaml+                      , text+                      , unordered-containers+                      , monad-parallel++source-repository head+  type:     git+  location: git@github.com:itchyny/miv.git
+ src/Command.hs view
@@ -0,0 +1,115 @@+{-# LANGUAGE OverloadedStrings #-}+module Command where++import Data.Monoid ((<>))+import qualified Data.Text as T+import Data.Text (Text, unwords)+import Prelude hiding (show, unwords)+import ShowText++data CmdBang = CmdBang+             | CmdNoBang+             deriving Eq++instance ShowText CmdBang where+  show CmdBang = "-bang"+  show CmdNoBang = ""++data CmdBar = CmdBar+            | CmdNoBar+            deriving Eq++instance ShowText CmdBar where+  show CmdBar = "-bar"+  show CmdNoBar = ""++data CmdRegister = CmdRegister+                 | CmdNoRegister+                 deriving Eq++instance ShowText CmdRegister where+  show CmdRegister = "-register"+  show CmdNoRegister = ""++data CmdBuffer = CmdBuffer+               | CmdNoBuffer+               deriving Eq++instance ShowText CmdBuffer where+  show CmdBuffer = "-buffer"+  show CmdNoBuffer = ""++data CmdRange = CmdRange+              | CmdRangeWhole+              | CmdRangeN Int+              | CmdRangeCount Int+              | CmdNoRange+              deriving Eq++instance ShowText CmdRange where+  show CmdRange = "-range"+  show CmdRangeWhole = "-range=%"+  show (CmdRangeN n) = "-range=" <> show n+  show (CmdRangeCount n) = "-count=" <> show n+  show CmdNoRange = ""++data CmdArg = CmdNonNegArg+            | CmdZeroOneArg+            | CmdPositiveArg+            | CmdOneArg+            | CmdNoArg+            deriving Eq++instance ShowText CmdArg where+  show CmdNonNegArg = "-nargs=*"+  show CmdZeroOneArg = "-nargs=?"+  show CmdPositiveArg = "-nargs=+"+  show CmdOneArg = "-nargs=1"+  show CmdNoArg = "-nargs=0"++data CmdComplete = CmdComplete Text+                 deriving Eq++instance ShowText CmdComplete where+  show (CmdComplete "") = ""+  show (CmdComplete complete) = "-complete=" <> complete++data Command =+     Command { cmdName     :: Text+             , cmdRepText  :: Text+             , cmdBang     :: CmdBang+             , cmdBar      :: CmdBar+             , cmdRegister :: CmdRegister+             , cmdBuffer   :: CmdBuffer+             , cmdRange    :: CmdRange+             , cmdArg      :: CmdArg+             , cmdComplete :: CmdComplete+     } deriving Eq++instance ShowText Command where+  show cmd = unwords (filter (not . T.null)+           [ "command!"+           , show (cmdBang cmd)+           , show (cmdBar cmd)+           , show (cmdRegister cmd)+           , show (cmdBuffer cmd)+           , show (cmdRange cmd)+           , show (cmdArg cmd)+           , show (cmdComplete cmd)+           , cmdName cmd+           , cmdRepText cmd+           ])++defaultCommand :: Command+defaultCommand+ = Command { cmdName     = ""+           , cmdRepText  = ""+           , cmdBang     = CmdBang+           , cmdBar      = CmdNoBar+           , cmdRegister = CmdNoRegister+           , cmdBuffer   = CmdNoBuffer+           , cmdRange    = CmdRange+           , cmdArg      = CmdNonNegArg+           , cmdComplete = CmdComplete ""+ }+
+ src/Git.hs view
@@ -0,0 +1,38 @@+module Git+  ( clone+  , cloneSubmodule+  , pull+  , pullSubmodule+  , lastUpdate+  , gitStatus+  , gitUrl+  )+  where++import Data.Monoid ((<>))+import System.Exit (ExitCode(..))+import System.Process (system, readProcess)++clone :: String -> FilePath -> String+clone repo path = unwords ["git", "clone", gitUrl repo, singleQuote path]++cloneSubmodule :: String -> FilePath -> String+cloneSubmodule repo path = unwords ["git", "clone", gitUrl repo, singleQuote path, "&&", "git", "-C", singleQuote path, "submodule", "update", "--init", "--recursive"]++pull :: FilePath -> String+pull path = unwords ["git", "-C", singleQuote path, "pull", "--rebase", "--prune"]++pullSubmodule :: FilePath -> String+pullSubmodule path = unwords ["git", "-C", singleQuote path, "pull", "--rebase", "--prune", "&&", "git", "-C", singleQuote path, "submodule", "update", "--init", "--recursive"]++lastUpdate :: FilePath -> IO Integer+lastUpdate path = read <$> readProcess "sh" ["-c", unwords ["git", "-C", singleQuote path, "show", "-s", "--format=%ct", "2>/dev/null", "||", "echo", "0"]] []++gitStatus :: FilePath -> IO ExitCode+gitStatus path = system $ unwords ["sh", "-c", "\"git", "-C", singleQuote path, "status", ">/dev/null 2>&1\""]++gitUrl :: String -> String+gitUrl = ("https://github.com/" <>)++singleQuote :: String -> String+singleQuote str = "'" <> str <> "'"
+ src/Main.hs view
@@ -0,0 +1,466 @@+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}+module Main where++import Control.Concurrent (threadDelay, newEmptyMVar, forkIO, putMVar, takeMVar)+import Control.Concurrent.Async+import Control.Exception+import Control.Monad (filterM, forM_, unless, void, when, guard)+import qualified Control.Monad.Parallel as P+import Data.List ((\\), foldl', isPrefixOf, nub, transpose, unfoldr)+import Data.Maybe (listToMaybe, fromMaybe, isNothing)+import Data.Monoid ((<>))+import Data.Text (Text, unlines, pack, unpack)+import qualified Data.Text as T+import Data.Text.IO (putStrLn, putStr, writeFile, hGetContents)+import Data.Time (getZonedTime)+import Data.Version (showVersion)+import Data.Yaml (decodeFileEither, prettyPrintParseException)+import GHC.Conc (getNumProcessors, setNumCapabilities)+import Prelude hiding (readFile, writeFile, unlines, putStrLn, putStr, show)+import System.Console.Concurrent ()+import System.Console.Regions+import System.Directory+import System.Environment (getArgs)+import System.Exit (ExitCode(..))+import System.Info (os)+import System.IO (openFile, IOMode(..), hClose, hFlush, stdout, hGetLine)+import System.IO.Error (isDoesNotExistError, tryIOError, isEOFError)+import System.Process++import Git+import Paths_miv (version)+import Plugin+import Setting+import ShowText+import VimScript++nameversion :: Text+nameversion = "miv " <> pack (showVersion version)++expandHomeDirectory :: FilePath -> IO FilePath+expandHomeDirectory ('~':path) = (<>path) <$> getHomeDirectory+expandHomeDirectory path = return path++getSettingFile :: IO (Maybe FilePath)+getSettingFile+  = fmap listToMaybe $ filterM doesFileExist =<< mapM expandHomeDirectory+       [ "~/.vimrc.yaml"+       , "~/.vim/.vimrc.yaml"+       , "~/vimrc.yaml"+       , "~/.vim/vimrc.yaml"+       , "~/_vimrc.yaml"+       , "~/.vim/_vimrc.yaml"+       , "~/_vim/_vimrc.yaml"+       , "~/vimfiles/.vimrc.yaml"+       , "~/vimfiles/vimrc.yaml"+       , "~/vimfiles/_vimrc.yaml"+       ]++getSetting :: IO Setting+getSetting = do+  maybeFile <- getSettingFile+  case maybeFile of+       Just file -> do+         maybeSetting <- decodeFileEither file+         case maybeSetting of+              Right setting -> return setting+              Left err -> error $ prettyPrintParseException err+       Nothing -> error "No setting file: ~/.vimrc.yaml"++pluginDirectory :: IO FilePath+pluginDirectory = do+  dir <- expandHomeDirectory "~/.vim/miv/"+  windir <- expandHomeDirectory "~/vimfiles/miv/"+  exists <- doesDirectoryExist dir+  return (if exists then dir else (if os `elem` ["windows", "mingw"] then windir else dir))++createPluginDirectory :: IO ()+createPluginDirectory =+  createDirectoryIfMissing True =<< pluginDirectory++printUsage :: IO ()+printUsage = mapM_ putStrLn usage++commandHelp :: IO ()+commandHelp = mapM_ (putStrLn . show) arguments++data Argument = Argument (Text, Text)+              deriving (Eq, Ord)+instance ShowText Argument where+  show (Argument (x, y)) = x <> T.replicate (10 - T.length x) " " <> y++arguments :: [Argument]+arguments = map Argument+          [ ("install" , "Installs the uninstalled plugins.")+          , ("update"  , "Updates the plugins.")+          , ("generate", "Generate the miv files.")+          , ("ftdetect", "Gather ftdetect scripts.")+          , ("helptags", "Generate the help tags file.")+          , ("each"    , "Execute command at each plugin directory.")+          , ("path"    , "Print the paths of each plugins.")+          , ("list"    , "List the plugins.")+          , ("clean"   , "Clean up unused plugins.")+          , ("edit"    , "Edit the configuration file.")+          , ("command" , "Show the sub-commands.")+          , ("version" , "Show the version of miv.")+          , ("help"    , "Show this help.")+          ]++usage :: [Text]+usage = [ nameversion+        , ""+        , "Usage: miv COMMAND"+        , ""+        , "Commands:"+        ]+     <> map (T.append "  " . show) arguments+     <> [ ""+        , "You can install the plugins by the following command:"+        , "  miv install"+        , ""+        , "You can update the plugins by the following command:"+        , "  miv update"+        , ""+        , "You can specify the name of plugins:"+        , "  miv update plugin1 plugin2"+        , ""+        , "Normally, outdated plugins are ignored but"+        , "you can update all the plugins with trailing !:"+        , "  miv update!"+        , ""+        , "You can use `miv each' to execute some commands at each plugin directory."+        , "  miv each pwd"+        , "  miv each git gc"+        , "  miv each git diff"+        , "  miv each 'echo $(du -sh .) $(basename $(pwd))'"+        , "  miv each 'git diff --quiet || echo ${PWD##*/}'"+        , ""+        , "This software is released under the MIT License."+        , "This software is distributed at https://github.com/itchyny/miv."+        , "Report a bug of this software at https://github.com/itchyny/miv/issues."+        , "The author is itchyny <https://github.com/itchyny>."+        ]++levenshtein :: Eq a => [a] -> [a] -> Int+levenshtein a b = last $ foldl' f [0..length a] b+  where+    f [] _ = []+    f xs@(x:xs') c = scanl (g c) (x + 1) (zip3 a xs xs')+    g c z (d, x, y) = minimum [y + 1, z + 1, x + fromEnum (c /= d)]++suggestCommand :: String -> IO ()+suggestCommand arg = do+  let distcommands = [(levenshtein arg (unpack x), Argument (x, y)) | (Argument (x, y)) <- arguments]+      mindist = fst (minimum distcommands)+      mincommands = [y | (x, y) <- distcommands, x == mindist]+      prefixcommands = [y | y@(Argument (x, _)) <- arguments, arg `isPrefixOf` unpack x || unpack x `isPrefixOf` arg]+      containedcommands = [y | y@(Argument (x, _)) <- arguments, length arg > 1 && arg `isContainedIn` unpack x]+  putStrLn $ "Unknown command: " <> pack arg+  putStrLn "Probably:"+  mapM_ (putStrLn . ("  "<>) . show) (if null prefixcommands then nub (containedcommands <> mincommands) else prefixcommands)++isContainedIn :: Eq a => [a] -> [a] -> Bool+isContainedIn xxs@(x:xs) (y:ys) = x == y && xs `isContainedIn` ys || xxs `isContainedIn` ys+isContainedIn [] _ = True+isContainedIn _ [] = False++suggestPlugin :: [Plugin] -> String -> IO ()+suggestPlugin plugin arg = do+  let distplugins = [(levenshtein arg (rtpName p), p) | p <- plugin]+      mindist = fst (minimum distplugins)+      minplugins = [y | (x, y) <- distplugins, x == mindist]+  putStrLn $ "Unknown plugin: " <> pack arg+  putStrLn "Probably:"+  mapM_ (putStrLn . ("  "<>) . show) minplugins++data Update = Install | Update deriving Eq+instance ShowText Update where+  show Install = "installing"+  show Update = "updating"++data UpdateStatus+   = UpdateStatus {+       installed :: [Plugin],+       updated :: [Plugin],+       nosync :: [Plugin],+       outdated :: [Plugin],+       failed :: [Plugin]+   }++instance Semigroup UpdateStatus where+  UpdateStatus i u n o f <> UpdateStatus i' u' n' o' f'+    = UpdateStatus (i <> i') (u <> u') (n <> n') (o <> o') (f <> f')++instance Monoid UpdateStatus where+  mempty = UpdateStatus [] [] [] [] []++updatePlugin :: Update -> Maybe [String] -> Setting -> IO ()+updatePlugin update maybePlugins setting = do+  setNumCapabilities =<< getNumProcessors+  let unknownPlugins = filter (`notElem` map rtpName (plugins setting)) (fromMaybe [] maybePlugins)+  unless (null unknownPlugins) $+    mapM_ (suggestPlugin (plugins setting)) unknownPlugins+  createPluginDirectory+  dir <- pluginDirectory+  let specified p = rtpName p `elem` fromMaybe [] maybePlugins || maybePlugins == Just []+  let filterplugin p = isNothing maybePlugins || specified p+  let ps = filter filterplugin (plugins setting)+  let count xs = if length xs > 1 then "s (" <> show (length xs) <> ")" else ""+  time <- maximum <$> mapM' (lastUpdatePlugin dir) ps+  status <- fmap mconcat <$> displayConsoleRegions $+    mapConcurrently (fmap mconcat . mapM (\p -> updateOnePlugin time dir update (specified p) p)) $+      transpose $ takeWhile (not . null) $ unfoldr (Just . splitAt 6) ps+  putStrLn $ (if null (failed status) then "Success" else "Error occured") <> " in " <> show update <> "."+  unless (null (installed status)) $ do+    putStrLn $ "Installed plugin" <> count (installed status) <> ": "+    mapM_ (putStrLn . ("  "<>) . name) (reverse (installed status))+  unless (null (updated status)) $ do+    putStrLn $ "Updated plugin" <> count (updated status) <> ": "+    mapM_ (putStrLn . ("  "<>) . name) (reverse (updated status))+  unless (null (failed status)) $ do+    putStrLn $ "Failed plugin" <> count (failed status) <> ": "+    mapM_ (putStrLn . ("  "<>) . name) (reverse (failed status))+  generatePluginCode setting+  gatherFtdetectScript setting+  generateHelpTags setting++mapM' :: P.MonadParallel m => (a -> m b) -> [a] -> m [b]+mapM' f = fmap mconcat . P.mapM (mapM f) . transpose . takeWhile (not . null) . unfoldr (Just . splitAt 32)++cleanAndCreateDirectory :: FilePath -> IO ()+cleanAndCreateDirectory dir = do+  createDirectoryIfMissing True dir+  removeDirectoryRecursive dir+  createDirectoryIfMissing True dir++generateHelpTags :: Setting -> IO ()+generateHelpTags setting = do+  dir <- pluginDirectory+  let docdir = dir <> "miv/doc/"+  cleanAndCreateDirectory docdir+  P.forM_ (map (\p -> dir <> rtpName p <> "/doc/") (plugins setting)) $ \path -> do+    exists <- doesDirectoryExist path+    when exists $+      void $ system $ unwords ["sh", "-c", "\"cd", "'" <> path <> "'", "&& cp *", "'" <> docdir <> "'", "2>/dev/null\""]+  _ <- system $ "vim -u NONE -i NONE -N -e -s -c 'helptags " <> docdir <> "' -c quit"+  putStrLn "Success in processing helptags."++lastUpdatePlugin :: FilePath -> Plugin -> IO Integer+lastUpdatePlugin dir plugin = do+  let path = dir <> rtpName plugin <> "/.git"+  exists <- doesDirectoryExist path+  if exists then lastUpdate path else return 0++updateOnePlugin :: Integer -> FilePath -> Update -> Bool -> Plugin -> IO UpdateStatus+updateOnePlugin time dir update specified plugin = do+  let path = dir <> rtpName plugin+      repo = vimScriptRepo (name plugin)+      cloneCommand = if submodule plugin then cloneSubmodule else clone+      pullCommand = if submodule plugin then pullSubmodule else pull+      putStrLn' = \region -> setConsoleRegion region . ((name plugin <> ": ") <>)+      finish' = \region -> finishConsoleRegion region . ((name plugin <> ": ") <>)+  exists <- doesDirectoryExist path+  gitstatus <- gitStatus path+  if not exists || (gitstatus /= ExitSuccess && not (sync plugin))+     then withConsoleRegion Linear $ \region -> do+       putStrLn' region "Installing"+       when exists $ removeDirectoryRecursive path+       cloneStatus <- execCommand (unpack $ name plugin) region $ cloneCommand repo path+       created <- doesDirectoryExist path+       if cloneStatus /= ExitSuccess || not created+          then return mempty { failed = [plugin] }+          else return mempty { installed = [plugin] }+     else if update == Install || not (sync plugin)+             then return mempty { nosync = [plugin] }+             else withConsoleRegion Linear $ \region -> do+               lastUpdateTime <- lastUpdate path+               if lastUpdateTime < time - 60 * 60 * 24 * 30 && not specified+                  then do+                    finish' region "Outdated"+                    return mempty { outdated = [plugin] }+                  else do+                    putStrLn' region "Pulling"+                    pullStatus <- execCommand (unpack $ name plugin) region $ pullCommand path+                    newUpdateTime <- lastUpdate path+                    return $ if pullStatus /= ExitSuccess+                                then mempty { failed = [plugin] }+                                else if newUpdateTime <= lastUpdateTime+                                        then mempty+                                        else mempty { updated = [plugin] }++execCommand :: String -> ConsoleRegion -> String -> IO ExitCode+execCommand pluginName region command = do+  (_, Just hout, Just herr, ph) <- createProcess (proc "sh" ["-c", command]) { std_out = CreatePipe, std_err = CreatePipe }+  outmvar <- newEmptyMVar+  errmvar <- newEmptyMVar+  _ <- forkIO $ go herr errmvar ""+  _ <- forkIO $ go hout outmvar ""+  errline <- takeMVar errmvar+  outline <- takeMVar outmvar+  code <- waitForProcess ph+  finishConsoleRegion region (pluginName ++ ": " ++ if null outline then errline else outline)+  return code+  where go h mvar lastLine = do+          e <- tryIOError $ hGetLine h+          case e of+               Left err -> if isEOFError err then putMVar mvar lastLine else return ()+               Right line -> do+                 setConsoleRegion region (pluginName ++ ": " ++ line)+                 threadDelay 100000+                 go h mvar line++vimScriptRepo :: Text -> FilePath+vimScriptRepo pluginname | T.any (=='/') pluginname = unpack pluginname+                         | otherwise = unpack $ "vim-scripts/" <> pluginname++listPlugin :: Setting -> IO ()+listPlugin setting = mapM_ putStrLn $ space $ map format $ plugins setting+  where format p = [show p, name p, pack $ gitUrl (vimScriptRepo (name p))]+        space xs =+          let max0 = maximum (map (T.length . (!!0)) xs) + 1+              max1 = maximum (map (T.length . (!!1)) xs) + 1+              in map (\(as:bs:cs:_) -> as <> T.replicate (max0 - T.length as) " " <> bs <> T.replicate (max1 - T.length bs) " " <> cs) xs++cleanDirectory :: Setting -> IO ()+cleanDirectory setting = do+  createPluginDirectory+  dir <- pluginDirectory+  createDirectoryIfMissing True dir+  cnt <- getDirectoryContents dir+  let paths = "." : ".." : "miv" : map (unpack . show) (plugins setting)+      delpath' = [ dir <> d | d <- cnt, d `notElem` paths ]+  deldir <- filterM doesDirectoryExist delpath'+  delfile <- filterM doesFileExist delpath'+  let delpath = deldir <> delfile+  if not (null delpath)+     then do putStrLn "Remove:"+             mapM_ (putStrLn . pack . ("  "<>)) delpath+             putStr "Really? [y/N] "+             hFlush stdout+             c <- getChar+             when (c == 'y' || c == 'Y') $ do+               mapM_ removeDirectoryRecursive deldir+               mapM_ removeFile delfile+     else putStrLn "Clean."++saveScript :: (FilePath, Place, [Text]) -> IO ()+saveScript (dir, place, code) = do+  let relname = show place+      path = dir <> unpack relname+      isAllAscii = all (T.all (<='~')) code+      body = "" : (if isAllAscii then [] else [ "scriptencoding utf-8", "" ])+         <> [ "let s:save_cpo = &cpo"+            , "set cpo&vim"+            , "" ]+         <> code+         <> [ ""+            , "let &cpo = s:save_cpo"+            , "unlet s:save_cpo" ]+  time <- getZonedTime+  contents <- fileContents path+  when (contents /= Just body) $+    writeFile path $ unlines $+              [ "\" Filename: " <> relname+              , "\" Last Change: " <> show time+              , "\" Generated by " <> nameversion ] ++ body++fileContents :: String -> IO (Maybe [Text])+fileContents path = do+  eitherFile <- tryJust (guard . isDoesNotExistError) (openFile path ReadMode)+  case eitherFile of+       Right file -> do+         contentLines <- T.lines <$> hGetContents file+         hClose file+         return $ Just $ dropWhile ("\" " `T.isPrefixOf`) contentLines+       Left _ -> return Nothing++generatePluginCode :: Setting -> IO ()+generatePluginCode setting = do+  dir <- fmap (<>"miv/") pluginDirectory+  createDirectoryIfMissing True dir+  -- TODO: remove unused directories and files+  createDirectoryIfMissing True (dir <> "plugin/")+  createDirectoryIfMissing True (dir <> "autoload/miv/")+  createDirectoryIfMissing True (dir <> "ftplugin/")+  P.mapM_ (saveScript . (\(t, s) -> (dir, t, s)))+          (vimScriptToList (gatherScript setting))+  putStrLn "Success in generating Vim scripts of miv."++gatherFtdetectScript :: Setting -> IO ()+gatherFtdetectScript setting = do+  dir <- pluginDirectory+  cleanAndCreateDirectory (dir <> "miv/ftdetect/")+  forM_ (plugins setting) $ \plugin -> do+    let path = rtpName plugin+    exists <- doesDirectoryExist (dir <> path <> "/ftdetect")+    when exists $ do+      files <- (\\ [".", ".."]) <$> getDirectoryContents (dir <> path <> "/ftdetect")+      forM_ files $ \file ->+        copyFile (dir <> path <> "/ftdetect/" <> file) (dir <> "miv/ftdetect/" <> file)+  putStrLn "Success in gathering ftdetect scripts."++data EachStatus = EachStatus { failed' :: [Plugin] }++instance Semigroup EachStatus where+  EachStatus f <> EachStatus f' = EachStatus (f <> f')++instance Monoid EachStatus where+  mempty = EachStatus []++eachPlugin :: String -> Setting -> IO ()+eachPlugin command setting = do+  createPluginDirectory+  dir <- pluginDirectory+  status <- mconcat <$> mapM (eachOnePlugin command dir) (plugins setting)+  unless (null (failed' status)) $ do+    putStrLn "Error:"+    mapM_ (putStrLn . ("  "<>) . name) (failed' status)++eachOnePlugin :: String -> FilePath -> Plugin -> IO EachStatus+eachOnePlugin command dir plugin = do+  let path = dir <> rtpName plugin+  exists <- doesDirectoryExist path+  if not exists+     then return mempty+     else do exitCode <- system (unwords ["cd", "'" <> path <> "'", "&&", command])+             case exitCode of+                  ExitSuccess -> return mempty+                  _ -> return $ mempty { failed' = [plugin] }++eachHelp :: IO ()+eachHelp = mapM_ putStrLn [ "Specify command:", "  miv each [command]" ]++pathPlugin :: [String] -> Setting -> IO ()+pathPlugin plugins' setting = do+  let ps = filter (\p -> rtpName p `elem` plugins' || null plugins') (plugins setting)+  dir <- pluginDirectory+  forM_ ps (\plugin -> putStrLn (pack dir <> show plugin))++mainProgram :: [String] -> IO ()+mainProgram [] = printUsage+mainProgram ['-':arg] = mainProgram [arg]+mainProgram ["help"] = printUsage+mainProgram ["version"] = putStrLn nameversion+mainProgram ["install"] = getSetting >>= updatePlugin Install Nothing+mainProgram ["update"] = getSetting >>= updatePlugin Update Nothing+mainProgram ["update!"] = getSetting >>= updatePlugin Update (Just [])+mainProgram ["update", "!"] = getSetting >>= updatePlugin Update (Just [])+mainProgram ["each"] = eachHelp+mainProgram ["list"] = getSetting >>= listPlugin+mainProgram ["command"] = commandHelp+mainProgram ["clean"] = getSetting >>= cleanDirectory+mainProgram ["edit"] = getSettingFile >>= maybe (return ()) (($) void . system . ("vim "<>))+mainProgram ["generate"] = getSetting >>= generatePluginCode+mainProgram ["ftdetect"] = getSetting >>= gatherFtdetectScript+mainProgram ["helptags"] = getSetting >>= generateHelpTags+mainProgram ["path"] = getSetting >>= pathPlugin []+mainProgram [arg] = suggestCommand arg+mainProgram ("install":args) = getSetting >>= updatePlugin Install (Just args)+mainProgram ("update":args) = getSetting >>= updatePlugin Update (Just args)+mainProgram ("each":args) = getSetting >>= eachPlugin (unwords args)+mainProgram ("path":args) = getSetting >>= pathPlugin args+mainProgram (('-':arg):args) = mainProgram (arg:args)+mainProgram _ = printUsage++main :: IO ()+main = getArgs >>= mainProgram
+ src/Mapping.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE OverloadedStrings #-}+module Mapping where++import Data.Monoid ((<>))+import Data.Text (Text, unwords, null)+import Prelude hiding (show, unwords, null)++import Mode+import ShowText++data MapUnique = MapUnique | MapNoUnique+               deriving Eq++instance ShowText MapUnique where+  show MapUnique = "<unique>"+  show MapNoUnique = ""++data MapSilent = MapSilent | MapNoSilent+               deriving Eq++instance ShowText MapSilent where+  show MapSilent = "<silent>"+  show MapNoSilent = ""++data Mapping =+     Mapping { mapName    :: Text+             , mapRepText :: Text+             , mapUnique  :: MapUnique+             , mapSilent  :: MapSilent+             , mapMode    :: Mode+     } deriving Eq++instance ShowText Mapping where+  show m = unwords (filter (not . null)+          [ show (mapMode m) <> "noremap"+          , show (mapUnique m)+         <> show (mapSilent m)+          , mapName m+          , mapRepText m+          ])++defaultMapping :: Mapping+defaultMapping+  = Mapping { mapName    = ""+            , mapRepText = ""+            , mapUnique  = MapUnique+            , mapSilent  = MapSilent+            , mapMode    = NormalMode+  }+
+ src/Mode.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE OverloadedStrings #-}+module Mode where++import Data.Text (unpack)++import ReadText+import ShowText++data Mode = NormalMode+          | VisualMode+          | SelectMode+          | InsertMode+          | CmdlineMode+          | ExMode+          | OperatorPendingMode+          | ReplaceMode+          | VirtualReplaceMode+          | InsertNormalMode+          | InesrtVisualMode+          | InsertSelectMode+          deriving Eq++instance ShowText Mode where+  show NormalMode = "n"+  show VisualMode = "v"+  show SelectMode = "s"+  show InsertMode = "i"+  show CmdlineMode = "c"+  show ExMode = "ex"+  show OperatorPendingMode = "o"+  show ReplaceMode = "r"+  show VirtualReplaceMode = "vr"+  show InsertNormalMode = "in"+  show InesrtVisualMode = "iv"+  show InsertSelectMode = "is"++instance ReadText Mode where+  read "n"  = NormalMode+  read "v"  = VisualMode+  read "s"  = SelectMode+  read "i"  = InsertMode+  read "c"  = CmdlineMode+  read "ex" = ExMode+  read "o"  = OperatorPendingMode+  read "r"  = ReplaceMode+  read "vr" = VirtualReplaceMode+  read "in" = InsertNormalMode+  read "iv" = InesrtVisualMode+  read "is" = InsertSelectMode+  read m    = error $ "unknown mode: " ++ unpack m
+ src/Plugin.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE OverloadedStrings, RecordWildCards #-}+module Plugin where++import Control.Applicative ((<|>))+import Data.Aeson+import qualified Data.Text as T+import Data.Text (Text, unpack)++import ShowText++data Plugin =+     Plugin { name       :: Text+            , filetypes  :: [Text]+            , commands   :: [Text]+            , functions  :: [Text]+            , mappings   :: [Text]+            , mapmodes   :: [Text]+            , insert     :: Bool+            , enable     :: Text+            , sync       :: Bool+            , mapleader  :: Text+            , script     :: [Text]+            , after      :: [Text]+            , before     :: [Text]+            , dependon   :: [Text]+            , dependedby :: [Text]+            , loadafter  :: [Text]+            , loadbefore :: [Text]+            , submodule  :: Bool+     } deriving (Eq, Ord)++instance ShowText Plugin where+  show plg = subst (name plg)+    where subst s | T.any (=='/') s = subst (T.tail $ T.dropWhile (/='/') s)+                  | ".vim" `T.isSuffixOf` s = T.take (T.length s - 4) s+                  | "-vim" `T.isSuffixOf` s = T.take (T.length s - 4) s+                  | "vim-" `T.isPrefixOf` s = T.drop 4 s+                  | otherwise = T.filter (`notElem`("!?;:/<>()[]{}|~'\"" :: String)) s++rtpName :: Plugin -> String+rtpName = unpack . ShowText.show++instance FromJSON Plugin where+  parseJSON = withObject "plugin" $ \o -> do+    name <- o .:? "name" .!= ""+    filetypes <- o .: "filetype" <|> (fmap return <$> o .:? "filetype") .!= []+    commands <- o .: "command" <|> (fmap return <$> o .:? "command") .!= []+    functions <- o .: "function" <|> (fmap return <$> o .:? "function") .!= []+    mappings <- o .: "mapping" <|> (fmap return <$> o .:? "mapping") .!= []+    mapmodes <- o .: "mapmode" <|> (fmap return <$> o .:? "mapmode") .!= []+    mapleader <- o .:? "mapleader" .!= ""+    insert <- o .:? "insert" .!= False+    enable <- o .:? "enable" .!= ""+    sync <- o .:? "sync" .!= True+    script <- T.lines <$> o .:? "script" .!= ""+    after <- T.lines <$> o .:? "after" .!= ""+    before <- T.lines <$> o .:? "before" .!= ""+    dependon <- o .: "dependon" <|> (fmap return <$> o .:? "dependon") .!= []+    dependedby <- o .: "dependedby" <|> (fmap return <$> o .:? "dependedby") .!= []+    loadafter <- o .: "loadafter" <|> (fmap return <$> o .:? "loadafter") .!= []+    loadbefore <- o .: "loadbefore" <|> (fmap return <$> o .:? "loadbefore") .!= []+    submodule <- o .:? "submodule" .!= False+    return Plugin {..}
+ src/ReadText.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE FlexibleInstances, IncoherentInstances, UndecidableInstances #-}+module ReadText where++import Data.Text++class ReadText a where+  read :: Text -> a++instance Read a => ReadText a where+  read = Prelude.read . unpack
+ src/Setting.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE OverloadedStrings, RecordWildCards #-}+module Setting where++import Data.Aeson+import Data.Function (on)+import qualified Data.HashMap.Lazy as HM+import Data.List (sortBy)+import Data.Text (Text, lines)+import Prelude hiding (lines)++import Plugin++data Setting =+     Setting { plugins  :: [Plugin]+             , filetype :: HM.HashMap Text [Text]+             , before   :: [Text]+             , after    :: [Text]+     } deriving Eq++instance FromJSON Setting where+  parseJSON = withObject "setting" $ \o -> do+    plugins <- pluginHashMapToList <$> o .:? "plugin" .!= HM.empty+    filetype <- HM.map lines <$> o .:? "filetype" .!= HM.empty+    before <- lines <$> o .:? "before" .!= ""+    after <- lines <$> o .:? "after" .!= ""+    return Setting {..}+      where pluginHashMapToList = sortWith name . HM.foldlWithKey' (\a k v -> v { name = k } : a) []++sortWith :: Ord b => (a -> b) -> [a] -> [a]+sortWith = sortBy . on compare
+ src/ShowText.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE FlexibleInstances, IncoherentInstances, UndecidableInstances #-}+module ShowText where++import Data.Text++class ShowText a where+  show :: a -> Text++instance Show a => ShowText a where+  show = pack . Prelude.show
+ src/VimScript.hs view
@@ -0,0 +1,351 @@+{-# LANGUAGE DeriveGeneric, OverloadedStrings #-}+module VimScript where++import Data.Char (isAlpha, isAlphaNum, toLower)+import Data.Hashable+import qualified Data.HashMap.Lazy as HM+import Data.List (foldl')+import Data.Maybe (mapMaybe)+import Data.Monoid ((<>))+import Data.Text (Text, unwords, singleton)+import qualified Data.Text as T+import GHC.Generics (Generic)+import Prelude hiding (show, unwords, read)++import qualified Command as C+import qualified Mapping as M+import Mode+import qualified Plugin as P+import ReadText+import qualified Setting as S+import ShowText++data VimScript = VimScript (HM.HashMap Place [Text])+               deriving (Eq)++data Place = Plugin+           | Autoload Text+           | Ftplugin Text+           deriving (Eq, Generic)++instance Hashable Place where+  hashWithSalt a Plugin       = a `hashWithSalt` (0 :: Int) `hashWithSalt` ("" :: Text)+  hashWithSalt a (Autoload s) = a `hashWithSalt` (1 :: Int) `hashWithSalt` s+  hashWithSalt a (Ftplugin s) = a `hashWithSalt` (2 :: Int) `hashWithSalt` s++instance ShowText Place where+  show Plugin        = "plugin/miv.vim"+  show (Autoload "") = "autoload/miv.vim"+  show (Autoload s)  = "autoload/miv/" <> s <> ".vim"+  show (Ftplugin s)  = "ftplugin/" <> s <> ".vim"++autoloadSubdirName :: Place -> Maybe Text+autoloadSubdirName (Autoload "") = Nothing+autoloadSubdirName (Autoload s) = Just s+autoloadSubdirName _ = Nothing++isFtplugin :: Place -> Bool+isFtplugin (Ftplugin _) = True+isFtplugin _ = False++vimScriptToList :: VimScript -> [(Place, [Text])]+vimScriptToList (VimScript x) = HM.toList x++instance Semigroup VimScript where+  VimScript x <> VimScript y+    | HM.null x = VimScript y+    | HM.null y = VimScript x+    | otherwise = VimScript (HM.unionWith concat' x y)+    where+      concat' a [] = a+      concat' [] b = b+      concat' a b = a <> [""] <> b++instance Monoid VimScript where+  mempty = VimScript HM.empty++gatherScript :: S.Setting -> VimScript+gatherScript setting = addAutoloadNames+                     $ beforeScript setting+                    <> gatherBeforeAfterScript plugins+                    <> gather "dependon" P.dependon plugins+                    <> gather "dependedby" P.dependedby plugins+                    <> gather "mappings" P.mappings plugins+                    <> gather "mapmodes" P.mapmodes plugins+                    <> gatherFuncUndefined setting+                    <> pluginLoader+                    <> mappingLoader+                    <> commandLoader+                    <> foldl' (<>) mempty (map pluginConfig plugins)+                    <> filetypeLoader setting+                    <> gatherInsertEnter setting+                    <> filetypeScript (S.filetype setting)+                    <> afterScript setting+  where plugins = S.plugins setting++gatherBeforeAfterScript :: [P.Plugin] -> VimScript+gatherBeforeAfterScript x = insertAuNameMap $ gatherScripts x (mempty, HM.empty)+  where+    insertAuNameMap :: (VimScript, HM.HashMap Text Text) -> VimScript+    insertAuNameMap (vs, hm) = VimScript (HM.singleton (Autoload "") $+          [ "let s:autoload = {" ]+       <> [ "      \\ " <> singleQuote k <> ": " <> singleQuote a <> "," | (k, a) <- HM.toList hm ]+       <> [ "      \\ }" ]) <> vs+    gatherScripts :: [P.Plugin] -> (VimScript, HM.HashMap Text Text) -> (VimScript, HM.HashMap Text Text)+    gatherScripts (p:ps) (vs, hm)+            | null (P.before p) && null (P.after p) = gatherScripts ps (vs, hm)+            | otherwise = gatherScripts ps (vs <> vs', HM.insert name hchar hm)+      where+        name = T.filter isAlphaNum (T.toLower (show p))+        hchar | null (loadScript p) = maybe "_" singleton $ getHeadChar $ show p+              | otherwise = "_"+        funcname str = "miv#" <> hchar <> "#" <> str <> "_" <> name+        au = Autoload hchar+        vs' = VimScript $ HM.singleton au $ wrapFunction (funcname "before") (P.before p)+                                         <> wrapFunction (funcname "after") (P.after p)+    gatherScripts [] (vs, hm) = (vs, hm)++addAutoloadNames :: VimScript -> VimScript+addAutoloadNames h@(VimScript hm)+  = VimScript (HM.singleton (Autoload "")+      [ "let s:autoloads = { " <> T.intercalate ", " (((<>": 1") . singleQuote)+                               <$> mapMaybe autoloadSubdirName (HM.keys hm)) <> " }"])+   <> h++gather :: Text -> (P.Plugin -> [Text]) -> [P.Plugin] -> VimScript+gather name f plg+  = VimScript (HM.singleton (Autoload "") $+      [ "let s:" <> name <> " = {" ]+   <> [ "      \\ " <> singleQuote (show p)+                    <> ": [ " <> T.intercalate ", " (map singleQuote (f p))  <> " ],"+                                                               | p <- plg, not (null (f p)) ]+   <> [ "      \\ }" ])++pluginConfig :: P.Plugin -> VimScript+pluginConfig plg+    = VimScript (HM.singleton Plugin $ wrapInfo $+        wrapEnable plg $ mapleader <> gatherCommand plg <> gatherMapping plg <> P.script plg <> loadScript plg)+  where+    wrapInfo [] = []+    wrapInfo str = ("\" " <> P.name plg) : str+    mapleader = (\s -> if T.null s then [] else ["let g:mapleader = " <> singleQuote s]) (P.mapleader plg)++loadScript :: P.Plugin -> [Text]+loadScript plg+  | all null [ P.commands plg, P.mappings plg, P.functions plg, P.filetypes plg+             , P.loadafter plg, P.loadbefore plg ] && not (P.insert plg)+  = ["call miv#load(" <> singleQuote (show plg) <> ")"]+  | otherwise = []++gatherCommand :: P.Plugin -> [Text]+gatherCommand plg+  | not (null (P.commands plg))+    = [show (C.defaultCommand { C.cmdName = c+        , C.cmdRepText = unwords ["call miv#command(" <> singleQuote (show plg) <> ","+                               , singleQuote c <> ","+                               , singleQuote "<bang>" <> ","+                               , "<q-args>,"+                               , "expand('<line1>'),"+                               , "expand('<line2>'))" ] }) | c <- P.commands plg]+  | otherwise = []++gatherMapping :: P.Plugin -> [Text]+gatherMapping plg+  | not (null (P.mappings plg))+    = let genMapping+            = [\mode ->+               M.defaultMapping+                  { M.mapName    = c+                  , M.mapRepText = escape mode <> ":<C-u>call miv#mapping("+                        <> singleQuote (show plg) <> ", "+                        <> singleQuote c <> ", "+                        <> singleQuote (show mode) <> ")<CR>"+                  , M.mapMode    = mode } | c <- P.mappings plg]+          escape m = if m `elem` [ InsertMode, OperatorPendingMode ] then "<ESC>" else ""+          modes = if null (P.mapmodes plg) then [NormalMode, VisualMode] else map read (P.mapmodes plg)+          in concat [map (show . f) modes | f <- genMapping]+  | otherwise = []++beforeScript :: S.Setting -> VimScript+beforeScript setting = VimScript (HM.singleton Plugin (S.before setting))++afterScript :: S.Setting -> VimScript+afterScript setting = VimScript (HM.singleton Plugin (S.after setting))++filetypeLoader :: S.Setting -> VimScript+filetypeLoader setting+  = HM.foldrWithKey f mempty (filetypeLoadPlugins (S.plugins setting) HM.empty)+  where+    f ft plg val =+      case getHeadChar ft of+           Nothing -> val+           Just c ->+             let funcname = "miv#" <> singleton c <> "#load_" <> T.filter isAlphaNum (T.toLower ft)+                 in val+                  <> VimScript (HM.singleton (Autoload (singleton c))+                       (("function! " <> funcname <> "() abort")+                       : "  setl ft="+                       :  concat [wrapEnable b+                       [ "  call miv#load(" <> singleQuote (show b) <> ")"] | b <- plg]+                    <> [ "  autocmd! MivFileTypeLoad" <> ft+                       , "  setl ft=" <> ft+                       , "  silent! doautocmd FileType " <> ft+                       , "endfunction"+                       ]))+                  <> VimScript (HM.singleton Plugin+                       [ "augroup MivFileTypeLoad" <> ft+                       , "  autocmd!"+                       , "  autocmd FileType " <> ft <> " call " <> funcname <> "()"+                       , "augroup END"+                       ])++filetypeLoadPlugins :: [P.Plugin] -> HM.HashMap Text [P.Plugin] -> HM.HashMap Text [P.Plugin]+filetypeLoadPlugins (b:plugins) fts+  | not (null (P.filetypes b))+  = filetypeLoadPlugins plugins (foldr (flip (HM.insertWith (<>)) [b]) fts (P.filetypes b))+  | otherwise = filetypeLoadPlugins plugins fts+filetypeLoadPlugins [] fts = fts++gatherInsertEnter :: S.Setting -> VimScript+gatherInsertEnter setting+  = VimScript (HM.singleton Plugin (f [ p | p <- S.plugins setting, P.insert p ]))+  where f [] = []+        f plgs = "\" InsertEnter"+               : "function! s:insertEnter() abort"+             : [ "  call miv#load(" <> singleQuote (show p) <> ")" | p <- plgs :: [P.Plugin] ]+            <> [ "  autocmd! MivInsertEnter"+               , "  silent! doautocmd InsertEnter"+               , "endfunction"+               , ""+               , "augroup MivInsertEnter"+               , "  autocmd!"+               , "  autocmd InsertEnter * call s:insertEnter()"+               , "augroup END"+               , "" ]++gatherFuncUndefined :: S.Setting -> VimScript+gatherFuncUndefined setting+  = VimScript (HM.singleton Plugin (f [ p | p <- S.plugins setting, not (null (P.functions p))]))+  where f [] = []+        f plgs = "\" FuncUndefined"+               : "function! s:funcUndefined() abort"+               : "  let f = expand('<amatch>')"+               : concat [+               [ "  if f =~# " <> singleQuote q+               , "    call miv#load(" <> singleQuote (show p) <> ")"+               , "  endif" ] | (p, q) <- concatMap (\q -> map ((,) q) (P.functions q)) plgs]+            <> [ "endfunction"+               , ""+               , "augroup MivFuncUndefined"+               , "  autocmd!"+               , "  autocmd FuncUndefined * call s:funcUndefined()"+               , "augroup END"+               , "" ]++wrapEnable :: P.Plugin -> [Text] -> [Text]+wrapEnable plg str+  | null str = []+  | T.null (P.enable plg) = str+  | P.enable plg == "0" = []+  | otherwise = (indent <> "if " <> P.enable plg)+                           : map ("  "<>) str+             <> [indent <> "endif"]+  where indent = T.takeWhile (==' ') (head str)++singleQuote :: Text -> Text+singleQuote str = "'" <> str <> "'"++filetypeScript :: HM.HashMap Text [Text] -> VimScript+filetypeScript =+  HM.foldrWithKey (\ft scr val -> val <> VimScript (HM.singleton (Ftplugin ft) scr)) mempty++wrapFunction :: Text -> [Text] -> [Text]+wrapFunction funcname script =+     ["function! " <> funcname <> "() abort"]+  <> map ("  "<>) script+  <> ["endfunction"]++getHeadChar :: Text -> Maybe Char+getHeadChar xs+  | T.null xs = Nothing+  | otherwise = let x = T.head xs in if isAlpha x then Just (toLower x) else getHeadChar (T.tail xs)++mappingLoader :: VimScript+mappingLoader = VimScript (HM.singleton (Autoload "")+  [ "function! miv#mapping(name, mapping, mode) abort"+  , "  call miv#load(a:name)"+  , "  if a:mode ==# 'v' || a:mode ==# 'x'"+  , "    call feedkeys('gv', 'n')"+  , "  elseif a:mode ==# 'o'"+  , "    call feedkeys(\"\\<Esc>\", 'n')"+  , "    call feedkeys(v:operator, 'm')"+  , "  endif"+  , "  call feedkeys((v:count ? v:count : '') . substitute(a:mapping, '<Plug>', \"\\<Plug>\", 'g'), 'm')"+  , "  return ''"+  , "endfunction"+  ])++commandLoader :: VimScript+commandLoader = VimScript (HM.singleton (Autoload "")+  [ "function! miv#command(name, command, bang, args, line1, line2) abort"+  , "  silent! execute 'delcommand' a:command"+  , "  call miv#load(a:name)"+  , "  let range = a:line1 != a:line2 ? a:line1.','.a:line2 : ''"+  , "  try"+  , "    exec range.a:command.a:bang a:args"+  , "  catch /^Vim\\%((\\a\\+)\\)\\=:E481:/"+  , "    exec a:command.a:bang a:args"+  , "  endtry"+  , "endfunction"+  ])++pluginLoader :: VimScript+pluginLoader = VimScript (HM.singleton (Autoload "")+  [ "let s:loaded = {}"+  , "let s:path = expand('<sfile>:p:h:h')"+  , "let s:mivpath = expand('<sfile>:p:h:h:h') . '/'"+  , "function! miv#load(name) abort"+  , "  if has_key(s:loaded, a:name)"+  , "    return"+  , "  endif"+  , "  let s:loaded[a:name] = 1"+  , "  for n in get(s:dependon, a:name, [])"+  , "    call miv#load(n)"+  , "  endfor"+  , "  for mode in get(s:mapmodes, a:name, [ 'n', 'v' ])"+  , "    for mapping in get(s:mappings, a:name, [])"+  , "      silent! execute mode . 'unmap' mapping"+  , "    endfor"+  , "  endfor"+  , "  let name = substitute(tolower(a:name), '[^a-z0-9]', '', 'g')"+  , "  let au = has_key(s:autoload, name) && get(s:autoloads, s:autoload[name])"+  , "  if au"+  , "    call miv#{s:autoload[name]}#before_{name}()"+  , "  endif"+  , "  let newrtp = s:mivpath . a:name . '/'"+  , "  if !isdirectory(newrtp)"+  , "    return"+  , "  endif"+  , "  let rtps = split(&rtp, ',')"+  , "  let i = index(map(deepcopy(rtps), 'resolve(v:val)'), s:path)"+  , "  if i < 0"+  , "    let mivpath = get(filter(deepcopy(rtps), 'v:val =~# \"miv.\\\\?$\"'), 0, '')"+  , "    let i = max([0, index(deepcopy(rtps), mivpath)])"+  , "  endif"+  , "  let &rtp = join(insert(rtps, newrtp, i), ',')"+  , "  if isdirectory(newrtp . 'after/')"+  , "    exec 'set rtp+=' . newrtp . 'after/'"+  , "  endif"+  , "  for dir in filter(['plugin', 'after/plugin'], 'isdirectory(newrtp . v:val)')"+  , "    for file in split(glob(newrtp . dir . '/**/*.vim'), '\\n')"+  , "      silent! source `=file`"+  , "    endfor"+  , "  endfor"+  , "  if au"+  , "    call miv#{s:autoload[name]}#after_{name}()"+  , "  endif"+  , "  for n in get(s:dependedby, a:name, [])"+  , "    call miv#load(n)"+  , "  endfor"+  , "endfunction"+  ])
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}