;tools: changelog: a silly tool that makes changelog work more pleasant
This commit is contained in:
parent
8c57d70940
commit
8464fed4f6
140
bin/changelog.hs
Executable file
140
bin/changelog.hs
Executable file
@ -0,0 +1,140 @@
|
|||||||
|
#!/usr/bin/env stack
|
||||||
|
{- stack script --resolver nightly-2021-11-19
|
||||||
|
--package data-default
|
||||||
|
--package extra
|
||||||
|
--package hledger-lib
|
||||||
|
--package process
|
||||||
|
--package text
|
||||||
|
-}
|
||||||
|
{- stack ghc
|
||||||
|
--package text
|
||||||
|
-}
|
||||||
|
-- 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.
|
||||||
|
|
||||||
|
{-# 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
|
||||||
|
import System.Process
|
||||||
|
import Text.Printf
|
||||||
|
import Hledger.Utils
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
|
||||||
|
-- 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, Generic, Default)
|
||||||
|
|
||||||
|
main = do
|
||||||
|
(i:_) <- getArgs
|
||||||
|
|
||||||
|
-- read specified changelog
|
||||||
|
(preamble:first:rest) <- splitOn "\n# " <$> readFile i
|
||||||
|
let
|
||||||
|
o = i ++ ".new"
|
||||||
|
s@ChangelogSection{..} = readSection first
|
||||||
|
s' <- editOneUnknown s
|
||||||
|
|
||||||
|
-- write back to new file
|
||||||
|
writeFile o $ init $ unlines $
|
||||||
|
preamble :
|
||||||
|
showSection s' :
|
||||||
|
map ("# "++) rest
|
||||||
|
|
||||||
|
-- show the diff
|
||||||
|
system' $ printf "diff %s %s" i o
|
||||||
|
|
||||||
|
-- overwrite the old file
|
||||||
|
system' $ printf "mv %s %s" o i
|
||||||
|
|
||||||
|
editOneUnknown :: ChangelogSection -> IO ChangelogSection
|
||||||
|
editOneUnknown s@ChangelogSection{..}
|
||||||
|
| null unknownitems = return s
|
||||||
|
| otherwise = do
|
||||||
|
let u = last unknownitems
|
||||||
|
putStrLn "Old:\n"
|
||||||
|
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.\n"
|
||||||
|
i <- getContents
|
||||||
|
let s' =
|
||||||
|
case i 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}
|
||||||
|
return s'{unknownitems=init unknownitems}
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user