├── .gitignore ├── Hobbes.hs ├── LICENSE ├── README.md ├── Setup.hs ├── hobbes.cabal └── stack.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | cabal-dev 3 | *.o 4 | *.hi 5 | *.chi 6 | *.chs.h 7 | .virtualenv 8 | .hsenv 9 | .cabal-sandbox/ 10 | cabal.sandbox.config 11 | cabal.config 12 | .stack-work/ 13 | 14 | hobbes 15 | -------------------------------------------------------------------------------- /Hobbes.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Control.Concurrent.Chan (getChanContents, newChan) 4 | import Data.Monoid ((<>)) 5 | import Options.Applicative (Parser, argument, execParser, 6 | fullDesc, header, helper, info, 7 | many, metavar, progDesc, str) 8 | import System.Exit (exitSuccess) 9 | import System.FilePath (splitFileName, takeFileName) 10 | import System.FilePath.GlobPattern (GlobPattern, (~~)) 11 | import System.FSNotify (Event (..), eventPath, 12 | watchTreeChan, withManager) 13 | import System.IO (BufferMode (NoBuffering), 14 | hSetBuffering, stdout) 15 | 16 | data Options = Options { paths :: [FilePath] } 17 | 18 | main :: IO () 19 | main = do 20 | hSetBuffering stdout NoBuffering 21 | getOptions >>= runWatcher 22 | exitSuccess 23 | 24 | getOptions :: IO Options 25 | getOptions = execParser opts 26 | where 27 | opts = 28 | info (helper <*> optionsParser) 29 | ( fullDesc 30 | <> progDesc "Echoes the filenames of modified files to stdout, \ 31 | \one file per line." 32 | <> header "hobbes - a file activity monitor" 33 | ) 34 | 35 | optionsParser :: Parser Options 36 | optionsParser = Options <$> (many . argument str . metavar $ "PATHS..") 37 | 38 | runWatcher :: Options -> IO () 39 | runWatcher (Options ps) = 40 | withManager $ \m -> do 41 | chan <- newChan 42 | mapM_ (watchPath m chan) ps 43 | getChanContents chan >>= mapM_ printPath 44 | where 45 | watchPath manager chan path = 46 | let (dir, glob) = splitFileName path 47 | in watchTreeChan manager dir (globModified glob) chan 48 | 49 | globModified :: GlobPattern -> Event -> Bool 50 | globModified _ (Removed _ _) = False 51 | globModified glob evt = matchesGlob glob evt 52 | 53 | matchesGlob :: GlobPattern -> Event -> Bool 54 | matchesGlob glob = fileMatchesGlob glob . takeFileName . eventPath 55 | 56 | printPath :: Event -> IO () 57 | printPath = putStrLn . eventPath 58 | 59 | fileMatchesGlob :: GlobPattern -> FilePath -> Bool 60 | fileMatchesGlob "" _ = True 61 | fileMatchesGlob "." _ = True 62 | fileMatchesGlob glob fp = fp ~~ glob 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Jason Hickner 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Jason Hickner nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Hobbes 2 | ====== 3 | 4 | [![Circle CI](https://circleci.com/gh/jhickner/hobbes.svg?style=svg)](https://circleci.com/gh/jhickner/hobbes) 5 | 6 | Hobbes is a small UNIX-style file watcher for ~~OSX~~ windows, linux and OSX (thanks @cgag), written after experiencing some OSX bugs with my usual standby [guard](https://github.com/guard/guard). The filenames of modified files are simply echoed to stdout, one file per line. You take it from there. 7 | 8 | Complex tasks can be accomplished by combining ```hobbes``` with other commands such as ```xargs```, for example: 9 | 10 | ```bash 11 | # automatic GHC recompile when your source files change 12 | hobbes "*.hs" | xargs -n1 ghc --make 13 | ``` 14 | 15 | ```xargs -n1 ``` means essentially "run the command on each word of input". So ```ghc --make``` is run on each modified file. 16 | 17 | 18 | Another example: I have a script called ```kick``` that reloads the current tab in Chrome and then refocuses iTerm. With this command to ```hobbes``` I get automatic browser reloading on every save. Script [here](https://gist.github.com/4081943) if you're interested. 19 | 20 | ```bash 21 | hobbes "*.html" | xargs -n1 kick 22 | ``` 23 | 24 | ```bash 25 | # running multiple commands when files change 26 | hobbes "*.elm" | xargs -n1 sh -c 'cmd1; cmd2; ...' 27 | ``` 28 | 29 | ### Installation with Stack 30 | 31 | Hobbes can be installed from sources with [stack](https://github.com/commercialhaskell/stack): 32 | 33 | ```shell 34 | $ git clone https://github.com/jhickner/hobbes.git 35 | $ cd hobbes 36 | $ stack install 37 | ``` 38 | 39 | ### Thanks 40 | 41 | Hobbes is a very small wrapper around fsnotify. 42 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /hobbes.cabal: -------------------------------------------------------------------------------- 1 | name: hobbes 2 | version: 0.3.0 3 | synopsis: A small file watcher for OSX 4 | -- description: 5 | homepage: http://github.com/jhickner/hobbes 6 | license: BSD3 7 | license-file: LICENSE 8 | author: Jason Hickner 9 | maintainer: jhickner@gmail.com 10 | -- copyright: 11 | category: System 12 | build-type: Simple 13 | cabal-version: >=1.8 14 | 15 | executable hobbes 16 | main-is: Hobbes.hs 17 | -- other-modules: 18 | build-depends: base >= 4 && < 5, 19 | filepath ==1.4.*, 20 | filemanip ==0.3.*, 21 | fsnotify >= 0.0.11, 22 | text >= 1.1.0.1, 23 | optparse-applicative >= 0.12 24 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | flags: {} 2 | packages: 3 | - '.' 4 | extra-deps: [] 5 | resolver: lts-5.13 6 | ghc-options: 7 | hobbes: -static -optl-static -optl-pthread 8 | --------------------------------------------------------------------------------