From aca82e59b0cc2be570a739e7ba5cceb9ca534a80 Mon Sep 17 00:00:00 2001 From: Moritz Rumpf Date: Fri, 6 Oct 2023 21:17:03 +0200 Subject: [PATCH] Bundle data files so that these do not need to be shipped together with the executable --- .github/workflows/binaries.yml | 2 +- app/{default => }/Main.hs | 0 app/bundled/Main.hs | 10 ---------- fints2ledger.cabal | 25 +++++++++---------------- package.yaml | 15 +++------------ src/Config/Files.hs | 22 ++++++++++++++++++---- src/Prompt.hs | 5 ++--- src/Transactions.hs | 33 +++++++++++++++++++-------------- test/TransactionSpec.hs | 7 +++---- 9 files changed, 55 insertions(+), 64 deletions(-) rename app/{default => }/Main.hs (100%) delete mode 100644 app/bundled/Main.hs diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml index 91d3424..a084c01 100644 --- a/.github/workflows/binaries.yml +++ b/.github/workflows/binaries.yml @@ -63,7 +63,7 @@ jobs: run: | export ARTIFACTS=${{ runner.os }}/fints2ledger mkdir -p ${ARTIFACTS} - cabal install --installdir="${ARTIFACTS}" --install-method=copy --enable-executable-static -fbundle_data_dir + cabal install --installdir="${ARTIFACTS}" --install-method=copy --enable-executable-static strip "${ARTIFACTS}/fints2ledger" cp -r data "${ARTIFACTS}" diff --git a/app/default/Main.hs b/app/Main.hs similarity index 100% rename from app/default/Main.hs rename to app/Main.hs diff --git a/app/bundled/Main.hs b/app/bundled/Main.hs deleted file mode 100644 index 87c32c4..0000000 --- a/app/bundled/Main.hs +++ /dev/null @@ -1,10 +0,0 @@ -module Main (main) where - -import Lib -import System.Environment (getExecutablePath, setEnv) -import System.FilePath (takeDirectory) - -main :: IO () -main = do - setEnv "fints2ledger_datadir" =<< (takeDirectory <$> getExecutablePath) - runFints2Ledger diff --git a/fints2ledger.cabal b/fints2ledger.cabal index 400266e..988b99e 100644 --- a/fints2ledger.cabal +++ b/fints2ledger.cabal @@ -1,6 +1,6 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.1. +-- This file has been generated from package.yaml by hpack version 0.35.2. -- -- see: https://github.com/sol/hpack @@ -30,11 +30,6 @@ source-repository head type: git location: https://github.com/MoritzR/fints2ledger -flag bundle_data_dir - description: Set the data dir to be relative to the executable for easier bundling - manual: True - default: False - library exposed-modules: App @@ -75,6 +70,7 @@ library , cryptohash-md5 ==0.11.101.* , dates >=0.2.3.2 && <0.2.4 , directory ==1.3.6.* + , file-embed ==0.0.15.* , filepath ==1.4.2.* , generic-lens ==2.2.2.* , haskeline ==0.8.2.* @@ -82,6 +78,7 @@ library , lens ==5.1.1 , optparse-applicative ==0.17.1.* , regex-tdfa ==1.3.2.* + , temporary , text ==1.2.5.* , text-format-heavy ==0.1.5.3 , time ==1.11.1.* @@ -96,6 +93,8 @@ executable fints2ledger main-is: Main.hs other-modules: Paths_fints2ledger + hs-source-dirs: + app default-extensions: DuplicateRecordFields OverloadedRecordDot @@ -114,6 +113,7 @@ executable fints2ledger , cryptohash-md5 ==0.11.101.* , dates >=0.2.3.2 && <0.2.4 , directory ==1.3.6.* + , file-embed ==0.0.15.* , filepath ==1.4.2.* , fints2ledger , generic-lens ==2.2.2.* @@ -122,6 +122,7 @@ executable fints2ledger , lens ==5.1.1 , optparse-applicative ==0.17.1.* , regex-tdfa ==1.3.2.* + , temporary , text ==1.2.5.* , text-format-heavy ==0.1.5.3 , time ==1.11.1.* @@ -131,16 +132,6 @@ executable fints2ledger , vty ==5.37 , yaml ==0.11.11.* default-language: GHC2021 - if (flag(bundle_data_dir)) - other-modules: - Main - hs-source-dirs: - app/bundled - else - other-modules: - Main - hs-source-dirs: - app/default test-suite fints2ledger-test type: exitcode-stdio-1.0 @@ -174,6 +165,7 @@ test-suite fints2ledger-test , cryptohash-md5 ==0.11.101.* , dates >=0.2.3.2 && <0.2.4 , directory ==1.3.6.* + , file-embed ==0.0.15.* , filepath ==1.4.2.* , fints2ledger , generic-lens ==2.2.2.* @@ -184,6 +176,7 @@ test-suite fints2ledger-test , regex-tdfa ==1.3.2.* , sydtest ==0.13.0.* , sydtest-discover ==0.0.0.* + , temporary , text ==1.2.5.* , text-format-heavy ==0.1.5.3 , time ==1.11.1.* diff --git a/package.yaml b/package.yaml index 25d0268..08b8af2 100644 --- a/package.yaml +++ b/package.yaml @@ -49,6 +49,8 @@ dependencies: - generic-lens >= 2.2.2 && < 2.2.3 # automatically generate lenses - cassava >= 0.5.3 && < 0.5.4 # csv encoding/decoding - vector >= 0.12.3 && < 0.12.4 # list-like type, used by cassava +- file-embed >= 0.0.15 && < 0.0.16 # for embedding files into the executable +- temporary # for creating temporary files language: GHC2021 @@ -77,12 +79,7 @@ library: executables: fints2ledger: main: Main.hs - when: - - condition: (flag(bundle_data_dir)) - then: - source-dirs: app/bundled - else: - source-dirs: app/default + source-dirs: app ghc-options: - -threaded - -rtsopts @@ -103,9 +100,3 @@ tests: - sydtest >= 0.13.0 && < 0.13.1 # testing library - sydtest-discover >= 0.0.0 && < 0.0.1 # automatic test discovery - QuickCheck == 2.14.3 # property based testing library - -flags: - bundle_data_dir: - description: Set the data dir to be relative to the executable for easier bundling - default: false - manual: true \ No newline at end of file diff --git a/src/Config/Files.hs b/src/Config/Files.hs index 5bf1816..b39f4a4 100644 --- a/src/Config/Files.hs +++ b/src/Config/Files.hs @@ -1,9 +1,14 @@ {-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE TemplateHaskell #-} -module Config.Files (getDefaultConfigDirectory, getConfigFilePath, getTemplatePath, ConfigDirectory (..)) where +module Config.Files (getDefaultConfigDirectory, getConfigFilePath, templateFile, exampleFile, pyfintsFile, ConfigDirectory (..)) where +import Data.ByteString (ByteString) +import Data.FileEmbed (embedDir) +import Data.Map (Map, fromList, (!)) import Data.String (IsString) -import Paths_fints2ledger (getDataFileName) +import Data.Text (Text) +import Data.Text.Encoding qualified as T import System.Directory (getHomeDirectory) import System.FilePath (()) @@ -15,8 +20,17 @@ getDefaultConfigDirectory = do getConfigFilePath :: ConfigDirectory -> FilePath getConfigFilePath configDirectory = configDirectory.get "config.yml" -getTemplatePath :: IO FilePath -getTemplatePath = getDataFileName "data/template.txt" +templateFile :: Text +templateFile = T.decodeUtf8 $ dataFiles ! "template.txt" + +exampleFile :: Text +exampleFile = T.decodeUtf8 $ dataFiles ! "example.json" + +pyfintsFile :: Text +pyfintsFile = T.decodeUtf8 $ dataFiles ! "pyfints.py" + +dataFiles :: Map FilePath ByteString +dataFiles = fromList ($(embedDir "data")) newtype ConfigDirectory = ConfigDirectory {get :: FilePath} deriving newtype (Show, IsString) diff --git a/src/Prompt.hs b/src/Prompt.hs index e192fd5..aae4143 100644 --- a/src/Prompt.hs +++ b/src/Prompt.hs @@ -2,7 +2,6 @@ module Prompt (transactionsToLedger) where import App (App, Env (..), PromptResult (..), printText) import Config.AppConfig (AppConfig (..)) -import Config.Files (getTemplatePath) import Config.YamlConfig (Fill, Filling (..), LedgerConfig (..)) import Control.Arrow ((>>>)) import Control.Monad (forM_, when) @@ -26,6 +25,7 @@ import Text.Regex.TDFA (AllTextMatches (getAllTextMatches), (=~)) import Transactions (Amount (..), Transaction (..)) import Utils (calculateMd5Value, formatDouble, toLazyTemplateMap) import Prelude hiding (appendFile, putStrLn, readFile) +import Config.Files (templateFile) {- | A Map of key/value pairs that will be used to fill the template file We fill these values step by step by: @@ -40,14 +40,13 @@ transactionsToLedger transactions = do config <- asks (.config) readFile <- asks (.readFile) existingMd5Sums <- getExistingMd5Sums <$> liftIO (readFile config.journalFile) - template <- liftIO $ readFile =<< getTemplatePath printText " Controls:" printText " - Ctrl + D or enter 's' to skip an entry" printText " - Ctrl + C to abort" forM_ transactions do - transactionToLedger existingMd5Sums template + transactionToLedger existingMd5Sums templateFile transactionToLedger :: Set Text -> Text -> Transaction -> App () transactionToLedger existingMd5Sums template transaction = do diff --git a/src/Transactions.hs b/src/Transactions.hs index aee9d6a..c17dfc2 100644 --- a/src/Transactions.hs +++ b/src/Transactions.hs @@ -12,6 +12,7 @@ module Transactions ( where import Config.AppConfig (AppConfig (..)) +import Config.Files (exampleFile, pyfintsFile) import Config.YamlConfig (FintsConfig (..), Password (..)) import Control.Exception (Exception, throwIO) import Data.Aeson (FromJSON, ToJSON) @@ -28,15 +29,15 @@ import Data.Time (Day, defaultTimeLocale, formatTime) import Data.Vector (toList) import GHC.Generics (Generic) import Hledger (getCurrentDay) -import Paths_fints2ledger (getDataFileName) import System.Console.Haskeline qualified as Haskeline +import System.IO.Temp (withSystemTempFile) import System.Process.Typed (ExitCode (ExitFailure, ExitSuccess), readProcess, shell) import Utils (encodeAsString, orElseThrow, (??)) +import System.IO (hFlush) getExampleTransactions :: IO [Transaction] getExampleTransactions = do - contents <- TIO.readFile =<< getDataFileName "data/example.json" - decodeTransactions contents `orElseThrow` TransactionDecodeError + decodeTransactions exampleFile `orElseThrow` TransactionDecodeError where decodeTransactions = Aeson.eitherDecode . TL.encodeUtf8 . TL.fromStrict @@ -53,7 +54,6 @@ convertTransactionsToCsv path = BS.writeFile path . Csv.encodeDefaultOrderedByNa getTransactionsFromFinTS :: AppConfig -> IO [Transaction] getTransactionsFromFinTS config = do currentDay <- getCurrentDay - pyfintsFilePath <- getDataFileName "data/pyfints.py" password <- maybe getPassword return config.fintsConfig.password let pyfintsArgs = @@ -66,20 +66,25 @@ getTransactionsFromFinTS config = do , start = formatDayForPython config.startDate , end = formatDayForPython currentDay } - let shellCommand = - shell $ - "FINTS2LEDGER_ARGS='" + + withSystemTempFile "fints2ledger.py" \path handle -> do + TIO.hPutStr handle pyfintsFile + hFlush handle + + let shellCommand = + shell + $ "FINTS2LEDGER_ARGS='" ++ encodeAsString pyfintsArgs ++ "' " ++ config.pythonExecutable ++ " " - ++ pyfintsFilePath - (exitCode, stdOut, stdErr) <- readProcess shellCommand - case exitCode of - ExitSuccess -> Aeson.eitherDecode stdOut `orElseThrow` TransactionDecodeError - ExitFailure _ -> do - TIO.putStrLn $ TL.toStrict $ TL.decodeUtf8 stdErr - throwIO $ PyFintsError "Failed to get FinTS transactions, check the message above." + ++ path + (exitCode, stdOut, stdErr) <- readProcess shellCommand + case exitCode of + ExitSuccess -> Aeson.eitherDecode stdOut `orElseThrow` TransactionDecodeError + ExitFailure _ -> do + TIO.putStrLn $ TL.toStrict $ TL.decodeUtf8 stdErr + throwIO $ PyFintsError "Failed to get FinTS transactions, check the message above." getPassword :: IO Password getPassword = do diff --git a/test/TransactionSpec.hs b/test/TransactionSpec.hs index d9dba8f..28e411f 100644 --- a/test/TransactionSpec.hs +++ b/test/TransactionSpec.hs @@ -1,9 +1,9 @@ module TransactionSpec (spec) where +import Config.Files (exampleFile) import Data.Aeson qualified as Aeson +import Data.Text.Lazy (fromStrict) import Data.Text.Lazy.Encoding (encodeUtf8) -import Data.Text.Lazy.IO as TLIO -import Paths_fints2ledger (getDataFileName) import Test.Syd (Spec, describe, it, shouldBe) import Transactions (Amount (Amount), Transaction (..)) @@ -11,8 +11,7 @@ spec :: Spec spec = do describe "Transactions" do it "parses the sample transactions" do - sampleTransaction <- TLIO.readFile =<< getDataFileName "data/example.json" - let parsedTransactions = Aeson.eitherDecode (encodeUtf8 sampleTransaction) + let parsedTransactions = Aeson.eitherDecode (encodeUtf8 $ fromStrict exampleFile) head <$> parsedTransactions `shouldBe` Right