module Music.Prelude.CmdLine (
converterMain,
lilypondConverterMain,
translateFile,
translateFileAndRunLilypond,
version,
versionString
) where
import Control.Exception
import Data.Version (showVersion)
import Data.Monoid
import Options.Applicative
#ifndef GHCI
import qualified Paths_music_preludes as Paths
#endif
import Data.Char
import Data.List (intercalate, isPrefixOf)
import Data.List.Split
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
import Prelude hiding (readFile, writeFile)
import System.Environment
import System.Exit
import System.FilePath
import System.IO
import System.IO.Temp
#ifndef mingw32_HOST_OS
import qualified System.Posix.Env as PE
#endif
import System.Process
import qualified Music.Prelude.Basic as PreludeBasic
import qualified Music.Prelude.Standard as PreludeStandard
#ifndef GHCI
version = Paths.version
#else
version = error "Could not get version"
#endif
versionString :: String
versionString = showVersion version
data ConverterOptions = ConverterOptions {
prelude :: Maybe String,
outFile :: Maybe FilePath,
inFile :: FilePath
} deriving (Show)
converterOptions :: Parser ConverterOptions
converterOptions = liftA3 ConverterOptions
(optional $ strOption $ mconcat [long "prelude", metavar "<name>"])
(optional $ strOption $ mconcat [short 'o', long "output", metavar "<file>"])
(argument str $ metavar "<input>")
converterMain
:: String
-> String
-> IO ()
converterMain func ext = do
pgmName <- getProgName
options <- execParser (opts pgmName)
runConverter func ext options
where
opts pgmName = info
(helper <*> converterOptions)
(fullDesc <> header (pgmName ++ "-" ++ versionString))
runConverter func ext (ConverterOptions prelude outFile inFile)
= translateFile func ext prelude (Just inFile) outFile
data LilypondConverterOptions = LilypondConverterOptions {
_prelude :: Maybe String,
_inFile :: FilePath
} deriving (Show)
lilypondConverterOptions :: Parser LilypondConverterOptions
lilypondConverterOptions = liftA2 LilypondConverterOptions
(optional $ strOption $ mconcat [long "prelude", metavar "<name>"])
(argument str $ metavar "<input>")
lilypondConverterMain
:: String
-> IO ()
lilypondConverterMain ext = do
pgmName <- getProgName
options <- execParser (opts pgmName)
runLilypondConverter ext options
where
opts pgmName = info
(helper <*> lilypondConverterOptions)
(fullDesc <> header (pgmName ++ "-" ++ versionString))
runLilypondConverter ext (LilypondConverterOptions prelude inFile)
= translateFileAndRunLilypond ext prelude (Just inFile)
translateFileAndRunLilypond
:: String
-> Maybe String
-> Maybe FilePath
-> IO ()
translateFileAndRunLilypond format preludeName' inFile' = do
let inFile = fromMaybe "test.music" inFile'
let preludeName = fromMaybe "basic" preludeName'
let lyFile = takeBaseName inFile ++ ".ly"
translateFile "writeLilypond" "ly" (Just preludeName) (Just inFile) (Just lyFile)
rawSystem "lilypond" ["--" ++ format, "-o", takeBaseName inFile, lyFile]
runCommand $ "rm -f " ++ takeBaseName inFile ++ "-*.tex " ++ takeBaseName inFile ++ "-*.texi " ++ takeBaseName inFile ++ "-*.count " ++ takeBaseName inFile ++ "-*.eps " ++ takeBaseName inFile ++ "-*.pdf " ++ takeBaseName inFile ++ ".eps"
return ()
translateFile
:: String
-> String
-> Maybe String
-> Maybe FilePath
-> Maybe FilePath
-> IO ()
translateFile translationFunction outSuffix preludeName' inFile' outFile' = do
code <- readFile inFile
newScore <- return $ if isNotExpression code
then expand declTempl (Map.fromList [
("prelude" , prelude),
("main" , main),
("scoreType" , scoreType),
("code" , code),
("outFile" , outFile)
])
else expand exprTempl (Map.fromList [
("prelude" , prelude),
("main" , main),
("scoreType" , scoreType),
("score" , code),
("outFile" , outFile)
])
withSystemTempDirectory "music-suite." $ \tmpDir -> do
let tmpFile = tmpDir ++ "/" ++ takeFileName inFile
let opts = ["-XOverloadedStrings", "-XNoMonomorphismRestriction", "-XTypeFamilies"]
putStrLn $ "Converting music..."
writeFile tmpFile newScore
withMusicSuiteInScope $ do
putStrLn $ "Writing '" ++ outFile ++ "'..."
rawSystem "runhaskell" (opts <> [tmpFile]) >>= \e -> if e == ExitSuccess then return () else fail ("Could not convert"++inFile)
return ()
where
inFile = fromMaybe "test.music" inFile'
preludeName = fromMaybe "basic" preludeName'
outFile = fromMaybe (
takeDirectory inFile ++ "/"
++ takeBaseName inFile
++ "." ++ outSuffix)
outFile'
prelude = "Music.Prelude." ++ toCamel preludeName
scoreType = "Score " ++ toCamel preludeName ++ "Note"
main = translationFunction
exprTempl = "module Main where { import $(prelude); {-# LINE 1 \"" ++ inFile ++ "\" #-}\nmain = $(main) \"$(outFile)\" ( $(score) :: $(scoreType) ) }"
declTempl = "module Main where \nimport $(prelude) {-# LINE 1 \"" ++ inFile ++ "\" #-}\n$(code) \nmain = $(main) \"$(outFile)\" ( example :: $(scoreType) )"
isNotExpression :: String -> Bool
isNotExpression t = anyLineStartsWith "type" t || anyLineStartsWith "data" t || anyLineStartsWith "example =" t
anyLineStartsWith :: String -> String -> Bool
anyLineStartsWith t = any (t `isPrefixOf`) . lines
type Template = String
expand :: Template -> Map String String -> String
expand t vs = (composed $ fmap (expander vs) $ Map.keys $ vs) t
where
expander vs k = replace ("$(" ++ k ++ ")") (fromJust $ Map.lookup k vs)
composed :: [a -> a] -> a -> a
composed = foldr (.) id
replace :: Eq a => [a] -> [a] -> [a] -> [a]
replace old new = intercalate new . splitOn old
toCamel [] = []
toCamel (x:xs) = toUpper x : xs
withMusicSuiteInScope :: IO a -> IO a
withMusicSuiteInScope k = do
r <- try $ readProcess "music-util" ["package-path"] ""
case r of
Left x -> let _ = (x::SomeException) in withEnv "GHC_PACKAGE_PATH" (const "") k
Right packagePath -> withEnv "GHC_PACKAGE_PATH" (const packagePath) k
withEnv :: String -> (Maybe String -> String) -> IO a -> IO a
#ifdef mingw32_HOST_OS
withEnv _ _ = id
#else
withEnv n f k = do
x <- PE.getEnv n
PE.setEnv n (f x) True
res <- k
case x of
Nothing -> PE.unsetEnv n >> return res
Just x2 -> PE.setEnv n x2 True >> return res
#endif