From e5eb53adceaa5ba6e0a9e3e897e5b8dde053a703 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:34:31 +0200 Subject: [PATCH 01/15] Add spec for newline detection --- test/Core/NewlineSpec.hs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 test/Core/NewlineSpec.hs diff --git a/test/Core/NewlineSpec.hs b/test/Core/NewlineSpec.hs new file mode 100644 index 00000000..59bb68fd --- /dev/null +++ b/test/Core/NewlineSpec.hs @@ -0,0 +1,17 @@ +module Core.NewlineSpec (spec) where + +import JbeamEdit.Core.Newline (detectNewline) +import System.IO (Newline (..)) +import Test.Hspec + +spec :: Spec +spec = + describe "detectNewline" $ do + it "detects LF for unix line endings" $ + detectNewline "a\nb\n" `shouldBe` LF + it "detects CRLF for windows line endings" $ + detectNewline "a\r\nb\r\n" `shouldBe` CRLF + it "detects CRLF when line endings are mixed" $ + detectNewline "a\r\nb\n" `shouldBe` CRLF + it "defaults to LF when there is no line ending" $ + detectNewline "abc" `shouldBe` LF From 37078edbed219c0ddd7f6ce574f5e9f5977e9309 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sat, 8 Aug 2026 23:49:15 +0200 Subject: [PATCH 02/15] Implemented newline detection support --- jbeam-edit.cabal | 2 ++ src/JbeamEdit/Core/Newline.hs | 14 ++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 src/JbeamEdit/Core/Newline.hs diff --git a/jbeam-edit.cabal b/jbeam-edit.cabal index a60dfd62..2bcb3ec6 100644 --- a/jbeam-edit.cabal +++ b/jbeam-edit.cabal @@ -63,6 +63,7 @@ flag windows-example-paths library exposed-modules: + JbeamEdit.Core.Newline JbeamEdit.Core.Node JbeamEdit.Core.NodeCursor JbeamEdit.Core.NodePath @@ -262,6 +263,7 @@ test-suite jbeam-edit-test build-tool-depends: hspec-discover:hspec-discover hs-source-dirs: test other-modules: + Core.NewlineSpec Core.NodeCursorSpec Core.NodePathSpec Core.NodeSpec diff --git a/src/JbeamEdit/Core/Newline.hs b/src/JbeamEdit/Core/Newline.hs new file mode 100644 index 00000000..6071d102 --- /dev/null +++ b/src/JbeamEdit/Core/Newline.hs @@ -0,0 +1,14 @@ +module JbeamEdit.Core.Newline (detectNewline) where + +import Data.ByteString.Lazy as LBS +import Data.ByteString.Lazy.Char8 as LBS8 (elemIndex) +import System.IO (Newline (..)) + +detectNewline :: LBS.ByteString -> Newline +detectNewline content = + case LBS8.elemIndex '\r' content of + Nothing -> LF + Just idx -> + case LBS.index content (idx + 1) of + 10 -> CRLF -- '\n' + _ -> detectNewline (LBS.drop (idx + 1) content) From 44349fb13ec78f90bb26bef565dfb70d585a160e Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sat, 8 Aug 2026 23:51:14 +0200 Subject: [PATCH 03/15] Added GHC 9.8.4 to ghc list --- jbeam-edit.cabal | 2 +- package.yaml | 2 +- src-extra/transformation/JbeamEdit/Transformation.hs | 2 +- .../transformation/JbeamEdit/Transformation/BeamValidation.hs | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/jbeam-edit.cabal b/jbeam-edit.cabal index 2bcb3ec6..aaeb4bd4 100644 --- a/jbeam-edit.cabal +++ b/jbeam-edit.cabal @@ -11,7 +11,7 @@ license-file: LICENSE copyright: 2025 webdevred maintainer: example@example.com author: webdevred -tested-with: ghc ==9.10.3 ghc ==9.14.1 +tested-with: ghc ==9.8.4 ghc ==9.10.3 ghc ==9.14.1 homepage: https://github.com/webdevred/jbeam-edit#readme bug-reports: https://github.com/webdevred/jbeam-edit/issues synopsis: diff --git a/package.yaml b/package.yaml index e07ccea6..0ea56827 100644 --- a/package.yaml +++ b/package.yaml @@ -25,7 +25,7 @@ description: >- and can automatically rename nodes and update references. Custom formatting rules are supported via JBFL (JBeam Formatting Language). See the README for usage instructions and examples: https://github.com/webdevred/jbeam-edit#readme -tested-with: [GHC == 9.10.3, GHC == 9.14.1] +tested-with: [GHC == 9.8.4, GHC == 9.10.3, GHC == 9.14.1] dependencies: - base >= 4.19 diff --git a/src-extra/transformation/JbeamEdit/Transformation.hs b/src-extra/transformation/JbeamEdit/Transformation.hs index 8e043502..34681f0e 100644 --- a/src-extra/transformation/JbeamEdit/Transformation.hs +++ b/src-extra/transformation/JbeamEdit/Transformation.hs @@ -5,7 +5,7 @@ import Data.Bifunctor (first) import Data.Bool (bool) import Data.Foldable.Extra (notNull) import Data.Function (on) -import Data.List (partition) +import Data.List (foldl', partition) import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty qualified as NE import Data.Map (Map) diff --git a/src-extra/transformation/JbeamEdit/Transformation/BeamValidation.hs b/src-extra/transformation/JbeamEdit/Transformation/BeamValidation.hs index f0c3aee3..e4e34df3 100644 --- a/src-extra/transformation/JbeamEdit/Transformation/BeamValidation.hs +++ b/src-extra/transformation/JbeamEdit/Transformation/BeamValidation.hs @@ -7,6 +7,7 @@ module JbeamEdit.Transformation.BeamValidation ( ) where import Control.Monad (forM_, unless) +import Data.List (foldl') import Data.List.NonEmpty (toList) import Data.Map qualified as M import Data.Set (Set) From 5b28fb07819041c775998c91a80300c10c710417 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sat, 8 Aug 2026 23:55:47 +0200 Subject: [PATCH 04/15] Ran fourmolu --- src/JbeamEdit/Core/Newline.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/JbeamEdit/Core/Newline.hs b/src/JbeamEdit/Core/Newline.hs index 6071d102..15c21338 100644 --- a/src/JbeamEdit/Core/Newline.hs +++ b/src/JbeamEdit/Core/Newline.hs @@ -10,5 +10,5 @@ detectNewline content = Nothing -> LF Just idx -> case LBS.index content (idx + 1) of - 10 -> CRLF -- '\n' - _ -> detectNewline (LBS.drop (idx + 1) content) + 10 -> CRLF -- '\n' + _ -> detectNewline (LBS.drop (idx + 1) content) From 96637779f800314022305b6eb755806f2250f844 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sun, 9 Aug 2026 00:04:52 +0200 Subject: [PATCH 05/15] Fixed impartial function call --- src/JbeamEdit/Core/Newline.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/JbeamEdit/Core/Newline.hs b/src/JbeamEdit/Core/Newline.hs index 15c21338..9c402f4d 100644 --- a/src/JbeamEdit/Core/Newline.hs +++ b/src/JbeamEdit/Core/Newline.hs @@ -9,6 +9,6 @@ detectNewline content = case LBS8.elemIndex '\r' content of Nothing -> LF Just idx -> - case LBS.index content (idx + 1) of - 10 -> CRLF -- '\n' + case LBS.uncons (LBS.drop (idx+1) content) of + Just (10, _) -> CRLF -- '\n' _ -> detectNewline (LBS.drop (idx + 1) content) From 76038e7da8a2619f6a3c0f61b9228fd344d7b231 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sun, 9 Aug 2026 00:36:33 +0200 Subject: [PATCH 06/15] Wire the new newline detection into the IO code --- exe/jbeam-edit/Main.hs | 9 +++++---- src/JbeamEdit/Formatting.hs | 19 ++++++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/exe/jbeam-edit/Main.hs b/exe/jbeam-edit/Main.hs index c65c9e25..b19b15b2 100644 --- a/exe/jbeam-edit/Main.hs +++ b/exe/jbeam-edit/Main.hs @@ -12,6 +12,7 @@ import JbeamEdit.IOUtils import JbeamEdit.Parsing.Jbeam (parseNodes) import System.Directory.OsPath import System.Environment (getArgs) +import System.IO (Newline) import System.OsPath #ifdef ENABLE_WINDOWS_NEWLINES @@ -49,15 +50,15 @@ editFile opts = do createBackupFile filename opts contents <- tryReadFile [] filename case contents >>= parseNodes of - Right ns -> processNodes opts filename ns formattingConfig + Right ns -> processNodes (detectNewline contents) opts filename ns formattingConfig Left err -> putErrorLine err Nothing -> putErrorLine "missing arg filename" -processNodes :: Options -> OsPath -> Node -> RuleSet -> IO () -processNodes opts outFile nodes formattingConfig = do +processNodes :: Newline -> Options -> OsPath -> Node -> RuleSet -> IO () +processNodes newline opts outFile nodes formattingConfig = do transformedNode <- applyTransform formattingConfig opts nodes case transformedNode of - Right transformedNode' -> formatNodeAndWrite formattingConfig outFile transformedNode' + Right transformedNode' -> formatNodeAndWrite newline formattingConfig outFile transformedNode' Left err -> putErrorLine err applyTransform :: RuleSet -> Options -> Node -> IO (Either Text Node) diff --git a/src/JbeamEdit/Formatting.hs b/src/JbeamEdit/Formatting.hs index 320f0a06..b5a51c1c 100644 --- a/src/JbeamEdit/Formatting.hs +++ b/src/JbeamEdit/Formatting.hs @@ -10,8 +10,9 @@ module JbeamEdit.Formatting ( emptyState, ) where +import System.IO import Data.Bool (bool) -import Data.ByteString.Lazy qualified as LBS (fromStrict) +import Data.ByteString.Lazy qualified as LBS (fromStrict,hPutStr) import Data.Char (isSpace) import Data.Foldable.Extra (notNull) import Data.Map.Strict (Map) @@ -51,7 +52,7 @@ import JbeamEdit.Formatting.Rules ( ) import JbeamEdit.Formatting.Rules.ComplexNewLine qualified as CNL import JbeamEdit.Formatting.Rules.TrailingComma qualified as TC -import System.File.OsPath qualified as OS (writeFile) +import System.File.OsPath qualified as OS import System.OsPath (OsPath) data FormattingState = FormattingState @@ -468,13 +469,17 @@ replaceNewlines = id #endif formatNodeAndWrite - :: RuleSet + :: Newline + -> RuleSet -> OsPath -> Node -> IO () -formatNodeAndWrite rs outFile = - OS.writeFile outFile - . LBS.fromStrict +formatNodeAndWrite newline rs outFile node = do + handle <- OS.openFile outFile WriteMode + hSetNewlineMode handle (NewlineMode LF newline) + LBS.hPutStr handle . + LBS.fromStrict . encodeUtf8 . replaceNewlines - . formatNode rs + $ formatNode rs node + hClose handle From de26d4b03d4eb78aad054235c0ea160d397cc385 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sun, 9 Aug 2026 00:45:22 +0200 Subject: [PATCH 07/15] Use the the newline detection in cross file update code --- .../transformation/JbeamEdit/Transformation.hs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src-extra/transformation/JbeamEdit/Transformation.hs b/src-extra/transformation/JbeamEdit/Transformation.hs index 34681f0e..081e5142 100644 --- a/src-extra/transformation/JbeamEdit/Transformation.hs +++ b/src-extra/transformation/JbeamEdit/Transformation.hs @@ -24,6 +24,7 @@ import Data.Traversable (mapAccumL) import Data.Vector (Vector, (!), (!?), (//)) import Data.Vector qualified as V import GHC.IsList +import JbeamEdit.Core.Newline import JbeamEdit.Core.Node import JbeamEdit.Core.NodeCursor (newCursor) import JbeamEdit.Core.NodeCursor qualified as NC @@ -492,12 +493,15 @@ filterJbeamFiles excludedFilenames = filter go updateOtherFiles :: RuleSet -> UpdateNamesMap -> OsPath -> IO () updateOtherFiles formattingConfig updatedNames filepath = do contents <- tryReadFile [] filepath - case contents >>= parseNodes of - Right node -> - let node' = findAndUpdateTextInNode updatedNames newCursor node - in when - (node /= node') - (formatNodeAndWrite formattingConfig filepath node') + case contents of + Right contents' -> + case parseNodes contents' of + Right node -> + let node' = findAndUpdateTextInNode updatedNames newCursor node + in when + (node /= node') + (formatNodeAndWrite (detectNewline contents') formattingConfig filepath node') + Left err -> putErrorLine err Left err -> putErrorLine err transform From 1e38334543e9847d0bf1bd04150cc83c0e5b9464 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sun, 9 Aug 2026 00:47:10 +0200 Subject: [PATCH 08/15] Ran fourmolu --- src/JbeamEdit/Core/Newline.hs | 2 +- src/JbeamEdit/Formatting.hs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/JbeamEdit/Core/Newline.hs b/src/JbeamEdit/Core/Newline.hs index 9c402f4d..fb11267f 100644 --- a/src/JbeamEdit/Core/Newline.hs +++ b/src/JbeamEdit/Core/Newline.hs @@ -9,6 +9,6 @@ detectNewline content = case LBS8.elemIndex '\r' content of Nothing -> LF Just idx -> - case LBS.uncons (LBS.drop (idx+1) content) of + case LBS.uncons (LBS.drop (idx + 1) content) of Just (10, _) -> CRLF -- '\n' _ -> detectNewline (LBS.drop (idx + 1) content) diff --git a/src/JbeamEdit/Formatting.hs b/src/JbeamEdit/Formatting.hs index b5a51c1c..42151fe1 100644 --- a/src/JbeamEdit/Formatting.hs +++ b/src/JbeamEdit/Formatting.hs @@ -10,9 +10,8 @@ module JbeamEdit.Formatting ( emptyState, ) where -import System.IO import Data.Bool (bool) -import Data.ByteString.Lazy qualified as LBS (fromStrict,hPutStr) +import Data.ByteString.Lazy qualified as LBS (fromStrict, hPutStr) import Data.Char (isSpace) import Data.Foldable.Extra (notNull) import Data.Map.Strict (Map) @@ -53,6 +52,7 @@ import JbeamEdit.Formatting.Rules ( import JbeamEdit.Formatting.Rules.ComplexNewLine qualified as CNL import JbeamEdit.Formatting.Rules.TrailingComma qualified as TC import System.File.OsPath qualified as OS +import System.IO import System.OsPath (OsPath) data FormattingState = FormattingState @@ -477,8 +477,8 @@ formatNodeAndWrite formatNodeAndWrite newline rs outFile node = do handle <- OS.openFile outFile WriteMode hSetNewlineMode handle (NewlineMode LF newline) - LBS.hPutStr handle . - LBS.fromStrict + LBS.hPutStr handle + . LBS.fromStrict . encodeUtf8 . replaceNewlines $ formatNode rs node From da50ecd0526d5b006fdfb20f828ae4d9db184b3c Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sun, 9 Aug 2026 00:53:08 +0200 Subject: [PATCH 09/15] Fix --- exe/jbeam-edit/Main.hs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/exe/jbeam-edit/Main.hs b/exe/jbeam-edit/Main.hs index b19b15b2..e131583c 100644 --- a/exe/jbeam-edit/Main.hs +++ b/exe/jbeam-edit/Main.hs @@ -5,6 +5,7 @@ module Main ( import CommandLineOptions import Control.Monad (when) import Data.Text (Text) +import JbeamEdit.Core.Newline import JbeamEdit.Core.Node (Node) import JbeamEdit.Formatting (RuleSet, formatNodeAndWrite) import JbeamEdit.Formatting.Config @@ -49,8 +50,11 @@ editFile opts = do Just filename -> do createBackupFile filename opts contents <- tryReadFile [] filename - case contents >>= parseNodes of - Right ns -> processNodes (detectNewline contents) opts filename ns formattingConfig + case contents of + Right contents' -> + case parseNodes contents' of + Right ns -> processNodes (detectNewline contents') opts filename ns formattingConfig + Left err -> putErrorLine err Left err -> putErrorLine err Nothing -> putErrorLine "missing arg filename" From f19d17a798bb2d45cc93c0d6377ecf7b643ded9c Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sun, 9 Aug 2026 18:42:33 +0200 Subject: [PATCH 10/15] Write files back with the line endings they came with hSetNewlineMode only applies to text-mode writes, so the ByteString output went straight past it and every file came out with LF. Rewrite the endings in the text instead. Makes the ENABLE_WINDOWS_NEWLINES flag redundant. --- .github/scripts/check_newline_preservation.sh | 67 ++++ .github/workflows/build-and-test.yaml | 5 + exe/jbeam-edit/Main.hs | 4 - jbeam-edit.cabal | 365 ++++++++---------- package.yaml | 2 - src/JbeamEdit/Core/Newline.hs | 7 +- src/JbeamEdit/Formatting.hs | 31 +- 7 files changed, 255 insertions(+), 226 deletions(-) create mode 100644 .github/scripts/check_newline_preservation.sh diff --git a/.github/scripts/check_newline_preservation.sh b/.github/scripts/check_newline_preservation.sh new file mode 100644 index 00000000..f2617fd9 --- /dev/null +++ b/.github/scripts/check_newline_preservation.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# +# Checks that formatting a file keeps the line endings it came with. The +# detection is exercised through the binary rather than the library, because +# reading the file, detecting the ending and writing it back out are three +# separate steps and any one of them can drop it. +# +# Usage: bash ./.github/scripts/check_newline_preservation.sh + +set -euo pipefail + +BINARY=$(realpath "${1:?usage: check_newline_preservation.sh }") +SOURCE_FILE="examples/jbeam/frame.jbeam" + +WORK_DIR=$(mktemp -d) +trap 'rm -rf "$WORK_DIR"' EXIT + +# The binary runs from the work directory, so point it back at the repo for the +# default ruleset it ships as a data file. +export jbeam_edit_datadir="$PWD" + +failed=0 + +carriage_returns() { + grep -c $'\r' "$1" || true +} + +check() { + local label="$1" expected="$2" target="$WORK_DIR/$1.jbeam" + + if [[ "$label" == crlf ]]; then + sed 's/\r$//; s/$/\r/' "$SOURCE_FILE" >"$target" + else + sed 's/\r$//' "$SOURCE_FILE" >"$target" + fi + + local before after + before=$(carriage_returns "$target") + if [[ "$before" -ne "$expected" ]]; then + echo "setup error: $label input has $before carriage returns, expected $expected" + failed=1 + return + fi + + (cd "$WORK_DIR" && "$BINARY" "$label.jbeam" >/dev/null) + + after=$(carriage_returns "$target") + if [[ "$expected" -eq 0 && "$after" -ne 0 ]]; then + echo "$label: line endings changed, LF input came out with $after carriage returns" + failed=1 + elif [[ "$expected" -ne 0 && "$after" -eq 0 ]]; then + echo "$label: line endings changed, CRLF input came out with no carriage returns" + failed=1 + fi +} + +lf_lines=$(sed 's/\r$//' "$SOURCE_FILE" | wc -l) + +check lf 0 +check crlf "$lf_lines" + +if [[ "$failed" -ne 0 ]]; then + echo "formatting does not preserve line endings" + exit 1 +fi + +echo "line endings survive a format for both LF and CRLF input" diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 9bfff6b2..42196181 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -198,3 +198,8 @@ jobs: run: cabal build --project-file cabal.project.ci all - name: Run tests (GHC ${{ steps.setup-ghc.outputs.ghc-version }}) run: cabal test --project-file cabal.project.ci + - name: Check that formatting preserves line endings + shell: bash + run: | + bash ./.github/scripts/check_newline_preservation.sh \ + "$(cabal list-bin --project-file cabal.project.ci jbeam-edit)" diff --git a/exe/jbeam-edit/Main.hs b/exe/jbeam-edit/Main.hs index e131583c..f037284d 100644 --- a/exe/jbeam-edit/Main.hs +++ b/exe/jbeam-edit/Main.hs @@ -16,10 +16,6 @@ import System.Environment (getArgs) import System.IO (Newline) import System.OsPath -#ifdef ENABLE_WINDOWS_NEWLINES -import Data.Text qualified as T -#endif - #ifdef ENABLE_TRANSFORMATION import JbeamEdit.Transformation import JbeamEdit.Transformation.Config diff --git a/jbeam-edit.cabal b/jbeam-edit.cabal index aaeb4bd4..63dff072 100644 --- a/jbeam-edit.cabal +++ b/jbeam-edit.cabal @@ -1,4 +1,4 @@ -cabal-version: 2.2 +cabal-version: 2.2 -- This file has been generated from package.yaml by hpack version 0.38.3. -- @@ -11,21 +11,19 @@ license-file: LICENSE copyright: 2025 webdevred maintainer: example@example.com author: webdevred -tested-with: ghc ==9.8.4 ghc ==9.10.3 ghc ==9.14.1 +tested-with: + GHC == 9.8.4, + GHC == 9.10.3, + GHC == 9.14.1 homepage: https://github.com/webdevred/jbeam-edit#readme bug-reports: https://github.com/webdevred/jbeam-edit/issues -synopsis: - A fast and reliable command-line tool for parsing, formatting, and editing JBeam files, supporting consistent node renaming, reference updating, and JBFL-based formatting. - -description: - jbeam-edit is a Haskell-based CLI utility for BeamNG JBeam files. It parses complete JBeam structures, preserves comments and whitespace, formats files consistently, and can automatically rename nodes and update references. Custom formatting rules are supported via JBFL (JBeam Formatting Language). See the README for usage instructions and examples: https://github.com/webdevred/jbeam-edit#readme - +synopsis: A fast and reliable command-line tool for parsing, formatting, and editing JBeam files, supporting consistent node renaming, reference updating, and JBFL-based formatting. +description: jbeam-edit is a Haskell-based CLI utility for BeamNG JBeam files. It parses complete JBeam structures, preserves comments and whitespace, formats files consistently, and can automatically rename nodes and update references. Custom formatting rules are supported via JBFL (JBeam Formatting Language). See the README for usage instructions and examples: https://github.com/webdevred/jbeam-edit#readme category: Command Line, Jbeam, Beamng build-type: Simple data-files: examples/jbfl/complex.jbfl examples/jbfl/minimal.jbfl - extra-source-files: README.md JBFL_DOCS.md @@ -41,25 +39,23 @@ extra-source-files: examples/formatted_jbeam/suspension-minimal-jbfl.jbeam source-repository head - type: git + type: git location: https://github.com/webdevred/jbeam-edit flag dump-ast description: Enable building the dump-ast executable (dev-only) - default: False - manual: True + default: False + manual: True flag transformation description: Enable transformation (experimental) - default: False - manual: True + default: False + manual: True flag windows-example-paths - description: - Use executable-relative example paths (for Windows release builds) - - default: False - manual: True + description: Use executable-relative example paths (for Windows release builds) + default: False + manual: True library exposed-modules: @@ -78,21 +74,28 @@ library JbeamEdit.Parsing.Common.Helpers JbeamEdit.Parsing.DSL JbeamEdit.Parsing.Jbeam - - hs-source-dirs: src - other-modules: Paths_jbeam_edit - autogen-modules: Paths_jbeam_edit - default-language: GHC2021 + hs-source-dirs: + src + other-modules: + Paths_jbeam_edit + autogen-modules: + Paths_jbeam_edit + default-language: GHC2021 + if ! flag(transformation) && ! flag(dump-ast) + build-depends: + containers >=0.8 + if os(windows) + ghc-options: -optc-Os -optl-static + if flag(transformation) + cpp-options: -DENABLE_TRANSFORMATION + if os(windows) && flag(windows-example-paths) + cpp-options: -DWINDOWS_EXAMPLE_PATHS default-extensions: - OverloadedStrings ImportQualifiedPost DerivingStrategies + OverloadedStrings + ImportQualifiedPost + DerivingStrategies TupleSections - - ghc-options: - -Wall -Wcompat -Widentities -Wincomplete-record-updates - -Wincomplete-uni-patterns -Wmissing-export-lists - -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints - -Wunused-packages - + ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -Wunused-packages build-depends: base >=4.19, bytestring, @@ -107,21 +110,6 @@ library text >=2.1.2, vector >=0.13 - if (!flag(transformation) && !flag(dump-ast)) - build-depends: containers >=0.8 - - if os(windows) - ghc-options: -optc-Os -optl-static - - if flag(transformation) - cpp-options: -DENABLE_TRANSFORMATION - - if (os(windows) && flag(windows-example-paths)) - cpp-options: -DWINDOWS_EXAMPLE_PATHS - - if os(windows) - cpp-options: -DENABLE_WINDOWS_NEWLINES - library jbeam-edit-transformation exposed-modules: JbeamEdit.Transformation @@ -131,38 +119,18 @@ library jbeam-edit-transformation JbeamEdit.Transformation.OMap1 JbeamEdit.Transformation.Types JbeamEdit.Transformation.VertexExtraction - - hs-source-dirs: src-extra/transformation - other-modules: Paths_jbeam_edit - autogen-modules: Paths_jbeam_edit - default-language: GHC2021 - default-extensions: - OverloadedStrings ImportQualifiedPost DerivingStrategies - TupleSections - - ghc-options: - -Wall -Wcompat -Widentities -Wincomplete-record-updates - -Wincomplete-uni-patterns -Wmissing-export-lists - -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints - - build-depends: - base >=4.19, - bytestring, - containers, - directory >=1.3.8.0, - filepath >=1.5.1.0, - jbeam-edit, - mtl, - scientific, - text >=2.1.2, - vector >=0.13 - - if (!flag(transformation) && !flag(dump-ast)) - build-depends: containers >=0.8 - + hs-source-dirs: + src-extra/transformation + other-modules: + Paths_jbeam_edit + autogen-modules: + Paths_jbeam_edit + default-language: GHC2021 + if ! flag(transformation) && ! flag(dump-ast) + build-depends: + containers >=0.8 if os(windows) ghc-options: -optc-Os -optl-static - if flag(transformation) build-depends: extra, @@ -170,29 +138,14 @@ library jbeam-edit-transformation ordered-containers >=0.2.4, transformers, yaml - else buildable: False - -executable jbeam-edit - main-is: Main.hs - hs-source-dirs: exe/jbeam-edit - other-modules: - CommandLineOptions - Paths_jbeam_edit - - autogen-modules: Paths_jbeam_edit - default-language: GHC2021 default-extensions: - OverloadedStrings ImportQualifiedPost DerivingStrategies - TupleSections CPP - - ghc-options: - -Wall -Wcompat -Widentities -Wincomplete-record-updates - -Wincomplete-uni-patterns -Wmissing-export-lists - -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints - -threaded -rtsopts -with-rtsopts=-N - + OverloadedStrings + ImportQualifiedPost + DerivingStrategies + TupleSections + ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints build-depends: base >=4.19, bytestring, @@ -205,32 +158,32 @@ executable jbeam-edit text >=2.1.2, vector >=0.13 - if (!flag(transformation) && !flag(dump-ast)) - build-depends: containers >=0.8 - +executable jbeam-edit + main-is: Main.hs + hs-source-dirs: + exe/jbeam-edit + other-modules: + CommandLineOptions + Paths_jbeam_edit + autogen-modules: + Paths_jbeam_edit + default-language: GHC2021 + if ! flag(transformation) && ! flag(dump-ast) + build-depends: + containers >=0.8 if os(windows) ghc-options: -optc-Os -optl-static - if flag(transformation) - cpp-options: -DENABLE_TRANSFORMATION - build-depends: jbeam-edit-transformation - -executable jbeam-edit-dump-ast - main-is: Main.hs - hs-source-dirs: tools/dump_ast - other-modules: Paths_jbeam_edit - autogen-modules: Paths_jbeam_edit - default-language: GHC2021 + cpp-options: -DENABLE_TRANSFORMATION + build-depends: + jbeam-edit-transformation default-extensions: - OverloadedStrings ImportQualifiedPost DerivingStrategies + OverloadedStrings + ImportQualifiedPost + DerivingStrategies TupleSections - - ghc-options: - -Wall -Wcompat -Widentities -Wincomplete-record-updates - -Wincomplete-uni-patterns -Wmissing-export-lists - -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints - -threaded -rtsopts -with-rtsopts=-N - + CPP + ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N build-depends: base >=4.19, bytestring, @@ -243,25 +196,51 @@ executable jbeam-edit-dump-ast text >=2.1.2, vector >=0.13 - if (!flag(transformation) && !flag(dump-ast)) - build-depends: containers >=0.8 - +executable jbeam-edit-dump-ast + main-is: Main.hs + hs-source-dirs: + tools/dump_ast + other-modules: + Paths_jbeam_edit + autogen-modules: + Paths_jbeam_edit + default-language: GHC2021 + if ! flag(transformation) && ! flag(dump-ast) + build-depends: + containers >=0.8 if os(windows) ghc-options: -optc-Os -optl-static - if (flag(dump-ast) && flag(transformation)) build-depends: jbeam-edit-transformation, pretty-simple >=4 - else buildable: False + default-extensions: + OverloadedStrings + ImportQualifiedPost + DerivingStrategies + TupleSections + ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N + build-depends: + base >=4.19, + bytestring, + containers, + directory >=1.3.8.0, + filepath >=1.5.1.0, + jbeam-edit, + mtl, + scientific, + text >=2.1.2, + vector >=0.13 test-suite jbeam-edit-test - type: exitcode-stdio-1.0 - main-is: Spec.hs - build-tool-depends: hspec-discover:hspec-discover - hs-source-dirs: test + type: exitcode-stdio-1.0 + main-is: Spec.hs + build-tool-depends: + hspec-discover:hspec-discover + hs-source-dirs: + test other-modules: Core.NewlineSpec Core.NodeCursorSpec @@ -273,19 +252,24 @@ test-suite jbeam-edit-test Parsing.JbeamSpec SpecHelper Paths_jbeam_edit - - autogen-modules: Paths_jbeam_edit - default-language: GHC2021 + autogen-modules: + Paths_jbeam_edit + default-language: GHC2021 + if ! flag(transformation) && ! flag(dump-ast) + build-depends: + containers >=0.8 + if os(windows) + ghc-options: -optc-Os -optl-static + if true + build-depends: + hspec-megaparsec, + megaparsec default-extensions: - OverloadedStrings ImportQualifiedPost DerivingStrategies + OverloadedStrings + ImportQualifiedPost + DerivingStrategies TupleSections - - ghc-options: - -Wall -Wcompat -Widentities -Wincomplete-record-updates - -Wincomplete-uni-patterns -Wmissing-export-lists - -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints - -threaded -rtsopts -with-rtsopts=-N - + ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N build-depends: base >=4.19, bytestring, @@ -299,34 +283,34 @@ test-suite jbeam-edit-test text >=2.1.2, vector >=0.13 - if (!flag(transformation) && !flag(dump-ast)) - build-depends: containers >=0.8 - +test-suite jbeam-edit-transformation-test + type: exitcode-stdio-1.0 + main-is: Spec.hs + hs-source-dirs: + test-extra/transformation + other-modules: + Paths_jbeam_edit + autogen-modules: + Paths_jbeam_edit + default-language: GHC2021 + if ! flag(transformation) && ! flag(dump-ast) + build-depends: + containers >=0.8 if os(windows) ghc-options: -optc-Os -optl-static - - if True + if true + ghc-options: -main-is Spec + if flag(transformation) build-depends: - hspec-megaparsec, - megaparsec - -test-suite jbeam-edit-transformation-test - type: exitcode-stdio-1.0 - main-is: Spec.hs - hs-source-dirs: test-extra/transformation - other-modules: Paths_jbeam_edit - autogen-modules: Paths_jbeam_edit - default-language: GHC2021 + jbeam-edit-transformation + else + buildable: False default-extensions: - OverloadedStrings ImportQualifiedPost DerivingStrategies + OverloadedStrings + ImportQualifiedPost + DerivingStrategies TupleSections - - ghc-options: - -Wall -Wcompat -Widentities -Wincomplete-record-updates - -Wincomplete-uni-patterns -Wmissing-export-lists - -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints - -threaded -rtsopts -with-rtsopts=-N - + ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N build-depends: base >=4.19, bytestring, @@ -340,38 +324,33 @@ test-suite jbeam-edit-transformation-test text >=2.1.2, vector >=0.13 - if (!flag(transformation) && !flag(dump-ast)) - build-depends: containers >=0.8 - +benchmark jbeam-edit-bench + type: exitcode-stdio-1.0 + main-is: Main.hs + hs-source-dirs: + tools/bench + other-modules: + Paths_jbeam_edit + autogen-modules: + Paths_jbeam_edit + default-language: GHC2021 + if ! flag(transformation) && ! flag(dump-ast) + build-depends: + containers >=0.8 if os(windows) ghc-options: -optc-Os -optl-static - - if True - ghc-options: -main-is Spec - if flag(transformation) - build-depends: jbeam-edit-transformation - + build-depends: + criterion, + jbeam-edit-transformation else buildable: False - -benchmark jbeam-edit-bench - type: exitcode-stdio-1.0 - main-is: Main.hs - hs-source-dirs: tools/bench - other-modules: Paths_jbeam_edit - autogen-modules: Paths_jbeam_edit - default-language: GHC2021 default-extensions: - OverloadedStrings ImportQualifiedPost DerivingStrategies + OverloadedStrings + ImportQualifiedPost + DerivingStrategies TupleSections - - ghc-options: - -Wall -Wcompat -Widentities -Wincomplete-record-updates - -Wincomplete-uni-patterns -Wmissing-export-lists - -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints - -threaded -rtsopts -with-rtsopts=-N - + ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N build-depends: base, bytestring, @@ -383,17 +362,3 @@ benchmark jbeam-edit-bench scientific, text >=2.1.2, vector >=0.13 - - if (!flag(transformation) && !flag(dump-ast)) - build-depends: containers >=0.8 - - if os(windows) - ghc-options: -optc-Os -optl-static - - if flag(transformation) - build-depends: - criterion, - jbeam-edit-transformation - - else - buildable: False diff --git a/package.yaml b/package.yaml index 0ea56827..04f0a267 100644 --- a/package.yaml +++ b/package.yaml @@ -101,8 +101,6 @@ library: cpp-options: -DENABLE_TRANSFORMATION - condition: os(windows) && flag(windows-example-paths) cpp-options: -DWINDOWS_EXAMPLE_PATHS - - condition: os(windows) - cpp-options: -DENABLE_WINDOWS_NEWLINES _jbeam-options: &jbeam-options main: Main.hs diff --git a/src/JbeamEdit/Core/Newline.hs b/src/JbeamEdit/Core/Newline.hs index fb11267f..b1a43020 100644 --- a/src/JbeamEdit/Core/Newline.hs +++ b/src/JbeamEdit/Core/Newline.hs @@ -1,10 +1,11 @@ module JbeamEdit.Core.Newline (detectNewline) where -import Data.ByteString.Lazy as LBS -import Data.ByteString.Lazy.Char8 as LBS8 (elemIndex) +import Data.ByteString.Lazy (LazyByteString) +import Data.ByteString.Lazy qualified as LBS +import Data.ByteString.Lazy.Char8 qualified as LBS8 (elemIndex) import System.IO (Newline (..)) -detectNewline :: LBS.ByteString -> Newline +detectNewline :: LazyByteString -> Newline detectNewline content = case LBS8.elemIndex '\r' content of Nothing -> LF diff --git a/src/JbeamEdit/Formatting.hs b/src/JbeamEdit/Formatting.hs index 42151fe1..34ebabb2 100644 --- a/src/JbeamEdit/Formatting.hs +++ b/src/JbeamEdit/Formatting.hs @@ -11,7 +11,7 @@ module JbeamEdit.Formatting ( ) where import Data.Bool (bool) -import Data.ByteString.Lazy qualified as LBS (fromStrict, hPutStr) +import Data.ByteString.Lazy qualified as LBS (fromStrict) import Data.Char (isSpace) import Data.Foldable.Extra (notNull) import Data.Map.Strict (Map) @@ -51,8 +51,8 @@ import JbeamEdit.Formatting.Rules ( ) import JbeamEdit.Formatting.Rules.ComplexNewLine qualified as CNL import JbeamEdit.Formatting.Rules.TrailingComma qualified as TC -import System.File.OsPath qualified as OS -import System.IO +import System.File.OsPath qualified as OS (writeFile) +import System.IO (Newline (..)) import System.OsPath (OsPath) data FormattingState = FormattingState @@ -460,13 +460,13 @@ formatWithCursor rs _ cursor n = formatNode :: RuleSet -> Node -> Text formatNode rs node = formatWithCursor rs emptyState newCursor node <> T.singleton '\n' -#ifdef ENABLE_WINDOWS_NEWLINES -replaceNewlines :: Text -> Text -replaceNewlines = T.replace "\n" "\r\n" -#else -replaceNewlines :: Text -> Text -replaceNewlines = id -#endif +{- | 'formatNode' always emits LF, so rewrite the line endings when the file +came with CRLF. The handle's newline mode cannot do this, it only applies to +text-mode writes and the output goes out as bytes. +-} +applyNewline :: Newline -> Text -> Text +applyNewline CRLF = T.replace "\n" "\r\n" +applyNewline LF = id formatNodeAndWrite :: Newline @@ -474,12 +474,9 @@ formatNodeAndWrite -> OsPath -> Node -> IO () -formatNodeAndWrite newline rs outFile node = do - handle <- OS.openFile outFile WriteMode - hSetNewlineMode handle (NewlineMode LF newline) - LBS.hPutStr handle +formatNodeAndWrite newline rs outFile = + OS.writeFile outFile . LBS.fromStrict . encodeUtf8 - . replaceNewlines - $ formatNode rs node - hClose handle + . applyNewline newline + . formatNode rs From d98289a051f058ed529b8cc96c74842bb48f3d29 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sun, 9 Aug 2026 21:50:13 +0200 Subject: [PATCH 11/15] Fix for parsing jbeam block comments --- src/JbeamEdit/Parsing/Jbeam.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/JbeamEdit/Parsing/Jbeam.hs b/src/JbeamEdit/Parsing/Jbeam.hs index a4fa95b9..bd22b2e7 100644 --- a/src/JbeamEdit/Parsing/Jbeam.hs +++ b/src/JbeamEdit/Parsing/Jbeam.hs @@ -98,7 +98,8 @@ associationDirection st = bool PreviousNode NextNode (lastNodeEndedWithNewline s commentStripSpace :: Text -> Text commentStripSpace initialText = - let initialNewline = mwhen (T.isPrefixOf "\n" initialText) "\n" + let startsWithNewline text = T.isPrefixOf "\n" text || T.isPrefixOf "\rg\n" text + initialNewline = mwhen (startsWithNewline initialText) "\n" trimTrailingSpaces = T.dropWhileEnd (charBoth (/= '\n') isSpace) endingNewline = mwhen (T.isSuffixOf "\n" $ trimTrailingSpaces initialText) "\n" go = T.intercalate "\n" . filter (not . T.all isSpace) . map T.strip . T.lines From 24f280539d43b3ad640f05e9283a3c9786892c30 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sun, 9 Aug 2026 21:56:40 +0200 Subject: [PATCH 12/15] Indent the newline check the way shfmt wants it --- .github/scripts/check_newline_preservation.sh | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/scripts/check_newline_preservation.sh b/.github/scripts/check_newline_preservation.sh index f2617fd9..620b9da8 100644 --- a/.github/scripts/check_newline_preservation.sh +++ b/.github/scripts/check_newline_preservation.sh @@ -22,36 +22,36 @@ export jbeam_edit_datadir="$PWD" failed=0 carriage_returns() { - grep -c $'\r' "$1" || true + grep -c $'\r' "$1" || true } check() { - local label="$1" expected="$2" target="$WORK_DIR/$1.jbeam" + local label="$1" expected="$2" target="$WORK_DIR/$1.jbeam" - if [[ "$label" == crlf ]]; then - sed 's/\r$//; s/$/\r/' "$SOURCE_FILE" >"$target" - else - sed 's/\r$//' "$SOURCE_FILE" >"$target" - fi + if [[ "$label" == crlf ]]; then + sed 's/\r$//; s/$/\r/' "$SOURCE_FILE" >"$target" + else + sed 's/\r$//' "$SOURCE_FILE" >"$target" + fi - local before after - before=$(carriage_returns "$target") - if [[ "$before" -ne "$expected" ]]; then - echo "setup error: $label input has $before carriage returns, expected $expected" - failed=1 - return - fi + local before after + before=$(carriage_returns "$target") + if [[ "$before" -ne "$expected" ]]; then + echo "setup error: $label input has $before carriage returns, expected $expected" + failed=1 + return + fi - (cd "$WORK_DIR" && "$BINARY" "$label.jbeam" >/dev/null) + (cd "$WORK_DIR" && "$BINARY" "$label.jbeam" >/dev/null) - after=$(carriage_returns "$target") - if [[ "$expected" -eq 0 && "$after" -ne 0 ]]; then - echo "$label: line endings changed, LF input came out with $after carriage returns" - failed=1 - elif [[ "$expected" -ne 0 && "$after" -eq 0 ]]; then - echo "$label: line endings changed, CRLF input came out with no carriage returns" - failed=1 - fi + after=$(carriage_returns "$target") + if [[ "$expected" -eq 0 && "$after" -ne 0 ]]; then + echo "$label: line endings changed, LF input came out with $after carriage returns" + failed=1 + elif [[ "$expected" -ne 0 && "$after" -eq 0 ]]; then + echo "$label: line endings changed, CRLF input came out with no carriage returns" + failed=1 + fi } lf_lines=$(sed 's/\r$//' "$SOURCE_FILE" | wc -l) @@ -60,8 +60,8 @@ check lf 0 check crlf "$lf_lines" if [[ "$failed" -ne 0 ]]; then - echo "formatting does not preserve line endings" - exit 1 + echo "formatting does not preserve line endings" + exit 1 fi echo "line endings survive a format for both LF and CRLF input" From 49c3ade0cf82630d79ef36c184188167a1d2a907 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sun, 9 Aug 2026 21:59:29 +0200 Subject: [PATCH 13/15] Match the actual CRLF prefix in block comments The prefix had a stray g in it, so the branch never fired and a CRLF file still lost the newline after the comment opener. --- src/JbeamEdit/Parsing/Jbeam.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JbeamEdit/Parsing/Jbeam.hs b/src/JbeamEdit/Parsing/Jbeam.hs index bd22b2e7..be8784e9 100644 --- a/src/JbeamEdit/Parsing/Jbeam.hs +++ b/src/JbeamEdit/Parsing/Jbeam.hs @@ -98,7 +98,7 @@ associationDirection st = bool PreviousNode NextNode (lastNodeEndedWithNewline s commentStripSpace :: Text -> Text commentStripSpace initialText = - let startsWithNewline text = T.isPrefixOf "\n" text || T.isPrefixOf "\rg\n" text + let startsWithNewline text = T.isPrefixOf "\n" text || T.isPrefixOf "\r\n" text initialNewline = mwhen (startsWithNewline initialText) "\n" trimTrailingSpaces = T.dropWhileEnd (charBoth (/= '\n') isSpace) endingNewline = mwhen (T.isSuffixOf "\n" $ trimTrailingSpaces initialText) "\n" From ceddf670de97a33d983faac3376d4e6961e849c0 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sun, 9 Aug 2026 22:04:09 +0200 Subject: [PATCH 14/15] Regenerate the cabal file with the hpack CI uses A newer local hpack rewrote the whole file, which the cabal file check rejects. Only the windows newlines block should have gone. --- jbeam-edit.cabal | 362 ++++++++++++++++++++++++++--------------------- 1 file changed, 197 insertions(+), 165 deletions(-) diff --git a/jbeam-edit.cabal b/jbeam-edit.cabal index 63dff072..c101eea4 100644 --- a/jbeam-edit.cabal +++ b/jbeam-edit.cabal @@ -1,4 +1,4 @@ -cabal-version: 2.2 +cabal-version: 2.2 -- This file has been generated from package.yaml by hpack version 0.38.3. -- @@ -11,19 +11,21 @@ license-file: LICENSE copyright: 2025 webdevred maintainer: example@example.com author: webdevred -tested-with: - GHC == 9.8.4, - GHC == 9.10.3, - GHC == 9.14.1 +tested-with: ghc ==9.8.4 ghc ==9.10.3 ghc ==9.14.1 homepage: https://github.com/webdevred/jbeam-edit#readme bug-reports: https://github.com/webdevred/jbeam-edit/issues -synopsis: A fast and reliable command-line tool for parsing, formatting, and editing JBeam files, supporting consistent node renaming, reference updating, and JBFL-based formatting. -description: jbeam-edit is a Haskell-based CLI utility for BeamNG JBeam files. It parses complete JBeam structures, preserves comments and whitespace, formats files consistently, and can automatically rename nodes and update references. Custom formatting rules are supported via JBFL (JBeam Formatting Language). See the README for usage instructions and examples: https://github.com/webdevred/jbeam-edit#readme +synopsis: + A fast and reliable command-line tool for parsing, formatting, and editing JBeam files, supporting consistent node renaming, reference updating, and JBFL-based formatting. + +description: + jbeam-edit is a Haskell-based CLI utility for BeamNG JBeam files. It parses complete JBeam structures, preserves comments and whitespace, formats files consistently, and can automatically rename nodes and update references. Custom formatting rules are supported via JBFL (JBeam Formatting Language). See the README for usage instructions and examples: https://github.com/webdevred/jbeam-edit#readme + category: Command Line, Jbeam, Beamng build-type: Simple data-files: examples/jbfl/complex.jbfl examples/jbfl/minimal.jbfl + extra-source-files: README.md JBFL_DOCS.md @@ -39,23 +41,25 @@ extra-source-files: examples/formatted_jbeam/suspension-minimal-jbfl.jbeam source-repository head - type: git + type: git location: https://github.com/webdevred/jbeam-edit flag dump-ast description: Enable building the dump-ast executable (dev-only) - default: False - manual: True + default: False + manual: True flag transformation description: Enable transformation (experimental) - default: False - manual: True + default: False + manual: True flag windows-example-paths - description: Use executable-relative example paths (for Windows release builds) - default: False - manual: True + description: + Use executable-relative example paths (for Windows release builds) + + default: False + manual: True library exposed-modules: @@ -74,28 +78,21 @@ library JbeamEdit.Parsing.Common.Helpers JbeamEdit.Parsing.DSL JbeamEdit.Parsing.Jbeam - hs-source-dirs: - src - other-modules: - Paths_jbeam_edit - autogen-modules: - Paths_jbeam_edit - default-language: GHC2021 - if ! flag(transformation) && ! flag(dump-ast) - build-depends: - containers >=0.8 - if os(windows) - ghc-options: -optc-Os -optl-static - if flag(transformation) - cpp-options: -DENABLE_TRANSFORMATION - if os(windows) && flag(windows-example-paths) - cpp-options: -DWINDOWS_EXAMPLE_PATHS + + hs-source-dirs: src + other-modules: Paths_jbeam_edit + autogen-modules: Paths_jbeam_edit + default-language: GHC2021 default-extensions: - OverloadedStrings - ImportQualifiedPost - DerivingStrategies + OverloadedStrings ImportQualifiedPost DerivingStrategies TupleSections - ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -Wunused-packages + + ghc-options: + -Wall -Wcompat -Widentities -Wincomplete-record-updates + -Wincomplete-uni-patterns -Wmissing-export-lists + -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints + -Wunused-packages + build-depends: base >=4.19, bytestring, @@ -110,6 +107,18 @@ library text >=2.1.2, vector >=0.13 + if (!flag(transformation) && !flag(dump-ast)) + build-depends: containers >=0.8 + + if os(windows) + ghc-options: -optc-Os -optl-static + + if flag(transformation) + cpp-options: -DENABLE_TRANSFORMATION + + if (os(windows) && flag(windows-example-paths)) + cpp-options: -DWINDOWS_EXAMPLE_PATHS + library jbeam-edit-transformation exposed-modules: JbeamEdit.Transformation @@ -119,18 +128,38 @@ library jbeam-edit-transformation JbeamEdit.Transformation.OMap1 JbeamEdit.Transformation.Types JbeamEdit.Transformation.VertexExtraction - hs-source-dirs: - src-extra/transformation - other-modules: - Paths_jbeam_edit - autogen-modules: - Paths_jbeam_edit - default-language: GHC2021 - if ! flag(transformation) && ! flag(dump-ast) - build-depends: - containers >=0.8 + + hs-source-dirs: src-extra/transformation + other-modules: Paths_jbeam_edit + autogen-modules: Paths_jbeam_edit + default-language: GHC2021 + default-extensions: + OverloadedStrings ImportQualifiedPost DerivingStrategies + TupleSections + + ghc-options: + -Wall -Wcompat -Widentities -Wincomplete-record-updates + -Wincomplete-uni-patterns -Wmissing-export-lists + -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints + + build-depends: + base >=4.19, + bytestring, + containers, + directory >=1.3.8.0, + filepath >=1.5.1.0, + jbeam-edit, + mtl, + scientific, + text >=2.1.2, + vector >=0.13 + + if (!flag(transformation) && !flag(dump-ast)) + build-depends: containers >=0.8 + if os(windows) ghc-options: -optc-Os -optl-static + if flag(transformation) build-depends: extra, @@ -138,14 +167,29 @@ library jbeam-edit-transformation ordered-containers >=0.2.4, transformers, yaml + else buildable: False + +executable jbeam-edit + main-is: Main.hs + hs-source-dirs: exe/jbeam-edit + other-modules: + CommandLineOptions + Paths_jbeam_edit + + autogen-modules: Paths_jbeam_edit + default-language: GHC2021 default-extensions: - OverloadedStrings - ImportQualifiedPost - DerivingStrategies - TupleSections - ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints + OverloadedStrings ImportQualifiedPost DerivingStrategies + TupleSections CPP + + ghc-options: + -Wall -Wcompat -Widentities -Wincomplete-record-updates + -Wincomplete-uni-patterns -Wmissing-export-lists + -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints + -threaded -rtsopts -with-rtsopts=-N + build-depends: base >=4.19, bytestring, @@ -158,32 +202,32 @@ library jbeam-edit-transformation text >=2.1.2, vector >=0.13 -executable jbeam-edit - main-is: Main.hs - hs-source-dirs: - exe/jbeam-edit - other-modules: - CommandLineOptions - Paths_jbeam_edit - autogen-modules: - Paths_jbeam_edit - default-language: GHC2021 - if ! flag(transformation) && ! flag(dump-ast) - build-depends: - containers >=0.8 + if (!flag(transformation) && !flag(dump-ast)) + build-depends: containers >=0.8 + if os(windows) ghc-options: -optc-Os -optl-static + if flag(transformation) - cpp-options: -DENABLE_TRANSFORMATION - build-depends: - jbeam-edit-transformation + cpp-options: -DENABLE_TRANSFORMATION + build-depends: jbeam-edit-transformation + +executable jbeam-edit-dump-ast + main-is: Main.hs + hs-source-dirs: tools/dump_ast + other-modules: Paths_jbeam_edit + autogen-modules: Paths_jbeam_edit + default-language: GHC2021 default-extensions: - OverloadedStrings - ImportQualifiedPost - DerivingStrategies + OverloadedStrings ImportQualifiedPost DerivingStrategies TupleSections - CPP - ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N + + ghc-options: + -Wall -Wcompat -Widentities -Wincomplete-record-updates + -Wincomplete-uni-patterns -Wmissing-export-lists + -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints + -threaded -rtsopts -with-rtsopts=-N + build-depends: base >=4.19, bytestring, @@ -196,51 +240,25 @@ executable jbeam-edit text >=2.1.2, vector >=0.13 -executable jbeam-edit-dump-ast - main-is: Main.hs - hs-source-dirs: - tools/dump_ast - other-modules: - Paths_jbeam_edit - autogen-modules: - Paths_jbeam_edit - default-language: GHC2021 - if ! flag(transformation) && ! flag(dump-ast) - build-depends: - containers >=0.8 + if (!flag(transformation) && !flag(dump-ast)) + build-depends: containers >=0.8 + if os(windows) ghc-options: -optc-Os -optl-static + if (flag(dump-ast) && flag(transformation)) build-depends: jbeam-edit-transformation, pretty-simple >=4 + else buildable: False - default-extensions: - OverloadedStrings - ImportQualifiedPost - DerivingStrategies - TupleSections - ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N - build-depends: - base >=4.19, - bytestring, - containers, - directory >=1.3.8.0, - filepath >=1.5.1.0, - jbeam-edit, - mtl, - scientific, - text >=2.1.2, - vector >=0.13 test-suite jbeam-edit-test - type: exitcode-stdio-1.0 - main-is: Spec.hs - build-tool-depends: - hspec-discover:hspec-discover - hs-source-dirs: - test + type: exitcode-stdio-1.0 + main-is: Spec.hs + build-tool-depends: hspec-discover:hspec-discover + hs-source-dirs: test other-modules: Core.NewlineSpec Core.NodeCursorSpec @@ -252,24 +270,19 @@ test-suite jbeam-edit-test Parsing.JbeamSpec SpecHelper Paths_jbeam_edit - autogen-modules: - Paths_jbeam_edit - default-language: GHC2021 - if ! flag(transformation) && ! flag(dump-ast) - build-depends: - containers >=0.8 - if os(windows) - ghc-options: -optc-Os -optl-static - if true - build-depends: - hspec-megaparsec, - megaparsec + + autogen-modules: Paths_jbeam_edit + default-language: GHC2021 default-extensions: - OverloadedStrings - ImportQualifiedPost - DerivingStrategies + OverloadedStrings ImportQualifiedPost DerivingStrategies TupleSections - ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N + + ghc-options: + -Wall -Wcompat -Widentities -Wincomplete-record-updates + -Wincomplete-uni-patterns -Wmissing-export-lists + -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints + -threaded -rtsopts -with-rtsopts=-N + build-depends: base >=4.19, bytestring, @@ -283,34 +296,34 @@ test-suite jbeam-edit-test text >=2.1.2, vector >=0.13 -test-suite jbeam-edit-transformation-test - type: exitcode-stdio-1.0 - main-is: Spec.hs - hs-source-dirs: - test-extra/transformation - other-modules: - Paths_jbeam_edit - autogen-modules: - Paths_jbeam_edit - default-language: GHC2021 - if ! flag(transformation) && ! flag(dump-ast) - build-depends: - containers >=0.8 + if (!flag(transformation) && !flag(dump-ast)) + build-depends: containers >=0.8 + if os(windows) ghc-options: -optc-Os -optl-static - if true - ghc-options: -main-is Spec - if flag(transformation) + + if True build-depends: - jbeam-edit-transformation - else - buildable: False + hspec-megaparsec, + megaparsec + +test-suite jbeam-edit-transformation-test + type: exitcode-stdio-1.0 + main-is: Spec.hs + hs-source-dirs: test-extra/transformation + other-modules: Paths_jbeam_edit + autogen-modules: Paths_jbeam_edit + default-language: GHC2021 default-extensions: - OverloadedStrings - ImportQualifiedPost - DerivingStrategies + OverloadedStrings ImportQualifiedPost DerivingStrategies TupleSections - ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N + + ghc-options: + -Wall -Wcompat -Widentities -Wincomplete-record-updates + -Wincomplete-uni-patterns -Wmissing-export-lists + -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints + -threaded -rtsopts -with-rtsopts=-N + build-depends: base >=4.19, bytestring, @@ -324,33 +337,38 @@ test-suite jbeam-edit-transformation-test text >=2.1.2, vector >=0.13 -benchmark jbeam-edit-bench - type: exitcode-stdio-1.0 - main-is: Main.hs - hs-source-dirs: - tools/bench - other-modules: - Paths_jbeam_edit - autogen-modules: - Paths_jbeam_edit - default-language: GHC2021 - if ! flag(transformation) && ! flag(dump-ast) - build-depends: - containers >=0.8 + if (!flag(transformation) && !flag(dump-ast)) + build-depends: containers >=0.8 + if os(windows) ghc-options: -optc-Os -optl-static + + if True + ghc-options: -main-is Spec + if flag(transformation) - build-depends: - criterion, - jbeam-edit-transformation + build-depends: jbeam-edit-transformation + else buildable: False + +benchmark jbeam-edit-bench + type: exitcode-stdio-1.0 + main-is: Main.hs + hs-source-dirs: tools/bench + other-modules: Paths_jbeam_edit + autogen-modules: Paths_jbeam_edit + default-language: GHC2021 default-extensions: - OverloadedStrings - ImportQualifiedPost - DerivingStrategies + OverloadedStrings ImportQualifiedPost DerivingStrategies TupleSections - ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N + + ghc-options: + -Wall -Wcompat -Widentities -Wincomplete-record-updates + -Wincomplete-uni-patterns -Wmissing-export-lists + -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints + -threaded -rtsopts -with-rtsopts=-N + build-depends: base, bytestring, @@ -362,3 +380,17 @@ benchmark jbeam-edit-bench scientific, text >=2.1.2, vector >=0.13 + + if (!flag(transformation) && !flag(dump-ast)) + build-depends: containers >=0.8 + + if os(windows) + ghc-options: -optc-Os -optl-static + + if flag(transformation) + build-depends: + criterion, + jbeam-edit-transformation + + else + buildable: False From da8b708a8dbcd93f64222369f27c688409719c60 Mon Sep 17 00:00:00 2001 From: webdevred <148627186+webdevred@users.noreply.github.com> Date: Sun, 9 Aug 2026 22:14:58 +0200 Subject: [PATCH 15/15] Ignore line endings when the release check compares output It feeds the tool a blob but compares against the working tree, so the two sides disagree about endings on Windows. The endings themselves have their own check now. --- .github/scripts/prepare_installer.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/scripts/prepare_installer.sh b/.github/scripts/prepare_installer.sh index fce5227f..c9cf0fd1 100644 --- a/.github/scripts/prepare_installer.sh +++ b/.github/scripts/prepare_installer.sh @@ -49,8 +49,11 @@ cp ./examples/jbeam-edit.yaml ./.jbeam-edit.yaml JBEAM_DIR="./examples/jbeam" +# Line endings are checked separately by check_newline_preservation.sh. Here we +# only care about content, and the two sides can disagree on endings because the +# input comes from a blob while the expected file comes from the working tree. custom_diff() { - diff --color=always --suppress-common-lines "$1" "$2" + diff --color=always --suppress-common-lines --strip-trailing-cr "$1" "$2" } mapfile -t JBEAM_FILES < <(find "$JBEAM_DIR" -maxdepth 1 -name "*.jbeam" -printf "%f\n")