Skip to content
This repository
Browse code

Factored out searching for app files to Yi.Paths.

Now Yi uses ~/.yi/ directory to store:
 * 'yi.hs' config script
 * 'articles.db' for Yi.IReader
 * 'history' blob for Yi.PersistentState
 * 'local/Env.hs' for GHCi command evaluator environment

This patch refactors searching for four above files to Yi.Paths,
so that they can be consistently updated.
I did it in preparation for prioritizing XDG-style directories
as implemented by xdg-basedir - if they exist, or there is no Yi config
present.
If the XDG-style directories ~/.config/yi and ~/.local/share/yi
do not exist, but ~/.yi exists, then future patch will fall back
to current behaviour.

NOTE: Indirectly Yi also uses ~/.cache/yi/ for storing binaries with
configuration compiled in. This is however handled by dyre library.
And already consistent with XDG directory standard by xdg-basedir
package.
  • Loading branch information...
commit 8695beaa6f95a7486d98e6f83301cac466a28c4b 1 parent 3d33b52
mgajda authored August 15, 2012
3  yi/src/library/Yi/Boot.hs
@@ -17,6 +17,7 @@ import Yi.Editor
17 17
 import Yi.Keymap
18 18
 import Yi.Main
19 19
 import qualified Yi.UI.Common as UI
  20
+import qualified Yi.Paths(getConfigDir)
20 21
 
21 22
 -- | once the custom yi is compiled this restores the editor state (if requested) then proceeds to
22 23
 -- run the editor.
@@ -52,7 +53,7 @@ yiDriver cfg = do
52 53
                             { Dyre.projectName  = "yi"
53 54
                             , Dyre.realMain     = realMain
54 55
                             , Dyre.showError    = showErrorsInConf
55  
-                            , Dyre.configDir    = Just . getAppUserDataDirectory $ "yi"
  56
+                            , Dyre.configDir    = Just Yi.Paths.getConfigDir
56 57
                             , Dyre.hidePackages = ["mtl"]
57 58
                             , Dyre.ghcOpts      = (["-threaded", "-O2"] ++
58 59
 #ifdef PROFILING
8  yi/src/library/Yi/Config/Default.hs
@@ -16,6 +16,7 @@ import Yi.Command (cabalBuildE, cabalConfigureE, grepFind, makeBuild, reloadProj
16 16
 import {-# source #-} Yi.Boot
17 17
 import Yi.Config
18 18
 import Yi.Config.Misc
  19
+import Yi.Paths(getConfigFilename)
19 20
 import Yi.Core
20 21
 import Yi.Eval(publishedActions)
21 22
 import Yi.File
@@ -233,14 +234,13 @@ nilKeymap = choice [
233 234
     where configHelp = newBufferE (Left "configuration help") $ R.fromString $ unlines $
234 235
                          ["This instance of Yi is not configured.",
235 236
                           "To get a standard reasonable keymap, you can run yi with either --as=cua, --as=vim or --as=emacs.",
236  
-                          "You should however create your own ~/.yi/yi.hs file: ",
  237
+                          "You should however create your own ~/.config/yi/yi.hs file: ",
237 238
                           "You can type 'c', 'e' or 'v' now to create and edit it using a temporary cua, emacs or vim keymap."]
238 239
           openCfg km kmName = write $ do
239 240
             dataDir <- io getDataDir
240 241
             let exampleCfg = dataDir </> "example-configs" </> kmName
241  
-            homeDir <- io getHomeDirectory
242  
-            cfgDir <- io $ getAppUserDataDirectory "yi"
243  
-            let cfgFile = cfgDir </> "yi.hs"
  242
+            cfgFile <- getConfigFilename
  243
+            let cfgDir = takeDirectory cfgFile
244 244
             cfgExists <- io $ doesFileExist cfgDir
245 245
             io $ createDirectoryIfMissing True cfgDir -- so that the file can be saved
246 246
             discard $ editFile cfgFile -- load config file
7  yi/src/library/Yi/Eval.hs
@@ -23,8 +23,7 @@ import Data.List
23 23
 import Data.Monoid
24 24
 import Prelude hiding (error, (.))
25 25
 import qualified Language.Haskell.Interpreter as LHI
26  
-import System.FilePath
27  
-import System.Directory
  26
+import System.Directory(doesFileExist)
28 27
 import qualified Data.HashMap.Strict as M
29 28
 
30 29
 import Yi.Config.Simple.Types
@@ -32,6 +31,7 @@ import Yi.Core  hiding (concatMap)
32 31
 import Yi.File
33 32
 import Yi.Hooks
34 33
 import Yi.Regex
  34
+import qualified Yi.Paths(getEvaluatorContextFilename)
35 35
 
36 36
 -- | Runs the action, as written by the user.
37 37
 --
@@ -85,8 +85,7 @@ ghciEvaluator :: Evaluator
85 85
 ghciEvaluator = Evaluator{..} where
86 86
     execEditorActionImpl :: String -> YiM ()
87 87
     execEditorActionImpl s = do
88  
-       contextPath <- (</> ".yi" </> "local") <$> io getHomeDirectory
89  
-       let contextFile = contextPath </> "Env.hs"
  88
+       contextFile <- Yi.Paths.getEvaluatorContextFilename
90 89
        haveUserContext <- io $ doesFileExist contextFile
91 90
        res <- io $ LHI.runInterpreter $ do
92 91
            LHI.set [LHI.searchPath LHI.:= []]
11  yi/src/library/Yi/IReader.hs
@@ -23,6 +23,7 @@ import Yi.Buffer.Region (readRegionB)
23 23
 import Yi.Dynamic
24 24
 import Yi.Keymap (withBuffer, YiM)
25 25
 import Yi.Prelude (getA, putA, io, discard, Initializable(..))
  26
+import Yi.Paths(getArticleDbFilename)
26 27
 
27 28
 type Article = B.ByteString
28 29
 newtype ArticleDB = ADB { unADB :: Seq Article }
@@ -64,21 +65,17 @@ insertArticle (ADB adb) new = ADB (new <| adb)
64 65
 
65 66
 -- | Serialize given 'ArticleDB' out.
66 67
 writeDB :: ArticleDB -> YiM ()
67  
-writeDB adb = discard $ io . join . fmap (flip encodeFile adb) $ dbLocation
  68
+writeDB adb = discard $ io . join . fmap (flip encodeFile adb) $ getArticleDbFilename
68 69
 
69  
--- | Read in database from 'dbLocation' and then parse it into an 'ArticleDB'.
  70
+-- | Read in database from 'getArticleDbFilename' and then parse it into an 'ArticleDB'.
70 71
 readDB :: YiM ArticleDB
71  
-readDB = io $ (dbLocation >>= r) `catch` returnDefault
  72
+readDB = io $ (getArticleDbFilename >>= r) `catch` returnDefault
72 73
           where r = fmap (decode . BL.fromChunks . return) . B.readFile
73 74
                 -- We read in with strict bytestrings to guarantee the file is closed,
74 75
                 -- and then we convert it to the lazy bytestring data.binary expects.
75 76
                 -- This is inefficient, but alas...
76 77
                 returnDefault (_ :: SomeException) = return initial
77 78
 
78  
--- | The canonical location. We assume \~\/.yi has been set up already.
79  
-dbLocation :: IO FilePath
80  
-dbLocation = getHomeDirectory >>= \home -> return (home ++ "/.yi/articles.db")
81  
-
82 79
 -- | Returns the database as it exists on the disk, and the current Yi buffer contents.
83 80
 --   Note that the Initializable typeclass gives us an empty Seq. So first we try the buffer
84 81
 --   state in the hope we can avoid a very expensive read from disk, and if we find nothing
62  yi/src/library/Yi/Paths.hs
... ...
@@ -0,0 +1,62 @@
  1
+{-# LANGUAGE NoMonomorphismRestriction #-}
  2
+module Yi.Paths(
  3
+   getEvaluatorContextFilename
  4
+  ,getConfigFilename
  5
+  ,getArticleDbFilename
  6
+  ,getPersistentStateFilename
  7
+  ,getConfigDir
  8
+
  9
+  ,getConfigPath
  10
+  ,getDataPath
  11
+) where
  12
+
  13
+import System.Directory(getHomeDirectory, getAppUserDataDirectory, -- TODO: phase out in favour of xdg-dir
  14
+                        doesFileExist, createDirectoryIfMissing)
  15
+import System.FilePath((</>), takeDirectory)
  16
+import Control.Applicative((<$>))
  17
+import Control.Monad(filterM, Monad)
  18
+import Control.Monad.Trans(liftIO, MonadIO)
  19
+-- import qualified System.Environment.XDG.BaseDir as XDG
  20
+
  21
+-- TODO: These would be replaced by xdg-basedir equivalents
  22
+getConfigDir = liftIO $ getAppUserDataDirectory "yi"
  23
+getDataDir   = liftIO $ getAppUserDataDirectory "yi"
  24
+
  25
+-- | This is internal function that factors out common code from
  26
+--   getConfigPath and getDataPath
  27
+getPathHelper :: (MonadIO m) => (String -> IO FilePath) -> FilePath -> m FilePath
  28
+getPathHelper basePathGenerator fname = do
  29
+  baseDir <- liftIO $ basePathGenerator "yi"
  30
+  case fname of
  31
+    "" -> return baseDir -- calling for directory path
  32
+    _  -> return $ baseDir </> fname
  33
+
  34
+-- | Given a path relative to application data directory,
  35
+--   this function finds a path to a given data file.
  36
+getDataPath :: (MonadIO m) => FilePath -> m FilePath
  37
+getDataPath   = getPathHelper getAppUserDataDirectory
  38
+
  39
+-- | Given a path relative to application configuration directory,
  40
+--   this function finds a path to a given configuration file.
  41
+getConfigPath :: (MonadIO m) => FilePath -> m FilePath
  42
+getConfigPath = getPathHelper getAppUserDataDirectory
  43
+
  44
+-- Note: Dyre also uses XDG cache dir - that would be:
  45
+--getCachePath = getPathHelper XDG.getUserCacheDirectory
  46
+
  47
+-- Below are all points that are used in Yi code (to keep it clean.)
  48
+getEvaluatorContextFilename, getConfigFilename, getArticleDbFilename, getPersistentStateFilename :: (MonadIO m) => m FilePath
  49
+
  50
+-- | Get Yi master configuration script.
  51
+getConfigFilename           = getConfigPath "yi.hs"
  52
+
  53
+-- | Get articles.db database of locations to visit (for Yi.IReader.)
  54
+getArticleDbFilename       = getConfigPath "articles.db"
  55
+
  56
+-- | Get path to Yi history that stores state between runs.
  57
+getPersistentStateFilename  = getDataPath   "history"
  58
+
  59
+-- | Get path to environment file that defines namespace used by Yi
  60
+--   command evaluator.
  61
+getEvaluatorContextFilename = getConfigPath $ "local" </> "Env.hs"
  62
+
9  yi/src/library/Yi/PersistentState.hs
@@ -15,8 +15,7 @@ import Prelude hiding ((.))
15 15
 import Data.Binary
16 16
 import Data.DeriveTH
17 17
 import Data.Accessor.Template(nameDeriveAccessors)
18  
-import System.FilePath((</>))
19  
-import System.Directory(getAppUserDataDirectory, doesFileExist)
  18
+import System.Directory(doesFileExist)
20 19
 import qualified Data.Map as M
21 20
 
22 21
 import Control.Exc(ignoringException)
@@ -32,6 +31,7 @@ import Yi.Keymap.Vim.TagStack(VimTagStack(..), getTagStack, setTagStack)
32 31
 import Yi.KillRing(Killring(..))
33 32
 import Yi.Search(getRegexE, setRegexE)
34 33
 import Yi.Regex(SearchExp(..))
  34
+import Yi.Paths(getPersistentStateFilename)
35 35
 
36 36
 
37 37
 data PersistentState = PersistentState { histories     :: !Histories
@@ -55,11 +55,6 @@ $(nameDeriveAccessors ''MaxHistoryEntries (\n -> Just (n ++ "A")))
55 55
 maxHistoryEntries :: Field Int
56 56
 maxHistoryEntries = unMaxHistoryEntriesA . customVariable
57 57
 
58  
--- | Finds a path of history file.
59  
-getPersistentStateFilename :: YiM String
60  
-getPersistentStateFilename = do cfgDir <- io $ getAppUserDataDirectory "yi"
61  
-                                return $ cfgDir </> "history"
62  
-
63 58
 -- | Trims per-command histories to contain at most N completions each.
64 59
 trimHistories :: Int -> Histories -> Histories
65 60
 trimHistories maxHistory = M.map trimH
1  yi/yi.cabal
@@ -167,6 +167,7 @@ library
167 167
     Yi.Mode.Latex
168 168
     Yi.Modes
169 169
     Yi.Monad
  170
+    Yi.Paths
170 171
     Yi.PersistentState
171 172
     Yi.Prelude
172 173
     Yi.Process

0 notes on commit 8695bea

Please sign in to comment.
Something went wrong with that request. Please try again.