diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cd440da1..361db704 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,6 +33,9 @@ jobs: - name: Install pandoc run: rush get pandoc + - name: Install shfmt + run: rush get shfmt + # libyaml needed for Ruby's YAML library - name: Install OS dependencies run: sudo apt-get -y install libyaml-dev diff --git a/examples/render-mandoc/docs/download.1 b/examples/render-mandoc/docs/download.1 index 7b8e71c9..3303044c 100644 --- a/examples/render-mandoc/docs/download.1 +++ b/examples/render-mandoc/docs/download.1 @@ -1,6 +1,6 @@ .\" Automatically generated by Pandoc 3.2 .\" -.TH "download" "1" "July 2025" "Version 0.1.0" "Sample application" +.TH "download" "1" "August 2025" "Version 0.1.0" "Sample application" .SH NAME \f[B]download\f[R] \- Sample application .SH SYNOPSIS diff --git a/examples/render-mandoc/docs/download.md b/examples/render-mandoc/docs/download.md index 1a750aef..b4f08c4a 100644 --- a/examples/render-mandoc/docs/download.md +++ b/examples/render-mandoc/docs/download.md @@ -1,6 +1,6 @@ % download(1) Version 0.1.0 | Sample application % Lana Lang -% July 2025 +% August 2025 NAME ================================================== diff --git a/lib/bashly.rb b/lib/bashly.rb index 1255c93f..016e2e2d 100644 --- a/lib/bashly.rb +++ b/lib/bashly.rb @@ -22,7 +22,7 @@ module Bashly module Script autoloads 'bashly/script', %i[ Argument Base CatchAll Command Dependency EnvironmentVariable Flag - Variable Wrapper + Formatter Variable Wrapper ] module Introspection diff --git a/lib/bashly/extensions/string.rb b/lib/bashly/extensions/string.rb index 0eb66e6e..7c7c20f2 100644 --- a/lib/bashly/extensions/string.rb +++ b/lib/bashly/extensions/string.rb @@ -44,8 +44,8 @@ def wrap(length = 80) end * "\n" end - def lint - gsub(/\s+\n/m, "\n\n").lines.grep_v(/^\s*##/).join + def remove_private_comments + lines.grep_v(/^\s*##/).join end def remove_front_matter diff --git a/lib/bashly/libraries/settings/settings.yml b/lib/bashly/libraries/settings/settings.yml index b6bc1ddd..2b9e0e31 100644 --- a/lib/bashly/libraries/settings/settings.yml +++ b/lib/bashly/libraries/settings/settings.yml @@ -41,16 +41,26 @@ partials_extension: sh #------------------------------------------------------------------------------- # Configure the bash options that will be added to the initialize function: -# strict: true Bash strict mode (set -euo pipefail) -# strict: false Only exit on errors (set -e) -# strict: '' Do not add any 'set' directive -# strict: Add any other custom 'set' directive +# strict: true # Bash strict mode (set -euo pipefail) +# strict: false # Only exit on errors (set -e) +# strict: '' # Do not add any 'set' directive +# strict: # Add any other custom 'set' directive strict: false # When true, the generated script will use tab indentation instead of spaces # (every 2 leading spaces will be converted to a tab character) tab_indent: false +# Choose a post-processor for the generated script: +# formatter: internal # Use Bashly's internal formatter (compacts newlines) +# formatter: external # Run the external command `shfmt --case-indent --indent 2` +# formatter: none # Disable formatting entirely +# formatter: # Use a custom shell command to format the script. +# # The command will receive the script via stdin and +# # must output the result to stdout. +# # Example: shfmt --minify +formatter: internal + #------------------------------------------------------------------------------- # INTERFACE OPTIONS @@ -100,10 +110,10 @@ env: development # Tweak the script output by enabling or disabling some script output. # These options accept one of the following strings: -# - production render this feature only when env == production -# - development render this feature only when env == development -# - always render this feature in any environment -# - never do not render this feature +# - production # render this feature only when env == production +# - development # render this feature only when env == development +# - always # render this feature in any environment +# - never # do not render this feature enable_header_comment: always enable_bash3_bouncer: always enable_view_markers: development diff --git a/lib/bashly/script/formatter.rb b/lib/bashly/script/formatter.rb new file mode 100644 index 00000000..d17c2881 --- /dev/null +++ b/lib/bashly/script/formatter.rb @@ -0,0 +1,44 @@ +require 'open3' +require 'shellwords' + +module Bashly + module Script + class Formatter + attr_reader :script, :mode + + def initialize(script, mode: nil) + @script = script + @mode = mode&.to_s || 'internal' + end + + def formatted_script + case mode + when 'internal' then script.gsub(/\s+\n/m, "\n\n") + when 'external' then shfmt_result + when 'none' then script + else custom_formatter_result mode + end + end + + private + + def shfmt_result + custom_formatter_result %w[shfmt --case-indent --indent 2] + end + + def custom_formatter_result(command) + command = Shellwords.split command if command.is_a? String + + begin + output, error, status = Open3.capture3(*command, stdin_data: script) + rescue Errno::ENOENT + raise Error, "Command not found: g`#{command.first}`" + end + + raise Error, "Failed running g`#{Shellwords.join command}`:\n\n#{error}" unless status.success? + + output + end + end + end +end diff --git a/lib/bashly/script/wrapper.rb b/lib/bashly/script/wrapper.rb index dd59e198..537766e4 100644 --- a/lib/bashly/script/wrapper.rb +++ b/lib/bashly/script/wrapper.rb @@ -23,7 +23,13 @@ def base_code [header, body] end - result.join("\n").lint + clean_code result.join("\n") + end + + def clean_code(script) + result = script.remove_private_comments + formatter = Formatter.new result, mode: Settings.formatter + formatter.formatted_script end def header diff --git a/lib/bashly/settings.rb b/lib/bashly/settings.rb index b59d5512..ac8b30ef 100644 --- a/lib/bashly/settings.rb +++ b/lib/bashly/settings.rb @@ -15,6 +15,7 @@ class << self :enable_inspect_args, :enable_sourcing, :enable_view_markers, + :formatter, :function_names, :lib_dir, :partials_extension, @@ -86,6 +87,10 @@ def env=(value) @env = value&.to_sym end + def formatter + @formatter ||= get :formatter + end + def full_lib_dir "#{source_dir}/#{lib_dir}" end diff --git a/schemas/settings.json b/schemas/settings.json index b52fb408..5cddac76 100644 --- a/schemas/settings.json +++ b/schemas/settings.json @@ -213,6 +213,25 @@ ], "default": "development" }, + "formatter": { + "title": "formatter", + "description": "Choose how to post-process the generated script\nhttps://bashly.dev/usage/settings/#formatter", + "anyOf": [ + { + "type": "string", + "enum": [ + "internal", + "external", + "none" + ] + }, + { + "type": "string", + "minLength": 1 + } + ], + "default": "internal" + }, "partials_extension": { "title": "partials extension", "description": "The extension to use when reading/writing partial script snippets\nhttps://bashly.dev/usage/settings/#partials_extension", diff --git a/spec/approvals/examples/render-mandoc b/spec/approvals/examples/render-mandoc index 96bc2eee..fca583fc 100644 --- a/spec/approvals/examples/render-mandoc +++ b/spec/approvals/examples/render-mandoc @@ -44,4 +44,4 @@ ISSUE TRACKER AUTHORS Lana Lang. -Version 0.1.0 July 2025 download(1) +Version 0.1.0 August 2025 download(1) diff --git a/spec/approvals/examples/stacktrace b/spec/approvals/examples/stacktrace index 79a6529d..1b4e3915 100644 --- a/spec/approvals/examples/stacktrace +++ b/spec/approvals/examples/stacktrace @@ -51,5 +51,5 @@ Examples: Stack trace: from ./download:15 in `root_command` - from ./download:260 in `run` - from ./download:266 in `main` + from ./download:259 in `run` + from ./download:265 in `main` diff --git a/spec/approvals/formatter/error-failure b/spec/approvals/formatter/error-failure new file mode 100644 index 00000000..b350a91b --- /dev/null +++ b/spec/approvals/formatter/error-failure @@ -0,0 +1 @@ +# \ No newline at end of file diff --git a/spec/approvals/formatter/error-not-found b/spec/approvals/formatter/error-not-found new file mode 100644 index 00000000..1e1a6ccb --- /dev/null +++ b/spec/approvals/formatter/error-not-found @@ -0,0 +1 @@ +# \ No newline at end of file diff --git a/spec/approvals/formatter/internal b/spec/approvals/formatter/internal new file mode 100644 index 00000000..086b573e --- /dev/null +++ b/spec/approvals/formatter/internal @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +funk() { + cat <