merge-bash-history (empty) → 0.1.0.0
raw patch · 7 files changed
+252/−0 lines, 7 filesdep +attoparsecdep +basedep +data-ordlistsetup-changed
Dependencies added: attoparsec, base, data-ordlist, errors, merge-bash-history, optparse-applicative, text
Files
- LICENSE +30/−0
- README.md +50/−0
- Setup.hs +2/−0
- app/Main.hs +35/−0
- merge-bash-history.cabal +49/−0
- src/MergeBashHistory.hs +84/−0
- test/Spec.hs +2/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Jiri Marsicek (c) 2015++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Jiri Marsicek nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,50 @@+# merge-bash-history++Utility to be used with unison (http://www.cis.upenn.edu/~bcpierce/unison/) to merge `.bash_history` file accross multiple machines.++# Installation++Clone the repository++```+git clone https://github.com/j1r1k/merge-bash-history.git+```++Install using stack+```+cd merge-bash-history+stack install+```++# Example configuration in unison preferences file++Assuming you have root set to your home directory and `merge-bash-history` is in your $PATH++Add following to your unsion .prf file++```+merge = Name .bash_history -> merge-bash-history CURRENT1 CURRENT2 > NEW+backupcurrent = Name .bash_history+```++# Manual usage++```+merge-bash-history [INPUT-FILE1] [INPUT-FILE2] > [MERGED-FILE]+```++# Limitations+- Does not support line deletion (e.g. manual removal of lines from .bash_history file)++# Warnings+- Sorts .bash_history file by timestamp if not sorted+- removes duplicates (comparing timestamp and command)++# Recommended Bash settings++```+shopt -s histappend+PROMPT_COMMAND="${PROMPT_COMMAND};history -a"+```++From Pawel Hajdan (http://phajdan-jr.blogspot.de/2015/03/more-reliable-handling-of-bash-history.html)
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,35 @@+module Main where++import MergeBashHistory++import Data.Text.IO as TIO (readFile, putStr)++import Options.Applicative++data HmbhO = HmbhO FilePath FilePath++opParseFilePath :: Parser FilePath+opParseFilePath = argument str $ metavar "FILE"++opParseHmbhO :: Parser HmbhO+opParseHmbhO = HmbhO <$> opParseFilePath+ <*> opParseFilePath++execOpParserHmbhO :: IO HmbhO+execOpParserHmbhO = execParser $ info (helper <*> opParseHmbhO) (fullDesc <> header "bash_history merger")++handleFile :: FilePath -> IO [HistoryRecord]+handleFile fp =+ do i <- TIO.readFile fp+ case parseFile i of Left e -> error $ show e+ Right r -> return r++hmbhHandler :: HmbhO -> IO ()+hmbhHandler (HmbhO fp1 fp2) =+ do f1 <- handleFile fp1+ f2 <- handleFile fp2+ mapM_ (TIO.putStr . recordToText) $ mergeRecords f1 f2+++main :: IO ()+main = execOpParserHmbhO >>= hmbhHandler
+ merge-bash-history.cabal view
@@ -0,0 +1,49 @@+name: merge-bash-history+version: 0.1.0.0+synopsis: Initial project template from stack+description: Please see README.md+homepage: http://github.com/j1r1k/merge-bash-history#readme+license: BSD3+license-file: LICENSE+author: Jiri Marsicek+maintainer: jiri.marsicek@gmail.com+copyright: 2015 Jiri Marsicek+category: Tools+build-type: Simple+extra-source-files: README.md+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: MergeBashHistory+ build-depends: base >= 4.7 && < 5+ , attoparsec >= 0.12+ , data-ordlist >= 0.4+ , errors >= 2+ , text >= 1.2+ ghc-options: -Wall+ default-language: Haskell2010++executable merge-bash-history+ hs-source-dirs: app+ main-is: Main.hs+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends: base+ , merge-bash-history+ , optparse-applicative >=0.11+ , text >= 1.2+ ghc-options: -Wall+ default-language: Haskell2010++test-suite MergeBashHistory-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Spec.hs+ build-depends: base+ , merge-bash-history+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010++source-repository head+ type: git+ location: https://github.com/j1r1k/merge-bash-history
+ src/MergeBashHistory.hs view
@@ -0,0 +1,84 @@+module MergeBashHistory+ ( Timestamp+ , Command+ , HmbhError+ , HistoryRecord+ , mergeRecords+ , parseFile+ , recordToText+ ) where++import Control.Applicative ((<|>))++import Data.EitherR (fmapL)+import Data.List.Ordered (merge, nub)+import Data.Text (Text)+import qualified Data.Text as Text++import Data.Attoparsec.Text+++type Timestamp = Integer+type Command = Text++data HistoryLine = Header Timestamp+ | Command Command+ deriving (Show)++data HmbhError = ParseError String+ | EmptyFile+ | MissingHeader++instance Show HmbhError where+ show (ParseError e) = "Error: Parser Error '" ++ e ++ "'"+ show EmptyFile = "Error: Merging with empty file"+ show MissingHeader = "Error: Missing header"++data HistoryRecord = HistoryRecord { header :: Timestamp+ , commands :: [Command]+ } deriving (Eq, Show)++instance Ord HistoryRecord where+ h1 `compare` h2 = header h1 `compare` header h2++recordToText :: HistoryRecord -> Text+recordToText (HistoryRecord t c) = Text.unlines $ Text.pack ('#' : show t) : c++emptyRecord :: Timestamp -> HistoryRecord+emptyRecord t = HistoryRecord { header = t, commands = [] }++appendCommand :: HistoryRecord -> Command -> HistoryRecord+appendCommand hr c = hr { commands = commands hr ++ [c] }++linesToRecords :: [HistoryLine] -> Either HmbhError [HistoryRecord]+linesToRecords [] = Left EmptyFile+linesToRecords (Command _: _) = Left MissingHeader+linesToRecords (Header t: as) = Right $ squash $ foldr f (emptyRecord t, []) as+ where f (Header s) acc = (emptyRecord s, squash acc)+ f (Command c) (current, ps) = (appendCommand current c, ps)+ squash (x, xs) = x : xs++historyHeader :: Parser HistoryLine+historyHeader =+ do _ <- char '#'+ t <- decimal+ endOfLine+ return $ Header t++historyCommand :: Parser HistoryLine+historyCommand =+ do c <- takeTill isEndOfLine+ endOfLine+ return $ Command c++historyLine :: Parser HistoryLine+historyLine = historyHeader <|> historyCommand++historyLines :: Parser [HistoryLine]+historyLines = many' historyLine++mergeRecords :: [HistoryRecord] -> [HistoryRecord] -> [HistoryRecord]+mergeRecords a b = nub $ merge a b++parseFile :: Text -> Either HmbhError [HistoryRecord]+parseFile t = fmapL ParseError (parseOnly historyLines t) >>= linesToRecords
+ test/Spec.hs view
@@ -0,0 +1,2 @@+main :: IO ()+main = putStrLn "Test suite not yet implemented"