;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