diff --git a/.gitignore b/.gitignore index 16b728b..7af935e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ dist/ dist-newstyle/ result .stack-work/ - +stack.yaml.lock diff --git a/Changelog.txt b/Changelog.txt index c66c3c7..e9d2755 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,5 +1,14 @@ Changelog +1.3, unreleased + - new config option: separator character. + - length limitation for filename lifted to maximum possible length. + - better help screen, UI hint how to open it with [h]. + - more keyboard shortcuts! + - launch file viewer in background. + - fix to sort library files by date. + Huge thanks to Nils Steinger (https://github.com/n-st) for his contributions to this release. + 1.2, 2019-04-21 - faster performance on large pdfs by only parsing the first couple of pages. - won't create the default directories until the first file is moved. diff --git a/README.md b/README.md index 290d6c1..6025249 100644 --- a/README.md +++ b/README.md @@ -86,5 +86,6 @@ You're very welcome to suggest new features or open issues. See the Roadmap http - [brick](https://github.com/jtdaugherty/brick) is a lovely way to a write a command-line UI. - [nmattia](https://github.com/nmattia) did the work to get Paperboy to build with [nix](https://github.com/NixOS/nix) and patiently explained some of the basics to me. - OsugiSakae and bri-an on reddit helped me with an annoying performance issue! +- [Nils Steinger](https://github.com/n-st) for feature contributions. The name 'Paperboy' is a reference to [this game](https://en.wikipedia.org/wiki/Paperboy_(video_game)), which I had for the NES and never quite mastered. diff --git a/nix/nixpkgs-src.json b/nix/nixpkgs-src.json index d980d8e..25aa70e 100644 --- a/nix/nixpkgs-src.json +++ b/nix/nixpkgs-src.json @@ -1,6 +1,6 @@ { "owner": "NixOS", "repo": "nixpkgs", - "rev": "4b5098e79aa29e73aa9059e530ceac65f3a49cb5", - "sha256": "1920vprnxridy0ql6fjsi4iqxj7q14lf9fqryjfakxa88i3bp370" + "rev": "e8a36cdf57193e56514aa6eeff936159372f0ace", + "sha256": "1jxdqphacpzkvwpkw67w1222jnmyplzall4n9sdwznyipxz6bqsv" } diff --git a/pboy.cabal b/pboy.cabal index 50878f5..92812dd 100644 --- a/pboy.cabal +++ b/pboy.cabal @@ -1,7 +1,7 @@ cabal-version: 2.4 name: pboy -version: 1.2 +version: 1.3 synopsis: a small .pdf management utility description: Please see the README on Github at homepage: https://github.com/2mol/pboy#readme diff --git a/src/Config.hs b/src/Config.hs index 931f53d..1bfe37d 100644 --- a/src/Config.hs +++ b/src/Config.hs @@ -8,6 +8,7 @@ module Config , libraryDir , importAction , ImportAction(..) + , wordSeparator , tryGetConfig , defaultConfig , createConfig @@ -18,8 +19,9 @@ import qualified Control.Exception as E import Data.Function ((&)) import Data.Ini.Config.Bidir (Ini, IniSpec, (.=)) import qualified Data.Ini.Config.Bidir as C +import Data.Text (Text) import qualified Data.Text.IO as TIO -import Lens.Micro ((^.)) +import Lens.Micro ((^.), (.~)) import Lens.Micro.TH (makeLenses) import Path (Abs, Dir, File, Path, Rel, ()) import qualified Path @@ -31,6 +33,7 @@ data Config = Config , _inboxDirs :: [Path Abs Dir] , _libraryDir :: Path Abs Dir , _importAction :: ImportAction + , _wordSeparator:: Text } @@ -42,6 +45,7 @@ data ConfigData = ConfigData { _inboxDirsD :: [FilePath] , _libraryDirD :: FilePath , _importMove :: Bool + , _wordSeparatorD :: Text } deriving Show @@ -55,6 +59,7 @@ defaultConfigData = { _inboxDirsD= ["Downloads"] , _libraryDirD = "papers" , _importMove = True + , _wordSeparatorD = "_" } @@ -80,10 +85,18 @@ tryGetConfig configPath = do configIniResult = configTxtResult >>= (\t -> C.parseIni t configIni) - configResult = + configDataResult = C.getIniValue <$> configIniResult - sequence $ readConfigData <$> configResult + -- special case: allow " " in the .ini to be interpreted as + -- a single space. Ugly but neceessary. + inferSpace :: Config -> Config + inferSpace conf = + if conf ^. wordSeparator == "\" \"" + then conf & wordSeparator .~ " " + else conf + configResult <- sequence $ readConfigData <$> configDataResult + pure $ inferSpace <$> configResult readConfigData :: ConfigData -> IO Config @@ -93,12 +106,14 @@ readConfigData configData = do libDir <- Path.resolveDir home (configData ^. libraryDirD) let action = if configData ^. importMove then Move else Copy + let wordSep = configData ^. wordSeparatorD pure Config { _homeDir = home , _inboxDirs = inbDir , _libraryDir = libDir , _importAction = action + , _wordSeparator = wordSep } @@ -113,7 +128,7 @@ configSpec = ] libraryDirD .= C.field "library" C.string - & C.comment ["The folder to copy/move renamed files to."] + & C.comment [ "The folder to copy/move renamed files to." ] importMove .= C.field "move" C.bool & C.comment @@ -121,6 +136,12 @@ configSpec = , "If set to false it will leave the original file unchanged." ] + wordSeparatorD .= C.field "wordseparator" C.text + & C.comment + [ "The character to insert between words." + , "Write \" \" for space." + ] + configIni :: Ini ConfigData configIni = C.ini defaultConfigData configSpec diff --git a/src/Lib.hs b/src/Lib.hs index ed8830b..1f2ab0c 100644 --- a/src/Lib.hs +++ b/src/Lib.hs @@ -29,6 +29,7 @@ import qualified Path import qualified Path.IO as Path import qualified System.FilePath as F import qualified System.Process as P +import qualified System.Directory import qualified Text.PDF.Info as PDFI data FileInfo = FileInfo @@ -116,7 +117,9 @@ getTopLines file = do lengthCheck :: Text -> Bool -lengthCheck t = T.length t >= 3 && T.length t <= 64 +lengthCheck t = + -- maximum filename length on most Unix FSs (255) - extension (.pdf, 4) = 251 + T.length t >= 3 && T.length t <= 251 boolToMaybe :: (a -> Bool) -> a -> Maybe a @@ -151,11 +154,11 @@ validChars x = -- shelving files into library folder -finalFileName :: Text -> Text -finalFileName text = +finalFileName :: Config -> Text -> Text +finalFileName conf text = text & T.unwords . T.words - & T.replace " " "_" + & T.replace " " (conf ^. Config.wordSeparator) fileFile :: Config -> Text -> Path Abs File -> IO () @@ -164,7 +167,7 @@ fileFile conf newFileName file = do newFile <- Path.parseRelFile (T.unpack newFileName <> Path.fileExtension file) let newFilePath = - conf ^. Config.libraryDir newFile + (conf ^. Config.libraryDir) newFile case conf ^. Config.importAction of Config.Copy -> Path.copyFile file newFilePath @@ -173,15 +176,21 @@ fileFile conf newFileName file = do openFile :: Path Abs File -> IO () openFile file = do - linuxOpen <- tryOpenWith file "xdg-open" + tryOpenWithMany ["xdg-open", "open"] file - if Either.isLeft linuxOpen - then do - _ <- tryOpenWith file "open" - pure () - else pure () +tryOpenWithMany :: [String] -> Path Abs File -> IO () +tryOpenWithMany [] _ = pure () +tryOpenWithMany (e:es) file = do + exe <- System.Directory.findExecutable e + case exe of + Nothing -> tryOpenWithMany es file + Just _ -> tryOpenWith e file -tryOpenWith :: Path Abs File -> FilePath -> IO (Either SomeException String) -tryOpenWith file cmd = - E.try (P.readProcess cmd [Path.fromAbsFile file] "") + +tryOpenWith :: FilePath -> Path Abs File -> IO () +tryOpenWith exePath file = do + P.createProcess + ( P.proc exePath [Path.fromAbsFile file] ) + { P.std_out = P.NoStream, P.std_err = P.NoStream } + pure () diff --git a/src/UI.hs b/src/UI.hs index 08ed6f2..12339df 100644 --- a/src/UI.hs +++ b/src/UI.hs @@ -120,9 +120,10 @@ initState = do refreshFiles :: State -> IO State refreshFiles s = do - libraryFileInfos <- Lib.listFiles (s ^. config . Config.libraryDir) + libraryFileInfos_ <- Lib.listFiles (s ^. config . Config.libraryDir) inboxFileInfos_ <- mapM Lib.listFiles (s ^. config . Config.inboxDirs) let + libraryFileInfos = Lib.sortFileInfoByDate libraryFileInfos_ inboxFileInfos = Lib.sortFileInfoByDate $ join inboxFileInfos_ libraryList = L.list Library (Vec.fromList libraryFileInfos) 1 inboxList = L.list Inbox (Vec.fromList inboxFileInfos) 1 @@ -230,7 +231,7 @@ drawUI s = <> " " <> inboxLabel , fill ' ' - , str " -> " + , str " press h for help " , fill ' ' , str $ libraryLabel <> " [Library]" ] @@ -320,33 +321,38 @@ handleFirstStartEvent s e = Nothing -> continue s -handleLibraryEvent :: State -> V.Event -> EventM ResourceName (Next State) -handleLibraryEvent s e = +openAction :: State -> EventM n (Next State) +openAction s = let openFile fileName = do _ <- liftIO $ Lib.openFile fileName continue s + in + case L.listSelectedElement (s ^. library) of + Just (_, fileInfo) -> openFile (Lib._fileName fileInfo) + _ -> continue s - openAction = - case L.listSelectedElement (s ^. library) of - Just (_, fileInfo) -> openFile (Lib._fileName fileInfo) - _ -> continue s - renameAction = +handleLibraryEvent :: State -> V.Event -> EventM ResourceName (Next State) +handleLibraryEvent s e = + let renameAction = case L.listSelectedElement (s ^. library) of Just (_, fileInfo) -> beginFileImport s fileInfo _ -> continue s in case e of V.EvKey V.KEnter [] -> - openAction + openAction s V.EvKey (V.KChar ' ') [] -> - openAction + openAction s V.EvKey (V.KChar 'r') [] -> renameAction + V.EvKey (V.KChar 'o') [] -> + openAction s + _ -> do newLibrary <- L.handleListEvent e (s ^. library) continue (s & library .~ newLibrary) @@ -370,6 +376,9 @@ handleInboxEvent s e = V.EvKey (V.KChar 'r') [] -> importAction + V.EvKey (V.KChar 'o') [] -> + openAction s + _ -> do newInbox <- L.handleListEvent e (s ^. inbox) continue (s & inbox .~ newInbox) @@ -395,7 +404,7 @@ handleImportScreenEvent fi s ev = fi ^. nameEdit & E.getEditContents & T.unlines - & Lib.finalFileName + & Lib.finalFileName conf _ <- liftIO $ Lib.fileFile conf newFileName (fi ^. currentFile) @@ -524,14 +533,17 @@ helpScreen cpath (Just d) = [ "Welcome to PAPERBOY!" , "====================" , " " - , "[Enter] or [Space]:" - , " - from inbox: start import/rename." - , " - from library: open pdf." - , " " , "[Tab] - switch between inbox and library." , " " + , "[Enter] or [Space]:" + , " - from inbox: start import/rename." + , " - from library: open pdf." + , "[r] - rename file" + , "[o] - open file" + , " " , "[Esc] or [q] - quit from main screen." , "[Ctrl-c] - quit from any screen." + , "[h] - this help screen." , " " , "Your config file is at" , Path.fromAbsFile cpath diff --git a/stack.yaml b/stack.yaml index bd41c99..fd0fce7 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,10 +1,6 @@ -resolver: lts-13.17 +resolver: lts-14.3 packages: - . extra-deps: -- brick-0.47@sha256:4936c50acfdf09620dad5217fb384fc0d59626f75abed8b48250b419ec2ab623 -- config-ini-0.2.4.0@sha256:38a6d484d471c6fac81445de2eac8c4e8c82760962fca5491ae1c3bfca9c4047 -- data-clist-0.1.2.2@sha256:4d70add0a200a178853cd37c6469101bac3c36aebb3aa9c503ff225211b1a8c9 -- text-zipper-0.10.1@sha256:8b73a97a3717a17df9b0a722b178950c476ff2268ca5c583e99d010c94af849e -- word-wrap-0.4.1@sha256:f72233b383ef569c557bfd9812cbb8e306c415ce509082c0bd15ee51c0239ccc \ No newline at end of file +