mirror of
https://github.com/simonmichael/hledger.git
synced 2024-12-26 20:02:27 +03:00
176 lines
5.3 KiB
Haskell
Executable File
176 lines
5.3 KiB
Haskell
Executable File
#!/usr/bin/env stack
|
|
{- stack script --resolver lts-18.18
|
|
--package data-default
|
|
--package extra
|
|
--package process
|
|
--package text
|
|
-}
|
|
{-
|
|
--package hledger-lib
|
|
-}
|
|
-- changelog.hs CHANGELOGFILE
|
|
--
|
|
-- Manipulate a hledger changelog. Currently does one thing: prompts
|
|
-- for a rewrite of the oldest uncategorised pending changelog item
|
|
-- and updates the file, printing a diff.
|
|
--
|
|
-- My workflow:
|
|
-- - In a terminal window (not emacs shell, it will hang) run this on a CHANGES.md file
|
|
-- - Edit the changelog item to changelog-readiness (if needed)
|
|
-- - Save and quit (C-x #) to do the next; it will exit after the last.
|
|
-- - On the side keep an auto-reverting editor open on the file to watch progress or for fixups.
|
|
--
|
|
-- Motivation:
|
|
-- This might seem a bit pointless, but it made this old chore more pleasant.
|
|
-- I can make incremental progress by doing just one item, or more as I feel it,
|
|
-- without having all the other pending items drag on my attention.
|
|
--
|
|
-- Limitations/Wishes:
|
|
--
|
|
-- - Parsing of uncategorised pending items currently assumes the section headings
|
|
-- are Features, Improvements, Fixes. This does not work for other section headings,
|
|
-- as used in the project changelog. Workaround: add those three headings at the top.
|
|
--
|
|
-- - It's not obvious how to stop; C-x C-x just loads the next item.
|
|
|
|
{-# LANGUAGE DeriveAnyClass #-}
|
|
{-# LANGUAGE DeriveGeneric #-}
|
|
-- {-# LANGUAGE NamedFieldPuns #-}
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
|
|
import Data.Char
|
|
import Data.Default
|
|
import GHC.Generics
|
|
import Data.List.Extra
|
|
-- import qualified Data.Text as T
|
|
import System.Environment
|
|
import System.IO.Extra
|
|
-- import System.IO
|
|
import System.Process
|
|
import Text.Printf
|
|
-- import Hledger.Utils (toRegex')
|
|
|
|
-- A top level section in the changelog, corresponding to one release.
|
|
data ChangelogSection = ChangelogSection {
|
|
heading :: ChangelogHeading
|
|
,unknownitems :: [ChangelogItem]
|
|
,featureitems :: [ChangelogItem]
|
|
,improvementitems :: [ChangelogItem]
|
|
,fixitems :: [ChangelogItem]
|
|
}
|
|
deriving (Show, Eq, Generic, Default)
|
|
|
|
-- The one-line heading for a top level section in the changelog,
|
|
-- with the leading #(s) removed.
|
|
type ChangelogHeading = String
|
|
|
|
-- One change description in the changelog, with the list bullet and
|
|
-- corresponding indentation removed.
|
|
type ChangelogItem = String
|
|
|
|
main = do
|
|
(f:_) <- getArgs
|
|
go f
|
|
|
|
go f = do
|
|
-- read specified changelog
|
|
(preamble:first:rest) <- splitOn "\n# " <$> readFile f
|
|
let
|
|
g = f ++ ".new"
|
|
s@ChangelogSection{} = readSection first
|
|
|
|
-- ask for an edit of this item's text
|
|
s' <- editOneUnknown s
|
|
|
|
if s' == s
|
|
then return () -- if it's unchanged, quit
|
|
else do
|
|
-- otherwise write to a temp file
|
|
writeFile g $ init $ unlines $
|
|
preamble :
|
|
showSection s' :
|
|
map ("# "++) rest
|
|
-- and show the diff
|
|
system' $ printf "diff %s %s" f g
|
|
-- and replace the old file
|
|
system' $ printf "mv %s %s" g f
|
|
-- and repeat
|
|
go f
|
|
|
|
editOneUnknown :: ChangelogSection -> IO ChangelogSection
|
|
editOneUnknown s@ChangelogSection{..}
|
|
| null unknownitems = return s
|
|
| otherwise = do
|
|
let
|
|
s' = s{unknownitems=init unknownitems}
|
|
u = last unknownitems
|
|
new <- textEditEditor u
|
|
return $ case new of
|
|
'f':'e':'a':'t':':':' ':t -> s'{featureitems = readItem t : featureitems}
|
|
'f':'i':'x':':':' ':t -> s'{fixitems = readItem t : fixitems}
|
|
'i':'m':'p':':':' ':t -> s'{improvementitems = readItem t : improvementitems}
|
|
t -> s'{improvementitems = readItem t : improvementitems}
|
|
|
|
textEditEditor t = withTempFile $ \f -> do
|
|
writeFile f t
|
|
ed <- getEnv "EDITOR"
|
|
system $ printf "%s %s" ed f
|
|
readFile f
|
|
|
|
-- textEditTty u = do
|
|
-- putStrLn "Old:"
|
|
-- putStrLn u
|
|
-- putStrLn "New: (prefix with feat:/imp:/fix: to categorise, ctrl-d to finish):\n" -- Just an = keeps it unchanged, empty string removes it."
|
|
-- getContents
|
|
|
|
-- Parse a changelog section which may or may not have the Features/Improvements/Fixes subheadings.
|
|
readSection :: String -> ChangelogSection
|
|
readSection s =
|
|
let
|
|
(heading,rest) = break (=='\n') s
|
|
parts = splitOn "\nFeatures\n" rest
|
|
(unknownitems, featureitems, improvementitems, fixitems) =
|
|
case parts of
|
|
[] -> ([], [], [], [])
|
|
[u] -> (readItems u, [], [], [])
|
|
(u:xs:_) -> (readItems u, readItems f, readItems i, readItems x)
|
|
where
|
|
(f:ys:_) = splitOn "\nImprovements\n" xs
|
|
(i:x:_) = splitOn "\nFixes\n" ys
|
|
in ChangelogSection{..}
|
|
where
|
|
readItems = map readItem . filter (not.all isSpace) . splitOn "\n- "
|
|
|
|
showSection ChangelogSection{..} =
|
|
unlines $
|
|
[("# "++heading), ""]
|
|
++ map showItem unknownitems
|
|
++ ["Features", ""]
|
|
++ map showItem featureitems
|
|
++ ["Improvements", ""]
|
|
++ map showItem improvementitems
|
|
++ ["Fixes", ""]
|
|
++ map showItem fixitems
|
|
|
|
readItem :: String -> ChangelogItem
|
|
readItem "" = def
|
|
readItem s =
|
|
let
|
|
(first:rest) = lines s
|
|
stripto2spaces (' ':' ':t) = t
|
|
stripto2spaces (' ':t) = t
|
|
stripto2spaces t = t
|
|
in unlines $
|
|
first :
|
|
map stripto2spaces rest
|
|
|
|
showItem "" = ""
|
|
showItem i =
|
|
let (first:rest) = lines i
|
|
in unlines $ ("- "++first) : map (" "++) rest
|
|
|
|
system' s = putStrLn s >> system s
|
|
|
|
-- re = toRegex' . T.pack
|
|
|