sh2md (empty) → 0.1.0.0
raw patch · 11 files changed
+380/−0 lines, 11 filesdep +Hclipdep +basedep +containerssetup-changed
Dependencies added: Hclip, base, containers, optparse-applicative, process, sh2md, text, transformers, unix
Files
- ChangeLog.md +3/−0
- LICENSE +30/−0
- README.md +31/−0
- Setup.hs +2/−0
- app/Main.hs +9/−0
- sh2md.cabal +88/−0
- src/RecordShellToMarkdown.hs +141/−0
- src/RecordShellToMarkdown/CLI.hs +23/−0
- src/RecordShellToMarkdown/Data.hs +32/−0
- src/RecordShellToMarkdown/Print.hs +19/−0
- test/Spec.hs +2/−0
+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for record-shell-to-markdown++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Mo Kweon (c) 2018++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 Mo Kweon 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,31 @@+# sh2md++Record your shell and print in the markdown format++<div align="center">+ <img src="./assets/demo.gif" width="100%">+</div>++```+sh2md --help+```++```+Usage: sh2md [--stdout]+ Record shell and print in markdown++Available options:+ --stdout Instead of copying to the clipboard, print the result+ to stdout+ -h,--help Show this help text+```+++## Build & Install++You need a [stack](https://www.haskellstack.org/)++```+stack build+stack install+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,9 @@+module Main where++import RecordShellToMarkdown (startShell)+import RecordShellToMarkdown.CLI (runCLIParser)++main :: IO ()+main = do+ parserInfo <- runCLIParser+ startShell parserInfo
+ sh2md.cabal view
@@ -0,0 +1,88 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.31.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: b3f2502d47043be2e62c6be650b0898ad16793fce66f66ce33b928e2eb2a08a7++name: sh2md+version: 0.1.0.0+synopsis: Record your shell session and print in the markdown format+description: Please see the README on GitHub at <https://github.com/kkweon/sh2md#readme>+category: Utilities+homepage: https://github.com/kkweon/sh2md#readme+bug-reports: https://github.com/kkweon/sh2md/issues+author: Mo Kweon+maintainer: kkweon@gmail.com+copyright: MIT+license: BSD3+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md+ ChangeLog.md++source-repository head+ type: git+ location: https://github.com/kkweon/sh2md++library+ exposed-modules:+ RecordShellToMarkdown+ RecordShellToMarkdown.CLI+ RecordShellToMarkdown.Data+ RecordShellToMarkdown.Print+ other-modules:+ Paths_sh2md+ hs-source-dirs:+ src+ build-depends:+ Hclip >=3.0.0 && <3.1+ , base >=4.7 && <5+ , containers >=0.5.11 && <0.6+ , optparse-applicative >=0.14.3 && <0.15+ , process >=1.6.3 && <1.7+ , text >=1.2.3 && <1.3+ , transformers >=0.5.5 && <0.6+ , unix >=2.7.2 && <2.8+ default-language: Haskell2010++executable sh2md+ main-is: Main.hs+ other-modules:+ Paths_sh2md+ hs-source-dirs:+ app+ ghc-options: -Wall -threaded -O3 -rtsopts -with-rtsopts=-N+ build-depends:+ Hclip >=3.0.0 && <3.1+ , base >=4.7 && <5+ , containers >=0.5.11 && <0.6+ , optparse-applicative >=0.14.3 && <0.15+ , process >=1.6.3 && <1.7+ , sh2md+ , text >=1.2.3 && <1.3+ , transformers >=0.5.5 && <0.6+ , unix >=2.7.2 && <2.8+ default-language: Haskell2010++test-suite sh2md-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Paths_sh2md+ hs-source-dirs:+ test+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ Hclip >=3.0.0 && <3.1+ , base >=4.7 && <5+ , containers >=0.5.11 && <0.6+ , optparse-applicative >=0.14.3 && <0.15+ , process >=1.6.3 && <1.7+ , sh2md+ , text >=1.2.3 && <1.3+ , transformers >=0.5.5 && <0.6+ , unix >=2.7.2 && <2.8+ default-language: Haskell2010
+ src/RecordShellToMarkdown.hs view
@@ -0,0 +1,141 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NamedFieldPuns #-}++{-|+Module : RecordShellToMarkdown+Description : Create an interactive shell and record the inputs & outputs. When an user interrupts, print the result in markdown format+Copyright : (c) Mo Kweon+License : MIT+Maintainer : kkweon@gmail.com+Stability : experimental+Portability : POSIX++This module exports 'startShell' which starts an interactive shell++> startShell++CTRL + C if you want to stop recording++Then it will copy the result to the system clipboard.+-}+module RecordShellToMarkdown+ ( startShell+ , runShellCommand+ , runCommand+ ) where++import Control.Concurrent.MVar (MVar, modifyMVar_, newMVar, readMVar)+import Control.Monad (unless)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Trans.State (execStateT, get, modify, put)+import Data.Sequence (Seq((:|>), Empty))+import qualified Data.Sequence as Seq+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import GHC.IO.Handle (BufferMode(NoBuffering), hGetContents, hSetBuffering)+import System.Hclip (setClipboard)+import System.IO (stdout)+import System.Posix.Signals (Handler(Catch), installHandler, sigINT)+import qualified System.Process as Process++import qualified RecordShellToMarkdown.CLI as CLI+import RecordShellToMarkdown.Data (RecordShellToMarkdownState, ShellIO(..))+import RecordShellToMarkdown.Print (joinSequence)++-- | Start an Interactive shell+--+-- When an user interrupt __CTRL+C__ ('sigINT') then it will stop the session and print markdown+startShell :: CLI.CLIOption -> IO ()+startShell CLI.CLIOption {cli_stdout} = do+ hSetBuffering stdout NoBuffering+ mVar <- newMVar False+ _ <- installHandler sigINT (Catch $ handleInterrupt mVar) Nothing+ shellIOs <- execStateT (foreverRunShellCommand mVar) Empty+ let result = Seq.foldMapWithIndex (\_ x -> buildMarkdown x) shellIOs+ if cli_stdout+ then TIO.putStrLn result+ else setClipboard (T.unpack result) >>+ TIO.putStrLn "The output is copied to your clipboard"+ where+ printAsMarkdown :: ShellIO -> IO ()+ printAsMarkdown = TIO.putStrLn . buildMarkdown+ buildMarkdown :: ShellIO -> T.Text+ buildMarkdown ShellIO {..} =+ let inputs = "```\n" <> T.strip (joinSequence shell_inputs) <> "\n```\n"+ in if T.null shell_output+ then inputs+ else inputs <> "```\n" <> T.strip shell_output <> "\n```\n"++-- | Handle CTRL-C Interrupt+handleInterrupt :: MVar Bool -> IO ()+handleInterrupt mVar = do+ putStrLn "\nPress any key to complete"+ modifyMVar_ mVar (\_ -> return True)++-- | Run shell until CTRL-C is handled+foreverRunShellCommand :: MVar Bool -> RecordShellToMarkdownState ()+foreverRunShellCommand mVar = do+ liftIO $ TIO.putStr "$ "+ cmd <- T.pack <$> liftIO getLine+ unless (T.null cmd) $ runShellCommand cmd+ interrupted <- liftIO $ readMVar mVar+ unless interrupted $ foreverRunShellCommand mVar++-- | Run Shell Command+-- This is a wrapper 'runCommand'+runShellCommand :: T.Text -> RecordShellToMarkdownState ()+runShellCommand cmd = do+ stack <- get+ shellIO <-+ case stack of+ Empty -> runCommand cmd Nothing+ (_ :|> x) -> runCommand cmd (Just . shell_wd $ x)+ unless (T.null . shell_output $ shellIO) $+ liftIO . TIO.putStrLn . T.strip $ shell_output shellIO++-- | Run a shell command+--+-- If the current working directory(CWD) is given, then run the command in the CWD+runCommand ::+ T.Text -- ^ Shell command+ -> Maybe T.Text -- ^ Current working directory+ -> RecordShellToMarkdownState ShellIO+runCommand cmd cwd = do+ (_, maybeHout, _, _) <-+ liftIO $+ Process.createProcess+ (Process.shell (T.unpack cmd ++ ";pwd"))+ { Process.std_out = Process.CreatePipe+ , Process.new_session = False+ , Process.cwd = T.unpack <$> cwd+ }+ case maybeHout of+ Just hout -> do+ content <- liftIO . (T.lines . T.pack <$>) $ hGetContents hout+ handleContent cmd content+ Nothing -> fail "Failed to create a stdout handle"++handleContent :: T.Text -> [T.Text] -> RecordShellToMarkdownState ShellIO+handleContent cmd content = do+ shellIOs <- get+ case shellIOs of+ (rest :|> lastShellIO@ShellIO {..}) ->+ if T.null shell_output+ then let newShellIO =+ lastShellIO+ { shell_inputs = shell_inputs :|> cmd+ , shell_output = shell_output <> T.unlines (init content)+ , shell_wd = last content+ }+ in put (rest :|> newShellIO) >> return newShellIO+ else addNewShellIO cmd content+ _ -> addNewShellIO cmd content++-- | Create a new ShellIO and add to the state+addNewShellIO :: T.Text -> [T.Text] -> RecordShellToMarkdownState ShellIO+addNewShellIO cmd content = do+ let shellIO =+ ShellIO (Seq.singleton cmd) (T.unlines (init content)) (last content)+ modify (:|> shellIO)+ return shellIO
+ src/RecordShellToMarkdown/CLI.hs view
@@ -0,0 +1,23 @@+module RecordShellToMarkdown.CLI where++import Options.Applicative++newtype CLIOption = CLIOption+ { cli_stdout :: Bool+ } deriving (Show)++cliOptionParser :: Parser CLIOption+cliOptionParser =+ CLIOption <$>+ switch+ (long "stdout" <>+ help "Instead of copying to the clipboard, print the result to stdout")++cliOptionParserInfo :: ParserInfo CLIOption+cliOptionParserInfo =+ info+ (cliOptionParser <**> helper)+ (fullDesc <> progDesc "Record shell and print in markdown")++runCLIParser :: IO CLIOption+runCLIParser = execParser cliOptionParserInfo
+ src/RecordShellToMarkdown/Data.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}++module RecordShellToMarkdown.Data+ ( ShellIO(..)+ , RecordShellToMarkdownState+ ) where++import Control.Monad.Trans.State (StateT)+import Data.Sequence (Seq(..))+import qualified Data.Sequence as Seq+import qualified Data.Text as T++import RecordShellToMarkdown.Print (joinSequence)++-- | Type Alias+--+-- This contains the entire shell session+type RecordShellToMarkdownState a = StateT (Seq ShellIO) IO a++-- | Shell Input and Output+data ShellIO = ShellIO+ { shell_inputs :: Seq T.Text -- ^ commands. If a command doesn't have stdout, it will be combined. For example, cd dir doesn't have an output so the next command will be concatenated to the command+ , shell_output :: !T.Text -- ^ output.+ , shell_wd :: T.Text -- ^ working directory when the output was printed+ }++instance Show ShellIO where+ show ShellIO {..} =+ T.unpack $+ joinSequence (fmap ("> " <>) shell_inputs) <> "\n" <> shell_output
+ src/RecordShellToMarkdown/Print.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE OverloadedStrings #-}++module RecordShellToMarkdown.Print+ ( joinSequence+ ) where++import Data.Sequence (Seq)+import qualified Data.Sequence as Seq+import qualified Data.Text as T++-- | Concat @Seq T.Text@ into 'T.Text'+joinSequence :: Seq T.Text -> T.Text+joinSequence = Seq.foldrWithIndex folder ""+ where+ folder :: Int -> T.Text -> T.Text -> T.Text+ folder _ value accumulator =+ if T.null value+ then accumulator+ else value <> "\n" <> accumulator
+ test/Spec.hs view
@@ -0,0 +1,2 @@+main :: IO ()+main = putStrLn "Test suite not yet implemented"