packages feed

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 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"