{-
Copyright 2013,2014 Marcelo Millani
This file is part of boomange.
boomange is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
boomange is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with boomange. If not, see
-}
import Control.Exception
import Control.Monad
import System.IO
import System.Directory
import System.Environment
import System.FilePath
import Data.List
import Text.Printf
import Data.Tree
import Paths_boomange -- automatically generated
import Data.DescriLo as DescriLo
import Data.Simtreelo as Simtreelo
import Data.Builder
import Data.Entities hiding (name)
import Data.Loader
appName = "boomange"
appVersion = "0.1.3.0"
-- header to be used on the sample config file
configHeader =
"# This is the configuration file for " ++ appName ++ "\n" ++
"# For a full description of its syntax, see the haddock documentation of DescriLo\n" ++
"# In the config description:\n" ++
"# output - where should the resulting html file be placed\n" ++
"# header - file to be prepended to output\n" ++
"# footer - file to be appended to output\n" ++
"# there are no other values for config\n\n" ++
"# In the watch description, there may be an unlimited amount of values and the left part is always ignored by the program and may be used for organization.\n" ++
"# The right part indicates which file will be read to create the output. It will behave as if all of the files were concatenated.\n\n"
-- header to be used on the sample bookmarks file
bookmarksHeader =
"#\n" ++
"# This is a sample bookmarks file for " ++ appName ++ "\n" ++
"# For a full description of its syntax, see the haddock documentation for SimtreeLo\n" ++
"# The first line indicates the comment pattern" ++
"# Leaves represent the URI of their direct parents\n"
generateBookmarks config = do
header <- readFile $ headerFile config
footer <- readFile $ footerFile config
final <- openFile (outputFile config) WriteMode
bookmarks <- loadBookmarks $ watch config
let body = htmlBookmarks bookmarks
hPutStr final header
hPutStr final body
hPutStr final footer
hClose final
printf "Output written to %s\n" (outputFile config)
-- | gets the directory where configuration files should be placed
--
-- First, checks if XDG_CONFIG_HOME exists, producing $XDG_CONFIG_HOME/appName if it does
-- if it does not, the checks if HOME does, producing $HOME/.config/appName if it does
-- if it still fails, returns getAppUserDataDirectory appName
getConfigDirectory appName =
let failXDG e = do
dir <- getEnv "HOME"
return $ dir ++ [pathSeparator] ++ ".config" ++ [pathSeparator] ++ appName
failHOME e = getAppUserDataDirectory appName
in
do
handle (failHOME::SomeException -> IO FilePath) $
handle (failXDG::SomeException -> IO FilePath) $ do
dir <- getEnv "XDG_CONFIG_HOME"
return $ dir ++ [pathSeparator] ++ appName
-- | installs a basic configuration
installConfig cDir =
let htmlDir = cDir ++ [pathSeparator] ++ "html"
bookFile = cDir ++ [pathSeparator] ++ "bookmarks"
outFile = cDir ++ [pathSeparator] ++ "bookmarks.html"
config =
Description
{
name = "config"
, values = [
("output",outFile)
, ("header",htmlDir ++ [pathSeparator] ++ "header.html")
, ("footer", htmlDir ++ [pathSeparator] ++ "footer.html")
]
}
watch =
Description
{
name = "watch"
, values = [
("default",bookFile)
]
}
sampleBookmarks =
"Boomange\n\tDocumentation\n" ++
"\t\tDescriLo\n\t\t\thttp://hackage.haskell.org/package/descrilo-0.1.0.0/docs/Data-DescriLo.html\n" ++
"\t\tSimtreeLo\n\t\t\thttp://hackage.haskell.org/package/simtreelo-0.1.0.0/docs/Data-Simtreelo.html\n"
cFile = cDir ++ [pathSeparator] ++ "config"
in
do
-- creates the base config file
hcFile <- openFile cFile WriteMode
hPutStr hcFile configHeader
hPutStr hcFile $ show config
hPutStr hcFile $ show watch
hClose hcFile
-- copies the default html files
headerFile <- getDataFileName "html/header.html"
footerFile <- getDataFileName "html/footer.html"
cssFile <- getDataFileName "style.css"
-- creates the html folder
createDirectoryIfMissing True htmlDir
-- copies html files
copyFile headerFile $ htmlDir ++ [pathSeparator] ++ "header.html"
copyFile footerFile $ htmlDir ++ [pathSeparator] ++ "footer.html"
-- copies css file
copyFile cssFile $ cDir ++ [pathSeparator] ++ "style.css"
-- creates a sample bookmarks file
hBookmarks <- openFile bookFile WriteMode
hPutStr hBookmarks bookmarksHeader
hPutStr hBookmarks $ sampleBookmarks
hClose hBookmarks
data Action = Help | ConfigFile String | Version | Status | Add String String String | Invalid String | Generate deriving Eq
parseArgs args activeConfig = case args of
"-h":r -> Help : parseArgs r activeConfig
"--help":r -> Help : parseArgs r activeConfig
"-c":file:r -> ConfigFile file : parseArgs r file
"--config":file:r -> ConfigFile file : parseArgs r file
"-v":r -> Version : parseArgs r activeConfig
"--version":r -> Version : parseArgs r activeConfig
"-s":r -> Status : parseArgs r activeConfig
"--status":r -> Status : parseArgs r activeConfig
"-a":bookmark:uri:r -> Add bookmark uri activeConfig : parseArgs r activeConfig
"--add":bookmark:uri:r -> Add bookmark uri activeConfig : parseArgs r activeConfig
[] -> []
other:r -> Invalid other : Help : parseArgs r activeConfig
-- | Adds a bookmark to the correct file inside the configuration file given
--
-- | The bookmark is in the form "id/path/of/bookmark", where 'id' is the identifier of the simtreelo file to which the bookmark should be added and each slash indicates a new depth in the tree. The last value should be the URI of the bookmark.
--
-- | Existing depths will be reused (i.e., duplicates will not be generated).
addBookmark bookmark uri configFile = do
let (id, r) = span (/= '/') bookmark
config <- DescriLo.loadDescriptionFile configFile ""
let mwatch = find (\x -> DescriLo.name x == "watch") config
case mwatch of
Just watch -> let mid = find (\(x,y) -> x == id) $ DescriLo.values watch in
case mid of
Nothing -> printf "No bookmark file with id '%s' found in configuration file '%s'. Bookmark '%s' ignored.\n" id configFile bookmark
-- adds the bookmark to the respective simtreelo file
Just (foundId, treeFile) -> addBookmarkTree (tail r) uri treeFile
Nothing -> putStr "invalid configuration file"
addBookmarkTree bookmark uri treeFile = do
let bookmarkTree = pathToTree bookmark uri
oldForest <- Simtreelo.loadFile treeFile
case oldForest of
Left error -> printf "Failed loading '%s':\n\t%s" treeFile error
Right forest -> do
let newTree = Simtreelo.merge forest bookmarkTree
Simtreelo.write newTree "" "\t" treeFile
printf "Added '%s' to '%s'.\n" uri treeFile
pathToTree path uri =
let (label, r) = span (/= '/') path in
Node{rootLabel = label, subForest = if r == [] then [Node{rootLabel = uri, subForest = []}] else [ pathToTree (tail r) uri ] }
execute [] _ _ = return ()
execute (h:r) activeConfig configFiles =
case h of
Add bookmark uri configFile -> do
addBookmark bookmark uri configFile
Help -> do
printf "usage: %s [OPTION...]\n" appName
putStr $
"Options:\n" ++
" -h, --help shows this help text\n" ++
" -c, --config uses as config instead of the default\n" ++
" -v, --version outputs version and exits\n"++
" -s, --status outputs configuration file info and exits\n" ++
" -a, --add adds a bookmark to file with the given id.\n" ++
" The depths of should be separated\n" ++
" with '/'s.\n" ++
" Example: 'id/section'\n"
Version -> do
printf "%s %s\n" appName appVersion
putStr $ "Copyright (C) 2013,2014 Marcelo Garlet Millani\n" ++
"License GPLv3+: GNU GPL version 3 or later .\n" ++
"This is free software: you are free to change and redistribute it.\n" ++
"There is NO WARRANTY, to the extent permitted by law.\n"
Status ->
mapM showStatus configFiles >>= mapM_ (mapM_ putStrLn)
Invalid opt -> do
printf "'%s' is not a valid option or has an incorrect number of arguments\n" opt
Generate -> do
configs <- mapM loadConfig configFiles
mapM_ generateBookmarks configs
>> execute r activeConfig configFiles
showStatus :: String -> IO [String]
showStatus configFile = do
config <- loadConfig configFile
return $ printf "%s:" configFile :
(printf "\tOutput file:\n\t\t%s" (outputFile config)) :
(printf "\tHeader file:\n\t\t%s" (headerFile config)) :
(printf "\tFooter file:\n\t\t%s" (footerFile config)) :
"\tBookmarks files:" :
(map (\x -> "\t\t" ++ x) $ watch config)
{-
separateConfigs [] = ([],[])
separateConfigs (h:r) =
let (actions, configs) = separateConfigs r in
case h of
ConfigFile x -> (actions, x : configs)
other -> (other : actions, configs)
-}
main = do
args <- getArgs
let (argConfigs', argActions) = partition (\x -> case x of ConfigFile _ -> True ; _ -> False) $ parseArgs args ""
argConfigs = map (\(ConfigFile x) -> x) argConfigs'
-- if help, status or version were asked, does not generate bookmarks
actions = if elem Help argActions || elem Version argActions || elem Status argActions then argActions else (argActions ++ [Generate])
-- if no configuration file was given, uses the default one
configs <- if argConfigs == [] then do
cDir <- getConfigDirectory appName
-- if the configuration directory does not exists, sets it up
confExists <- doesDirectoryExist cDir
when (not confExists) $ do
createDirectoryIfMissing True cDir
installConfig cDir
return $ [cDir ++ "/config"]
else (return argConfigs)
execute actions "" configs
{-
cDir <- getConfigDirectory appName
-- if the configuration directory does not exists, sets it up
confExists <- doesDirectoryExist cDir
when (not confExists) $ do
createDirectoryIfMissing True cDir
installConfig cDir
let cFile = cDir ++ "/config"
config <- loadConfig cFile
generateBookmarks config
-}