mirror of
https://github.com/SabreTools/SabreTools.CommandLine.git
synced 2026-11-15 16:40:42 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7831da365c | ||
|
|
c5ec963fd1 | ||
|
|
4efc3becf5 | ||
|
|
88c2c25a71 | ||
|
|
71ef11ac49 | ||
|
|
2753759b33 | ||
|
|
09f08a9031 | ||
|
|
8695e519c6 | ||
|
|
754aa6bc32 | ||
|
|
1da60f1091 | ||
|
|
5fd4b8b0a5 | ||
|
|
2bca1ff744 | ||
|
|
8be595bf56 | ||
|
|
47d067df1c | ||
|
|
90bfcc8fe2 | ||
|
|
be081c701d | ||
|
|
66b0e27d4e | ||
|
|
573fe2e160 | ||
|
|
6ad9fa4521 | ||
|
|
a261f428f2 | ||
|
|
5011331164 | ||
|
|
e257c30892 | ||
|
|
029cfe6dc6 | ||
|
|
f38b8734b5 | ||
|
|
df54b92031 | ||
|
|
39acb90dbc |
167
.editorconfig
Normal file
167
.editorconfig
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# C# files
|
||||||
|
[*.cs]
|
||||||
|
|
||||||
|
# Indentation and spacing
|
||||||
|
charset = utf-8
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
tab_width = 4
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
max_line_length = unset
|
||||||
|
|
||||||
|
# using directive preferences
|
||||||
|
csharp_using_directive_placement = outside_namespace
|
||||||
|
dotnet_diagnostic.IDE0005.severity = error
|
||||||
|
|
||||||
|
# Code-block preferences
|
||||||
|
csharp_style_namespace_declarations = block_scoped
|
||||||
|
csharp_style_prefer_method_group_conversion = true
|
||||||
|
csharp_style_prefer_top_level_statements = false
|
||||||
|
|
||||||
|
# Expression-level preferences
|
||||||
|
csharp_prefer_simple_default_expression = true
|
||||||
|
csharp_style_inlined_variable_declaration = true
|
||||||
|
csharp_style_unused_value_assignment_preference = discard_variable
|
||||||
|
csharp_style_unused_value_expression_statement_preference = discard_variable
|
||||||
|
dotnet_diagnostic.IDE0001.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0002.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0004.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0010.severity = error
|
||||||
|
dotnet_diagnostic.IDE0051.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0052.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0072.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0080.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0100.severity = error
|
||||||
|
dotnet_diagnostic.IDE0110.severity = error
|
||||||
|
dotnet_diagnostic.IDE0120.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0121.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0240.severity = error
|
||||||
|
dotnet_diagnostic.IDE0241.severity = error
|
||||||
|
dotnet_style_coalesce_expression = true
|
||||||
|
dotnet_style_namespace_match_folder = false
|
||||||
|
dotnet_style_null_propagation = true
|
||||||
|
dotnet_style_prefer_auto_properties = true
|
||||||
|
dotnet_style_prefer_collection_expression = when_types_loosely_match
|
||||||
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
|
||||||
|
dotnet_style_prefer_compound_assignment = true
|
||||||
|
csharp_style_prefer_simple_property_accessors = true
|
||||||
|
dotnet_style_prefer_simplified_interpolation = true
|
||||||
|
dotnet_style_prefer_simplified_boolean_expressions = true
|
||||||
|
csharp_style_prefer_unbound_generic_type_in_nameof = true
|
||||||
|
|
||||||
|
# Field preferences
|
||||||
|
dotnet_diagnostic.IDE0044.severity = warning
|
||||||
|
dotnet_style_readonly_field = true
|
||||||
|
|
||||||
|
# Language keyword vs. framework types preferences
|
||||||
|
dotnet_diagnostic.IDE0049.severity = error
|
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true
|
||||||
|
dotnet_style_predefined_type_for_member_access = true
|
||||||
|
|
||||||
|
# Modifier preferences
|
||||||
|
csharp_prefer_static_local_function = true
|
||||||
|
csharp_style_prefer_readonly_struct = true
|
||||||
|
dotnet_diagnostic.IDE0036.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0040.severity = error
|
||||||
|
dotnet_diagnostic.IDE0380.severity = error
|
||||||
|
dotnet_style_require_accessibility_modifiers = always
|
||||||
|
|
||||||
|
# New-line preferences
|
||||||
|
dotnet_diagnostic.IDE2000.severity = warning
|
||||||
|
dotnet_diagnostic.IDE2002.severity = warning
|
||||||
|
dotnet_diagnostic.IDE2003.severity = warning
|
||||||
|
dotnet_diagnostic.IDE2004.severity = warning
|
||||||
|
dotnet_diagnostic.IDE2005.severity = warning
|
||||||
|
dotnet_diagnostic.IDE2006.severity = warning
|
||||||
|
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false
|
||||||
|
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false
|
||||||
|
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false
|
||||||
|
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false
|
||||||
|
dotnet_style_allow_multiple_blank_lines_experimental = false
|
||||||
|
dotnet_style_allow_statement_immediately_after_block_experimental = false
|
||||||
|
|
||||||
|
# Null-checking preferences
|
||||||
|
csharp_style_conditional_delegate_call = true
|
||||||
|
|
||||||
|
# Parameter preferences
|
||||||
|
dotnet_code_quality_unused_parameters = all
|
||||||
|
dotnet_diagnostic.IDE0280.severity = error
|
||||||
|
|
||||||
|
# Parentheses preferences
|
||||||
|
dotnet_diagnostic.IDE0047.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0048.severity = warning
|
||||||
|
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
|
||||||
|
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
|
||||||
|
dotnet_style_parentheses_in_other_operators = always_for_clarity
|
||||||
|
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
|
||||||
|
|
||||||
|
# Pattern-matching preferences
|
||||||
|
dotnet_diagnostic.IDE0019.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0020.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0038.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0066.severity = none
|
||||||
|
dotnet_diagnostic.IDE0083.severity = warning
|
||||||
|
dotnet_diagnostic.IDE0260.severity = warning
|
||||||
|
csharp_style_pattern_matching_over_as_with_null_check = true
|
||||||
|
csharp_style_pattern_matching_over_is_with_cast_check = true
|
||||||
|
csharp_style_prefer_not_pattern = true
|
||||||
|
csharp_style_prefer_pattern_matching = true
|
||||||
|
|
||||||
|
# this. and Me. preferences
|
||||||
|
dotnet_style_qualification_for_event = false
|
||||||
|
dotnet_style_qualification_for_field = false
|
||||||
|
dotnet_style_qualification_for_method = false
|
||||||
|
dotnet_style_qualification_for_property = false
|
||||||
|
|
||||||
|
# var preferences
|
||||||
|
csharp_style_var_for_built_in_types = false
|
||||||
|
csharp_style_var_when_type_is_apparent = true
|
||||||
|
|
||||||
|
# .NET formatting options
|
||||||
|
dotnet_separate_import_directive_groups = false
|
||||||
|
dotnet_sort_system_directives_first = true
|
||||||
|
|
||||||
|
# C# formatting options
|
||||||
|
csharp_indent_block_contents = true
|
||||||
|
csharp_indent_braces = false
|
||||||
|
csharp_indent_case_contents = true
|
||||||
|
csharp_indent_case_contents_when_block = false
|
||||||
|
csharp_indent_labels = one_less_than_current
|
||||||
|
csharp_indent_switch_labels = true
|
||||||
|
csharp_new_line_before_catch = true
|
||||||
|
csharp_new_line_before_else = true
|
||||||
|
csharp_new_line_before_finally = true
|
||||||
|
csharp_new_line_before_members_in_anonymous_types = true
|
||||||
|
csharp_new_line_before_members_in_object_initializers = true
|
||||||
|
csharp_new_line_before_open_brace = all
|
||||||
|
csharp_new_line_between_query_expression_clauses = true
|
||||||
|
csharp_preserve_single_line_blocks = true
|
||||||
|
csharp_preserve_single_line_statements = true
|
||||||
|
csharp_space_after_cast = false
|
||||||
|
csharp_space_after_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_after_comma = true
|
||||||
|
csharp_space_after_dot = false
|
||||||
|
csharp_space_after_keywords_in_control_flow_statements = true
|
||||||
|
csharp_space_after_semicolon_in_for_statement = true
|
||||||
|
csharp_space_around_binary_operators = before_and_after
|
||||||
|
csharp_space_around_declaration_statements = false
|
||||||
|
csharp_space_before_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_before_comma = false
|
||||||
|
csharp_space_before_dot = false
|
||||||
|
csharp_space_before_open_square_brackets = false
|
||||||
|
csharp_space_before_semicolon_in_for_statement = false
|
||||||
|
csharp_space_between_empty_square_brackets = false
|
||||||
|
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||||
|
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_parentheses = false
|
||||||
|
csharp_space_between_square_brackets = false
|
||||||
69
.github/workflows/build_and_test.yml
vendored
69
.github/workflows/build_and_test.yml
vendored
@@ -1,40 +1,49 @@
|
|||||||
name: Build and Test
|
name: Build and Test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main" ]
|
branches: ["main"]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v5
|
||||||
with:
|
with:
|
||||||
dotnet-version: |
|
dotnet-version: |
|
||||||
6.0.x
|
8.0.x
|
||||||
8.0.x
|
9.0.x
|
||||||
9.0.x
|
10.0.x
|
||||||
|
11.0.x
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: dotnet test
|
run: dotnet test
|
||||||
|
|
||||||
- name: Run publish script
|
- name: Run publish script
|
||||||
run: ./publish-nix.sh
|
run: ./publish-nix.sh
|
||||||
|
|
||||||
- name: Upload to rolling
|
- name: Update rolling tag
|
||||||
uses: ncipollo/release-action@v1.14.0
|
run: |
|
||||||
with:
|
git config user.name "github-actions[bot]"
|
||||||
allowUpdates: True
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
artifacts: "*.nupkg,*.snupkg"
|
git tag -f rolling
|
||||||
body: 'Last built commit: ${{ github.sha }}'
|
git push origin :refs/tags/rolling || true
|
||||||
name: 'Rolling Release'
|
git push origin rolling --force
|
||||||
prerelease: True
|
|
||||||
replacesArtifacts: True
|
- name: Upload to rolling
|
||||||
tag: "rolling"
|
uses: ncipollo/release-action@v1.20.0
|
||||||
updateOnlyUnreleased: True
|
with:
|
||||||
|
allowUpdates: True
|
||||||
|
artifacts: "*.nupkg,*.snupkg"
|
||||||
|
body: "Last built commit: ${{ github.sha }}"
|
||||||
|
name: "Rolling Release"
|
||||||
|
prerelease: True
|
||||||
|
replacesArtifacts: True
|
||||||
|
tag: "rolling"
|
||||||
|
updateOnlyUnreleased: True
|
||||||
|
|||||||
31
.github/workflows/check_pr.yml
vendored
31
.github/workflows/check_pr.yml
vendored
@@ -3,21 +3,22 @@ name: Build PR
|
|||||||
on: [pull_request]
|
on: [pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v5
|
||||||
with:
|
with:
|
||||||
dotnet-version: |
|
dotnet-version: |
|
||||||
6.0.x
|
8.0.x
|
||||||
8.0.x
|
9.0.x
|
||||||
9.0.x
|
10.0.x
|
||||||
|
11.0.x
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: dotnet build
|
run: dotnet build
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: dotnet test
|
run: dotnet test
|
||||||
|
|||||||
329
.gitignore
vendored
329
.gitignore
vendored
@@ -1,22 +1,18 @@
|
|||||||
*.swp
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
*.*~
|
## files generated by popular Visual Studio add-ons.
|
||||||
project.lock.json
|
##
|
||||||
.DS_Store
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
*.pyc
|
|
||||||
nupkg/
|
|
||||||
|
|
||||||
# Visual Studio Code
|
|
||||||
.vscode
|
|
||||||
|
|
||||||
# Rider
|
|
||||||
.idea
|
|
||||||
|
|
||||||
# User-specific files
|
# User-specific files
|
||||||
|
*.lscache
|
||||||
*.suo
|
*.suo
|
||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
# Build results
|
# Build results
|
||||||
[Dd]ebug/
|
[Dd]ebug/
|
||||||
[Dd]ebugPublic/
|
[Dd]ebugPublic/
|
||||||
@@ -24,15 +20,312 @@ nupkg/
|
|||||||
[Rr]eleases/
|
[Rr]eleases/
|
||||||
x64/
|
x64/
|
||||||
x86/
|
x86/
|
||||||
build/
|
|
||||||
bld/
|
bld/
|
||||||
[Bb]in/
|
[Bb]in/
|
||||||
[Oo]bj/
|
[Oo]bj/
|
||||||
[Oo]ut/
|
[Ll]og/
|
||||||
msbuild.log
|
|
||||||
msbuild.err
|
|
||||||
msbuild.wrn
|
|
||||||
|
|
||||||
# Visual Studio 2015
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
.vs/
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUNIT
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
**/Properties/launchSettings.json
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_i.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# JustCode is a .NET coding add-in
|
||||||
|
.JustCode
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
# CodeRush
|
||||||
|
.cr/
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|||||||
24
.vscode/tasks.json
vendored
Executable file
24
.vscode/tasks.json
vendored
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "shell",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
// Ask dotnet build to generate full paths for file names.
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
// Do not generate summary otherwise it leads to duplicate errors in Problems panel
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "silent"
|
||||||
|
},
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2016-2025 Matt Nadareski
|
Copyright (c) 2016-2026 Matt Nadareski
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ Three reference feature implementations are included in the `SabreTools.CommandL
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `Help` | Outputs either a generic help text or one specific to a feature name included as the second argument |
|
| `Help` | Outputs either a generic help text or one specific to a feature name included as the second argument |
|
||||||
| `HelpExtended` | Outputs either a generic help text with extended descriptions or one specific to a feature name included as the second argument |
|
| `HelpExtended` | Outputs either a generic help text with extended descriptions or one specific to a feature name included as the second argument |
|
||||||
|
| `Manpage` | Outputs a roff-formatted manpage based on the current command set |
|
||||||
| `Version` | Outputs the version number of the program, derived from the informational version |
|
| `Version` | Outputs the version number of the program, derived from the informational version |
|
||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using SabreTools.CommandLine.Inputs;
|
using SabreTools.CommandLine.Inputs;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -13,9 +14,16 @@ namespace SabreTools.CommandLine.Test
|
|||||||
var input1 = new FlagInput("input1", "--input1", "input1");
|
var input1 = new FlagInput("input1", "--input1", "input1");
|
||||||
var input2 = new FlagInput("input2", "--input2", "input2");
|
var input2 = new FlagInput("input2", "--input2", "input2");
|
||||||
|
|
||||||
|
var feature1 = new MockFeature("feature1", "feature1", "feature1");
|
||||||
|
var inputA = new FlagInput("inputA", "--inputA", "inputA");
|
||||||
|
var inputB = new FlagInput("inputB", "--inputB", "inputB");
|
||||||
|
feature1.Add(inputA);
|
||||||
|
feature1.Add(inputB);
|
||||||
|
|
||||||
var featureSet = new CommandSet();
|
var featureSet = new CommandSet();
|
||||||
featureSet.Add(input1);
|
featureSet.Add(input1);
|
||||||
featureSet.Add(input2);
|
featureSet.Add(input2);
|
||||||
|
featureSet.AddFrom(feature1);
|
||||||
|
|
||||||
var actualInput1 = featureSet["input1"];
|
var actualInput1 = featureSet["input1"];
|
||||||
Assert.NotNull(actualInput1);
|
Assert.NotNull(actualInput1);
|
||||||
@@ -27,6 +35,14 @@ namespace SabreTools.CommandLine.Test
|
|||||||
|
|
||||||
var actualInput3 = featureSet["input3"];
|
var actualInput3 = featureSet["input3"];
|
||||||
Assert.Null(actualInput3);
|
Assert.Null(actualInput3);
|
||||||
|
|
||||||
|
var actualInputA = featureSet["inputA"];
|
||||||
|
Assert.NotNull(actualInputA);
|
||||||
|
Assert.Equal("inputA", actualInputA.Name);
|
||||||
|
|
||||||
|
var actualinputB = featureSet["inputB"];
|
||||||
|
Assert.NotNull(actualinputB);
|
||||||
|
Assert.Equal("inputB", actualinputB.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -764,7 +780,7 @@ namespace SabreTools.CommandLine.Test
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void ProcessArgs_EmptyArgs_Success()
|
public void ProcessArgs_EmptyArgs_Success()
|
||||||
{
|
{
|
||||||
CommandSet commandSet = new CommandSet();
|
var commandSet = new CommandSet();
|
||||||
|
|
||||||
string[] args = [];
|
string[] args = [];
|
||||||
|
|
||||||
@@ -775,7 +791,7 @@ namespace SabreTools.CommandLine.Test
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void ProcessArgs_ValidArgs_Success()
|
public void ProcessArgs_ValidArgs_Success()
|
||||||
{
|
{
|
||||||
CommandSet commandSet = new CommandSet();
|
var commandSet = new CommandSet();
|
||||||
Feature feature = new MockFeature("a", "a", "a");
|
Feature feature = new MockFeature("a", "a", "a");
|
||||||
feature.Add(new FlagInput("b", "b", "b"));
|
feature.Add(new FlagInput("b", "b", "b"));
|
||||||
feature.Add(new FlagInput("c", "c", "c"));
|
feature.Add(new FlagInput("c", "c", "c"));
|
||||||
@@ -791,7 +807,7 @@ namespace SabreTools.CommandLine.Test
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void ProcessArgs_InvalidArg_AddedAsGeneric()
|
public void ProcessArgs_InvalidArg_AddedAsGeneric()
|
||||||
{
|
{
|
||||||
CommandSet commandSet = new CommandSet();
|
var commandSet = new CommandSet();
|
||||||
Feature feature = new MockFeature("a", "a", "a");
|
Feature feature = new MockFeature("a", "a", "a");
|
||||||
feature.Add(new FlagInput("b", "b", "b"));
|
feature.Add(new FlagInput("b", "b", "b"));
|
||||||
feature.Add(new FlagInput("d", "d", "d"));
|
feature.Add(new FlagInput("d", "d", "d"));
|
||||||
@@ -808,7 +824,7 @@ namespace SabreTools.CommandLine.Test
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void ProcessArgs_NestedArgs_Success()
|
public void ProcessArgs_NestedArgs_Success()
|
||||||
{
|
{
|
||||||
CommandSet commandSet = new CommandSet();
|
var commandSet = new CommandSet();
|
||||||
Feature feature = new MockFeature("a", "a", "a");
|
Feature feature = new MockFeature("a", "a", "a");
|
||||||
var sub = new FlagInput("b", "b", "b");
|
var sub = new FlagInput("b", "b", "b");
|
||||||
sub.Add(new FlagInput("c", "c", "c"));
|
sub.Add(new FlagInput("c", "c", "c"));
|
||||||
@@ -824,6 +840,152 @@ namespace SabreTools.CommandLine.Test
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Manpage Output
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OutputManpage_Structure_ContainsRequiredSections()
|
||||||
|
{
|
||||||
|
var set = BuildSampleSet();
|
||||||
|
var info = new ManpageInfo("sample")
|
||||||
|
{
|
||||||
|
Version = "sample 1.0",
|
||||||
|
Description = "do sample things",
|
||||||
|
};
|
||||||
|
|
||||||
|
string page = set.OutputManpage(info);
|
||||||
|
|
||||||
|
Assert.Contains(".TH \"SAMPLE\" \"1\"", page);
|
||||||
|
Assert.Contains(".SH NAME", page);
|
||||||
|
Assert.Contains("sample \\- do sample things", page);
|
||||||
|
Assert.Contains(".SH SYNOPSIS", page);
|
||||||
|
Assert.Contains(".SH DESCRIPTION", page);
|
||||||
|
Assert.Contains(".SH OPTIONS", page);
|
||||||
|
Assert.Contains(".TP", page);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OutputManpage_EscapesHyphensInFlags()
|
||||||
|
{
|
||||||
|
var set = BuildSampleSet();
|
||||||
|
var info = new ManpageInfo("sample");
|
||||||
|
|
||||||
|
string page = set.OutputManpage(info);
|
||||||
|
|
||||||
|
Assert.Contains(".B \\-\\-convert", page);
|
||||||
|
Assert.DoesNotContain(".B --convert", page);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OutputManpage_Verbose_TogglesDetailedText()
|
||||||
|
{
|
||||||
|
var set = BuildSampleSet();
|
||||||
|
var info = new ManpageInfo("sample");
|
||||||
|
|
||||||
|
string terse = set.OutputManpage(info, includeVerbose: false);
|
||||||
|
string verbose = set.OutputManpage(info, includeVerbose: true);
|
||||||
|
|
||||||
|
Assert.DoesNotContain("Built\\-in to most of the programs", terse);
|
||||||
|
Assert.Contains("Built\\-in to most of the programs", verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OutputManpage_NestsChildren()
|
||||||
|
{
|
||||||
|
var set = BuildSampleSet();
|
||||||
|
var info = new ManpageInfo("sample");
|
||||||
|
|
||||||
|
string page = set.OutputManpage(info);
|
||||||
|
|
||||||
|
Assert.Contains(".RS", page);
|
||||||
|
Assert.Contains(".RE", page);
|
||||||
|
Assert.Contains(".B \\-\\-output", page);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OutputManpage_HonorsCustomOptionsHeading()
|
||||||
|
{
|
||||||
|
var set = BuildSampleSet();
|
||||||
|
var info = new ManpageInfo("sample") { OptionsHeading = "COMMANDS" };
|
||||||
|
|
||||||
|
string page = set.OutputManpage(info);
|
||||||
|
|
||||||
|
Assert.Contains(".SH COMMANDS", page);
|
||||||
|
Assert.DoesNotContain(".SH OPTIONS", page);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OutputManpage_LeavesBlankDateForStamping()
|
||||||
|
{
|
||||||
|
var set = BuildSampleSet();
|
||||||
|
var info = new ManpageInfo("sample") { Version = "sample 1.0" };
|
||||||
|
|
||||||
|
string page = set.OutputManpage(info);
|
||||||
|
|
||||||
|
// Section, then an empty date field, then the version
|
||||||
|
Assert.Contains(".TH \"SAMPLE\" \"1\" \"\" \"sample 1.0\"", page);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OutputManpage_LinesWithinWidth()
|
||||||
|
{
|
||||||
|
var set = BuildSampleSet();
|
||||||
|
var info = new ManpageInfo("sample") { Description = "do sample things" };
|
||||||
|
|
||||||
|
string page = set.OutputManpage(info, includeVerbose: true);
|
||||||
|
|
||||||
|
foreach (string line in page.Split('\n'))
|
||||||
|
{
|
||||||
|
Assert.True(line.Length <= 78, $"Line exceeds 78 columns: {line}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OutputManpage_FileOverload_WritesSameContent()
|
||||||
|
{
|
||||||
|
var set = BuildSampleSet();
|
||||||
|
var info = new ManpageInfo("sample");
|
||||||
|
|
||||||
|
string expected = set.OutputManpage(info);
|
||||||
|
string path = Path.Combine(Path.GetTempPath(), $"sabretools-manpage-{Guid.NewGuid():N}.1");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
set.OutputManpage(path, info);
|
||||||
|
string actual = File.ReadAllText(path);
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build a representative command set with nested inputs and detail text
|
||||||
|
/// </summary>
|
||||||
|
private static CommandSet BuildSampleSet()
|
||||||
|
{
|
||||||
|
var set = new CommandSet("Sample header line");
|
||||||
|
|
||||||
|
var help = new MockFeature("Help",
|
||||||
|
["?", "h", "help"],
|
||||||
|
"Show this help",
|
||||||
|
"Built-in to most of the programs is a basic help text.");
|
||||||
|
set.Add(help);
|
||||||
|
|
||||||
|
var convert = new MockFeature("Convert",
|
||||||
|
"--convert",
|
||||||
|
"Convert input files",
|
||||||
|
"Converts the provided input files into the desired output format.");
|
||||||
|
convert.Add(new StringInput("output", "--output", "Set the output path"));
|
||||||
|
convert.Add(new FlagInput("force", "--force", "Overwrite existing files"));
|
||||||
|
set.Add(convert);
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mock Feature implementation for testing
|
/// Mock Feature implementation for testing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
83
SabreTools.CommandLine.Test/Features/ManpageTests.cs
Normal file
83
SabreTools.CommandLine.Test/Features/ManpageTests.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using SabreTools.CommandLine.Features;
|
||||||
|
using SabreTools.CommandLine.Inputs;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SabreTools.CommandLine.Test.Features
|
||||||
|
{
|
||||||
|
public class ManpageTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ManpageFeature_WritesPageToStandardOutput()
|
||||||
|
{
|
||||||
|
var set = BuildSampleSet();
|
||||||
|
var info = new ManpageInfo("sample") { Version = "sample 1.0" };
|
||||||
|
set.Add(new Manpage(info));
|
||||||
|
|
||||||
|
var original = Console.Out;
|
||||||
|
var writer = new StringWriter();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Console.SetOut(writer);
|
||||||
|
bool result = set.ProcessArgs(["man"]);
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Console.SetOut(original);
|
||||||
|
}
|
||||||
|
|
||||||
|
string actual = writer.ToString();
|
||||||
|
Assert.Contains(".TH \"SAMPLE\" \"1\"", actual);
|
||||||
|
Assert.Contains(".SH NAME", actual);
|
||||||
|
Assert.Contains(".B \\-\\-convert", actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build a representative command set with nested inputs and detail text
|
||||||
|
/// </summary>
|
||||||
|
private static CommandSet BuildSampleSet()
|
||||||
|
{
|
||||||
|
var set = new CommandSet("Sample header line");
|
||||||
|
|
||||||
|
var help = new MockFeature("Help",
|
||||||
|
["?", "h", "help"],
|
||||||
|
"Show this help",
|
||||||
|
"Built-in to most of the programs is a basic help text.");
|
||||||
|
set.Add(help);
|
||||||
|
|
||||||
|
var convert = new MockFeature("Convert",
|
||||||
|
"--convert",
|
||||||
|
"Convert input files",
|
||||||
|
"Converts the provided input files into the desired output format.");
|
||||||
|
convert.Add(new StringInput("output", "--output", "Set the output path"));
|
||||||
|
convert.Add(new FlagInput("force", "--force", "Overwrite existing files"));
|
||||||
|
set.Add(convert);
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mock Feature implementation for testing
|
||||||
|
/// </summary>
|
||||||
|
private class MockFeature : Feature
|
||||||
|
{
|
||||||
|
public MockFeature(string name, string flag, string description, string? detailed = null)
|
||||||
|
: base(name, flag, description, detailed)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public MockFeature(string name, string[] flags, string description, string? detailed = null)
|
||||||
|
: base(name, flags, description, detailed)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Execute() => true;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool VerifyInputs() => true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
<TargetFrameworks>net8.0;net9.0;net10.0;net11.0</TargetFrameworks>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
@@ -12,13 +12,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
<PackageReference Include="coverlet.collector" Version="10.1.0">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.11.1" />
|
||||||
<PackageReference Include="xunit" Version="2.9.3" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.1">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using SabreTools.CommandLine.Features;
|
using SabreTools.CommandLine.Features;
|
||||||
using SabreTools.CommandLine.Inputs;
|
using SabreTools.CommandLine.Inputs;
|
||||||
|
using SabreTools.CommandLine.Tools;
|
||||||
|
|
||||||
namespace SabreTools.CommandLine
|
namespace SabreTools.CommandLine
|
||||||
{
|
{
|
||||||
@@ -40,6 +42,41 @@ namespace SabreTools.CommandLine
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom help text to print instead of the automatically
|
||||||
|
/// generated generic help
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This only replaces the automatically generated help
|
||||||
|
/// for <see cref="OutputGenericHelp"/>. It does not impact
|
||||||
|
/// either <see cref="OutputAllHelp"/> or <see cref="OutputFeatureHelp"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public string? CustomHelp { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Feature that represents the default functionality
|
||||||
|
/// for a command set
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// It is recommended to use this in applications that
|
||||||
|
/// do not need multiple distinct functional modes.
|
||||||
|
///
|
||||||
|
/// When printing help text, the flags and description
|
||||||
|
/// of this feature will be omitted, instead printing
|
||||||
|
/// the children of the feature directly at the
|
||||||
|
/// top level.
|
||||||
|
///
|
||||||
|
/// If the default feature is included as a normal
|
||||||
|
/// top-level input for the command set, then the flags
|
||||||
|
/// will be printed twice - once under the feature
|
||||||
|
/// itself and once at the top level.
|
||||||
|
/// </remarks>
|
||||||
|
public Feature? DefaultFeature { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -120,6 +157,29 @@ namespace SabreTools.CommandLine
|
|||||||
public void Add(UserInput input)
|
public void Add(UserInput input)
|
||||||
=> _inputs.Add(input.Name, input);
|
=> _inputs.Add(input.Name, input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add all children from an input to the set
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">UserInput object to retrieve children from</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// This should only be used in situations where an input is defined
|
||||||
|
/// but not used within the context of the command set directly.
|
||||||
|
///
|
||||||
|
/// This is helpful for when there are applications with default functionality
|
||||||
|
/// that need to be able to expose both defined features as well as
|
||||||
|
/// the default functionality in help text.
|
||||||
|
///
|
||||||
|
/// If there is any overlap between existing names and the names from
|
||||||
|
/// any of the children, this operation will overwrite them.
|
||||||
|
/// </reamrks>
|
||||||
|
public void AddFrom(UserInput input)
|
||||||
|
{
|
||||||
|
foreach (var kvp in input.Children)
|
||||||
|
{
|
||||||
|
_inputs.Add(kvp.Key, kvp.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Children
|
#region Children
|
||||||
@@ -730,13 +790,34 @@ namespace SabreTools.CommandLine
|
|||||||
if (_header.Count > 0)
|
if (_header.Count > 0)
|
||||||
output.AddRange(_header);
|
output.AddRange(_header);
|
||||||
|
|
||||||
// Now append all available top-level flags
|
// If custom help text is defined
|
||||||
output.Add("Available options:");
|
if (CustomHelp is not null)
|
||||||
foreach (var input in _inputs.Values)
|
|
||||||
{
|
{
|
||||||
var outputs = input.Format(pre: 2, midpoint: 30, detailed);
|
string customHelp = CustomHelp.Replace("\r\n", "\n");
|
||||||
if (outputs != null)
|
string[] customLines = customHelp.Split('\n');
|
||||||
output.AddRange(outputs);
|
output.AddRange(customLines);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Append all available top-level flags
|
||||||
|
output.Add("Available options:");
|
||||||
|
foreach (var input in _inputs.Values)
|
||||||
|
{
|
||||||
|
var outputs = input.Format(pre: 2, midpoint: 30, detailed);
|
||||||
|
if (outputs is not null)
|
||||||
|
output.AddRange(outputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a default feature
|
||||||
|
if (DefaultFeature is not null)
|
||||||
|
{
|
||||||
|
foreach (var input in DefaultFeature.Children)
|
||||||
|
{
|
||||||
|
var outputs = input.Value.Format(pre: 2, midpoint: 30, detailed);
|
||||||
|
if (outputs is not null)
|
||||||
|
output.AddRange(outputs);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append the footer, if needed
|
// Append the footer, if needed
|
||||||
@@ -760,15 +841,26 @@ namespace SabreTools.CommandLine
|
|||||||
if (_header.Count > 0)
|
if (_header.Count > 0)
|
||||||
output.AddRange(_header);
|
output.AddRange(_header);
|
||||||
|
|
||||||
// Now append all available flags recursively
|
// Append all available flags recursively
|
||||||
output.Add("Available options:");
|
output.Add("Available options:");
|
||||||
foreach (var input in _inputs.Values)
|
foreach (var input in _inputs.Values)
|
||||||
{
|
{
|
||||||
var outputs = input.FormatRecursive(pre: 2, midpoint: 30, detailed);
|
var outputs = input.FormatRecursive(pre: 2, midpoint: 30, detailed);
|
||||||
if (outputs != null)
|
if (outputs is not null)
|
||||||
output.AddRange(outputs);
|
output.AddRange(outputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there is a default feature
|
||||||
|
if (DefaultFeature is not null)
|
||||||
|
{
|
||||||
|
foreach (var input in DefaultFeature.Children)
|
||||||
|
{
|
||||||
|
var outputs = input.Value.Format(pre: 2, midpoint: 30, detailed);
|
||||||
|
if (outputs is not null)
|
||||||
|
output.AddRange(outputs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Append the footer, if needed
|
// Append the footer, if needed
|
||||||
if (_footer.Count > 0)
|
if (_footer.Count > 0)
|
||||||
output.AddRange(_footer);
|
output.AddRange(_footer);
|
||||||
@@ -873,6 +965,120 @@ namespace SabreTools.CommandLine
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Manpage Output
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate a man page from the current set of inputs
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">Document-level metadata for the man page</param>
|
||||||
|
/// <param name="includeVerbose">True if detailed descriptions should be included, false otherwise</param>
|
||||||
|
/// <returns>The complete man page as a roff-formatted string</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// The output uses only the portable man(7) macro set so that it
|
||||||
|
/// renders without warnings under both groff and mandoc. The page
|
||||||
|
/// content is derived from the same model used to format help text,
|
||||||
|
/// so it stays in sync with the output of the help features.
|
||||||
|
/// </remarks>
|
||||||
|
public string OutputManpage(ManpageInfo info, bool includeVerbose = false)
|
||||||
|
{
|
||||||
|
// Start building the output list
|
||||||
|
List<string> output = [];
|
||||||
|
|
||||||
|
// Leading comment and title line
|
||||||
|
output.Add(".\\\" Generated from the command-line model. Do not edit by hand.");
|
||||||
|
output.Add(BuildTitleHeader(info));
|
||||||
|
|
||||||
|
// Name section
|
||||||
|
output.Add(".SH NAME");
|
||||||
|
string nameLine = Roff.Escape(info.Name);
|
||||||
|
if (!string.IsNullOrEmpty(info.Description))
|
||||||
|
nameLine += $" \\- {Roff.Escape(info.Description)}";
|
||||||
|
|
||||||
|
output.Add(nameLine);
|
||||||
|
|
||||||
|
// Synopsis section
|
||||||
|
output.Add(".SH SYNOPSIS");
|
||||||
|
output.Add(item: $".B {Roff.Escape(info.Name)}");
|
||||||
|
output.Add("[options]");
|
||||||
|
|
||||||
|
// Description section, derived from the header lines
|
||||||
|
if (_header.Count > 0)
|
||||||
|
{
|
||||||
|
output.Add(".SH DESCRIPTION");
|
||||||
|
foreach (string line in _header)
|
||||||
|
{
|
||||||
|
output.AddRange(Roff.FormatText(line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options section, derived from all available inputs
|
||||||
|
output.Add($".SH {info.OptionsHeading}");
|
||||||
|
foreach (var input in _inputs.Values)
|
||||||
|
{
|
||||||
|
output.AddRange(input.FormatManpage(includeVerbose));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a default feature, include its children directly
|
||||||
|
if (DefaultFeature is not null)
|
||||||
|
{
|
||||||
|
foreach (var input in DefaultFeature.Children.Values)
|
||||||
|
{
|
||||||
|
output.AddRange(input.FormatManpage(includeVerbose));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notes section, derived from the footer lines
|
||||||
|
if (_footer.Count > 0)
|
||||||
|
{
|
||||||
|
output.Add(".SH NOTES");
|
||||||
|
foreach (string line in _footer)
|
||||||
|
{
|
||||||
|
output.AddRange(Roff.FormatText(line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join the lines with a trailing newline for a well-formed file
|
||||||
|
return string.Join("\n", [.. output]) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generate a man page from the current set of inputs and write it to a file
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Path to write the generated man page to</param>
|
||||||
|
/// <param name="info">Document-level metadata for the man page</param>
|
||||||
|
/// <param name="includeVerbose">True if detailed descriptions should be included, false otherwise</param>
|
||||||
|
/// <remarks>The file is written as UTF-8 without a byte order mark</remarks>
|
||||||
|
public void OutputManpage(string path, ManpageInfo info, bool includeVerbose = false)
|
||||||
|
{
|
||||||
|
string content = OutputManpage(info, includeVerbose);
|
||||||
|
File.WriteAllText(path, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build the roff <c>.TH</c> title line from the page metadata
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="info">Document-level metadata for the man page</param>
|
||||||
|
/// <returns>The formatted title line</returns>
|
||||||
|
private static string BuildTitleHeader(ManpageInfo info)
|
||||||
|
{
|
||||||
|
return ".TH "
|
||||||
|
+ Quote(info.Name.ToUpperInvariant())
|
||||||
|
+ " " + Quote(info.Section)
|
||||||
|
+ " " + Quote(info.Date)
|
||||||
|
+ " " + Quote(info.Version)
|
||||||
|
+ " " + Quote(info.Title);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wrap an escaped value in double quotes for a <c>.TH</c> field
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Value to quote, if any</param>
|
||||||
|
/// <returns>The escaped, quoted value</returns>
|
||||||
|
private static string Quote(string? value)
|
||||||
|
=> $"\"{Roff.EscapeField(value)}\"";
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Processing
|
#region Processing
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -891,7 +1097,7 @@ namespace SabreTools.CommandLine
|
|||||||
// If there's no arguments, show help
|
// If there's no arguments, show help
|
||||||
if (args.Length == 0)
|
if (args.Length == 0)
|
||||||
{
|
{
|
||||||
OutputGenericHelp();
|
OutputAllHelp();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,7 +1106,7 @@ namespace SabreTools.CommandLine
|
|||||||
|
|
||||||
// Get the associated feature
|
// Get the associated feature
|
||||||
var topLevel = GetTopLevel(featureName);
|
var topLevel = GetTopLevel(featureName);
|
||||||
if (topLevel == null || topLevel is not Feature feature)
|
if (topLevel is null || topLevel is not Feature feature)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"'{featureName}' is not valid feature flag");
|
Console.WriteLine($"'{featureName}' is not valid feature flag");
|
||||||
OutputFeatureHelp(featureName);
|
OutputFeatureHelp(featureName);
|
||||||
@@ -918,6 +1124,11 @@ namespace SabreTools.CommandLine
|
|||||||
helpExtFeature.ProcessArgs(args, 0, this);
|
helpExtFeature.ProcessArgs(args, 0, this);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
else if (topLevel is Manpage manFeature)
|
||||||
|
{
|
||||||
|
manFeature.ProcessArgs(args, 0, this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Now verify that all other flags are valid
|
// Now verify that all other flags are valid
|
||||||
if (!feature.ProcessArgs(args, 1))
|
if (!feature.ProcessArgs(args, 1))
|
||||||
|
|||||||
72
SabreTools.CommandLine/Features/Manpage.cs
Normal file
72
SabreTools.CommandLine/Features/Manpage.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SabreTools.CommandLine.Features
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reference feature that emits a man page for the enclosing command set
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This mirrors the built-in <see cref="Help"/> feature: it must be given
|
||||||
|
/// the enclosing <see cref="CommandSet"/> so that it can render the page
|
||||||
|
/// from the same model used to format help text. The roff output is
|
||||||
|
/// written to standard output so that it can be redirected to a file at
|
||||||
|
/// packaging time, for example <c>tool man > tool.1</c>.
|
||||||
|
/// </remarks>
|
||||||
|
public class Manpage : Feature
|
||||||
|
{
|
||||||
|
public const string DisplayName = "Manpage";
|
||||||
|
|
||||||
|
private static readonly string[] _defaultFlags = ["man"];
|
||||||
|
|
||||||
|
private const string _description = "Generate a man page";
|
||||||
|
|
||||||
|
private const string _detailedDescription = "Write a roff-formatted man page for this program to standard output.";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Document-level metadata for the generated man page
|
||||||
|
/// </summary>
|
||||||
|
private readonly ManpageInfo _info;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if detailed descriptions should be included
|
||||||
|
/// </summary>
|
||||||
|
private readonly bool _includeVerbose;
|
||||||
|
|
||||||
|
public Manpage(ManpageInfo info, bool includeVerbose = true)
|
||||||
|
: base(DisplayName, _defaultFlags, _description, _detailedDescription)
|
||||||
|
{
|
||||||
|
_info = info;
|
||||||
|
_includeVerbose = includeVerbose;
|
||||||
|
RequiresInputs = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Manpage(ManpageInfo info, string[] flags, bool includeVerbose = true)
|
||||||
|
: base(DisplayName, flags, _description, _detailedDescription)
|
||||||
|
{
|
||||||
|
_info = info;
|
||||||
|
_includeVerbose = includeVerbose;
|
||||||
|
RequiresInputs = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool ProcessArgs(string[] args, int index)
|
||||||
|
=> ProcessArgs(args, index, null);
|
||||||
|
|
||||||
|
/// <inheritdoc cref="ProcessArgs(string[], int)"/>
|
||||||
|
/// <param name="parentSet">Reference to the enclosing parent set</param>
|
||||||
|
public bool ProcessArgs(string[] args, int index, CommandSet? parentSet)
|
||||||
|
{
|
||||||
|
// The page can only be rendered with the enclosing set
|
||||||
|
if (parentSet is not null)
|
||||||
|
Console.Write(parentSet.OutputManpage(_info, _includeVerbose));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool VerifyInputs() => true;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Execute() => true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ namespace SabreTools.CommandLine.Features
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var assembly = Assembly.GetEntryAssembly();
|
var assembly = Assembly.GetEntryAssembly();
|
||||||
if (assembly == null)
|
if (assembly is null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var assemblyVersion = Attribute.GetCustomAttribute(assembly, typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute;
|
var assemblyVersion = Attribute.GetCustomAttribute(assembly, typeof(AssemblyInformationalVersionAttribute)) as AssemblyInformationalVersionAttribute;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using SabreTools.CommandLine.Tools;
|
||||||
|
|
||||||
namespace SabreTools.CommandLine.Inputs
|
namespace SabreTools.CommandLine.Inputs
|
||||||
{
|
{
|
||||||
@@ -41,7 +42,7 @@ namespace SabreTools.CommandLine.Inputs
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set of children associated with this input
|
/// Set of children associated with this input
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected readonly Dictionary<string, UserInput> Children = [];
|
protected internal readonly Dictionary<string, UserInput> Children = [];
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -86,7 +87,7 @@ namespace SabreTools.CommandLine.Inputs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public UserInput? this[UserInput subfeature]
|
public UserInput? this[UserInput subfeature]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!Children.TryGetValue(subfeature.Name, out var input))
|
if (!Children.TryGetValue(subfeature.Name, out var input))
|
||||||
return null;
|
return null;
|
||||||
@@ -723,13 +724,45 @@ namespace SabreTools.CommandLine.Inputs
|
|||||||
protected abstract string FormatFlags();
|
protected abstract string FormatFlags();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a padding space based on the given length
|
/// Create roff man page entries for this input and all of its children
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="spaces">Number of padding spaces to add</param>
|
/// <param name="includeVerbose">True if the detailed description should be included, false otherwise</param>
|
||||||
/// <returns>String with requested number of blank spaces</returns>
|
/// <returns>Roff man page lines for this input and its children</returns>
|
||||||
private static string CreatePadding(int spaces) => spaces > 0
|
internal List<string> FormatManpage(bool includeVerbose)
|
||||||
? string.Empty.PadRight(spaces)
|
{
|
||||||
: string.Empty;
|
List<string> output = [];
|
||||||
|
|
||||||
|
// Use the flags as the tag, falling back to the name when none are formatted
|
||||||
|
string tag = FormatFlags();
|
||||||
|
if (tag.Length == 0)
|
||||||
|
tag = Name;
|
||||||
|
|
||||||
|
// Tagged paragraph: bold flags followed by the description
|
||||||
|
output.Add(".TP");
|
||||||
|
output.Add(".B " + Roff.Escape(tag));
|
||||||
|
output.AddRange(Roff.FormatText(Description));
|
||||||
|
|
||||||
|
// Append the detailed description as an additional paragraph
|
||||||
|
if (includeVerbose && !string.IsNullOrEmpty(DetailedDescription))
|
||||||
|
{
|
||||||
|
output.Add(".sp");
|
||||||
|
output.AddRange(Roff.FormatText(DetailedDescription));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indent and append all children recursively
|
||||||
|
if (Children.Count > 0)
|
||||||
|
{
|
||||||
|
output.Add(".RS");
|
||||||
|
foreach (var child in Children.Values)
|
||||||
|
{
|
||||||
|
output.AddRange(child.FormatManpage(includeVerbose));
|
||||||
|
}
|
||||||
|
|
||||||
|
output.Add(".RE");
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Format the standard help output line
|
/// Format the standard help output line
|
||||||
@@ -839,6 +872,15 @@ namespace SabreTools.CommandLine.Inputs
|
|||||||
return outputList;
|
return outputList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a padding space based on the given length
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spaces">Number of padding spaces to add</param>
|
||||||
|
/// <returns>String with requested number of blank spaces</returns>
|
||||||
|
private static string CreatePadding(int spaces) => spaces > 0
|
||||||
|
? string.Empty.PadRight(spaces)
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create formatted help text including all children
|
/// Create formatted help text including all children
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ namespace SabreTools.CommandLine.Inputs
|
|||||||
#region Instance Methods
|
#region Instance Methods
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override abstract bool ProcessInput(string[] args, ref int index);
|
public abstract override bool ProcessInput(string[] args, ref int index);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override abstract string FormatFlags();
|
protected abstract override string FormatFlags();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
69
SabreTools.CommandLine/ManpageInfo.cs
Normal file
69
SabreTools.CommandLine/ManpageInfo.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
namespace SabreTools.CommandLine
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Document-level metadata used when generating a man page
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// These values populate the parts of a man page that cannot be
|
||||||
|
/// derived from the command-line model itself, such as the
|
||||||
|
/// <c>.TH</c> title line and the <c>NAME</c> section.
|
||||||
|
/// </remarks>
|
||||||
|
public class ManpageInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Program name as invoked on the command line
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Used for the <c>.TH</c> title line, the <c>NAME</c> section,
|
||||||
|
/// and the <c>SYNOPSIS</c> section.
|
||||||
|
/// </remarks>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manual section the page belongs to
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Defaults to section 1 (user commands)</remarks>
|
||||||
|
public string Section { get; set; } = "1";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Date used for the <c>.TH</c> title line
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// When left null or empty, the field is emitted blank so that a
|
||||||
|
/// downstream packager can stamp it at build time.
|
||||||
|
/// </remarks>
|
||||||
|
public string? Date { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Version string used for the <c>.TH</c> title line
|
||||||
|
/// </summary>
|
||||||
|
public string? Version { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Manual title used for the <c>.TH</c> title line
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Typically a value such as "User Commands"</remarks>
|
||||||
|
public string? Title { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Short description used for the <c>NAME</c> section
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Should be a single printable line</remarks>
|
||||||
|
public string? Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Section heading used for the list of inputs
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Defaults to "OPTIONS"</remarks>
|
||||||
|
public string OptionsHeading { get; set; } = "OPTIONS";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="ManpageInfo"/> for the given program name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Program name as invoked on the command line</param>
|
||||||
|
public ManpageInfo(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Assembly Properties -->
|
<!-- Assembly Properties -->
|
||||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;netstandard2.0;netstandard2.1</TargetFrameworks>
|
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0;net11.0;netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
@@ -11,12 +11,12 @@
|
|||||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
<Version>1.3.2</Version>
|
<Version>1.6.0</Version>
|
||||||
|
|
||||||
<!-- Package Properties -->
|
<!-- Package Properties -->
|
||||||
<Authors>Matt Nadareski</Authors>
|
<Authors>Matt Nadareski</Authors>
|
||||||
<Description>Commandline parsing and output library</Description>
|
<Description>Commandline parsing and output library</Description>
|
||||||
<Copyright>Copyright (c)2016-2025 Matt Nadareski</Copyright>
|
<Copyright>Copyright (c)2016-2026 Matt Nadareski</Copyright>
|
||||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
<RepositoryUrl>https://github.com/SabreTools/SabreTools.CommandLine</RepositoryUrl>
|
<RepositoryUrl>https://github.com/SabreTools/SabreTools.CommandLine</RepositoryUrl>
|
||||||
|
|||||||
150
SabreTools.CommandLine/Tools/Roff.cs
Normal file
150
SabreTools.CommandLine/Tools/Roff.cs
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SabreTools.CommandLine.Tools
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helpers for emitting roff-formatted man page text
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Output is restricted to the portable man(7) macro set so that the
|
||||||
|
/// same page renders without warnings under both groff and mandoc.
|
||||||
|
/// </remarks>
|
||||||
|
internal static class Roff
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum input line length, in characters, before wrapping
|
||||||
|
/// </summary>
|
||||||
|
private const int LineWidth = 78;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Escape a value for safe inclusion in roff output
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Value to escape, if any</param>
|
||||||
|
/// <returns>The escaped value, or an empty string if null or empty</returns>
|
||||||
|
public static string Escape(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
// Backslashes are escaped first so that escapes introduced by
|
||||||
|
// later replacements are not themselves re-escaped
|
||||||
|
string escaped = value!.Replace("\\", "\\e");
|
||||||
|
|
||||||
|
// Render hyphens as literal ASCII minus signs instead of letting
|
||||||
|
// groff translate them to Unicode hyphens in UTF-8 output
|
||||||
|
escaped = escaped.Replace("-", "\\-");
|
||||||
|
|
||||||
|
return escaped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Escape a value for inclusion in a quoted <c>.TH</c> title field
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">Value to escape, if any</param>
|
||||||
|
/// <returns>The escaped value, or an empty string if null or empty</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Unlike <see cref="Escape"/>, hyphens are left intact so that the
|
||||||
|
/// date field remains parseable by mandoc and version strings render
|
||||||
|
/// verbatim. Embedded quotes are escaped so they cannot terminate the
|
||||||
|
/// surrounding field.
|
||||||
|
/// </remarks>
|
||||||
|
public static string EscapeField(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return value!.Replace("\\", "\\e")
|
||||||
|
.Replace("\"", "\\(dq");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Format a block of text into wrapped, escaped roff output lines
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">Text block to format, which may contain newlines</param>
|
||||||
|
/// <returns>Roff-safe output lines for the text block</returns>
|
||||||
|
public static List<string> FormatText(string? text)
|
||||||
|
{
|
||||||
|
List<string> output = [];
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
return output;
|
||||||
|
|
||||||
|
// Normalize line endings and split into paragraphs on blank lines
|
||||||
|
string normalized = text!.Replace("\r\n", "\n").Replace("\r", "\n");
|
||||||
|
string[] paragraphs = normalized.Split(["\n\n"], StringSplitOptions.None);
|
||||||
|
|
||||||
|
for (int i = 0; i < paragraphs.Length; i++)
|
||||||
|
{
|
||||||
|
// Collapse single newlines within a paragraph into spaces
|
||||||
|
string paragraph = paragraphs[i].Replace("\n", " ").Trim();
|
||||||
|
if (paragraph.Length == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Separate consecutive paragraphs with a blank line
|
||||||
|
if (output.Count > 0)
|
||||||
|
output.Add(".sp");
|
||||||
|
|
||||||
|
List<string> wrapped = WrapParagraph(paragraph);
|
||||||
|
output.AddRange(wrapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wrap a single paragraph into escaped roff output lines
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="paragraph">Single-line paragraph text to wrap</param>
|
||||||
|
/// <returns>Escaped output lines no longer than the configured width</returns>
|
||||||
|
private static List<string> WrapParagraph(string paragraph)
|
||||||
|
{
|
||||||
|
List<string> lines = [];
|
||||||
|
string[] words = paragraph.Split(' ');
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < words.Length; i++)
|
||||||
|
{
|
||||||
|
string word = Escape(words[i]);
|
||||||
|
if (word.Length == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (sb.Length == 0)
|
||||||
|
{
|
||||||
|
sb.Append(word);
|
||||||
|
}
|
||||||
|
else if (sb.Length + 1 + word.Length <= LineWidth)
|
||||||
|
{
|
||||||
|
sb.Append(' ');
|
||||||
|
sb.Append(word);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lines.Add(Protect(sb.ToString()));
|
||||||
|
sb = new StringBuilder();
|
||||||
|
sb.Append(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sb.Length > 0)
|
||||||
|
lines.Add(Protect(sb.ToString()));
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Protect a text line from being parsed as a roff control line
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="line">Line to protect</param>
|
||||||
|
/// <returns>The line, prefixed with a zero-width escape when needed</returns>
|
||||||
|
private static string Protect(string line)
|
||||||
|
{
|
||||||
|
// A line beginning with a control character would be treated as a
|
||||||
|
// request; a leading zero-width escape forces it to be text
|
||||||
|
if (line.Length > 0 && (line[0] == '.' || line[0] == '\''))
|
||||||
|
return $"\\&{line}";
|
||||||
|
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#! /bin/bash
|
#! /bin/bash
|
||||||
|
|
||||||
# This batch file assumes the following:
|
# This batch file assumes the following:
|
||||||
# - .NET 9.0 (or newer) SDK is installed and in PATH
|
# - .NET 11.0 (or newer) SDK is installed and in PATH
|
||||||
#
|
#
|
||||||
# If any of these are not satisfied, the operation may fail
|
# If any of these are not satisfied, the operation may fail
|
||||||
# in an unpredictable way and result in an incomplete output.
|
# in an unpredictable way and result in an incomplete output.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# This batch file assumes the following:
|
# This batch file assumes the following:
|
||||||
# - .NET 9.0 (or newer) SDK is installed and in PATH
|
# - .NET 11.0 (or newer) SDK is installed and in PATH
|
||||||
#
|
#
|
||||||
# If any of these are not satisfied, the operation may fail
|
# If any of these are not satisfied, the operation may fail
|
||||||
# in an unpredictable way and result in an incomplete output.
|
# in an unpredictable way and result in an incomplete output.
|
||||||
|
|||||||
Reference in New Issue
Block a user