feat: implement complete Modern API for Horde/Argv#8
Open
ralflang wants to merge 4 commits intoFRAMEWORK_6_0from
Open
feat: implement complete Modern API for Horde/Argv#8ralflang wants to merge 4 commits intoFRAMEWORK_6_0from
ralflang wants to merge 4 commits intoFRAMEWORK_6_0from
Conversation
Implement immutable help formatter for Modern API following Principle #5 and #7. Provides professional, terminal-width-aware help text formatting with full support for options, groups, and descriptions. Core Components: - HelpFormatter: Immutable help text generator - Integration with ImmutableParser via formatHelp() - Builder pattern for customization - Terminal width auto-detection Features: - Usage line formatting with %prog substitution - Description and epilog support - Option formatting with alignment and wrapping - Option groups with descriptions - Default value display (e.g., "(default: 8080)") - Choice list display (e.g., "(choices: 'json', 'xml', 'csv')") - Metavar support for argument placeholders - Short/long option ordering control - Text wrapping for terminal width - Configurable indentation Formatter Configuration: - width: Terminal width (0 = auto-detect, default: 80) - indent: Indentation increment (default: 2) - maxHelpPosition: Column before wrapping help text (default: 24) - shortFirst: Show short option first in listings (default: true) Formatting Rules: - Options aligned at configurable column position - Help text wraps to terminal width - Default values and choices automatically appended - Groups separated with headers - Consistent spacing and indentation Parser Integration: - formatHelp(): Generate help text for parser - Accepts optional custom formatter - Uses builder-provided formatter if available - Falls back to default formatter Example Usage: $formatter = HelpFormatter::create() ->withWidth(100) ->withIndent(4) ->build(); $parser = ParserBuilder::create() ->withUsage('%prog [options] <file>') ->withDescription('Process files') ->addOption($verboseOption) ->build(); echo $parser->formatHelp($formatter); Output Example: Usage: myapp [options] <file> Process files Options: -v, --verbose Increase verbosity -p, --port=PORT Server port (default: 8080) -f, --format=FORMAT Output format (choices: 'json', 'xml', 'csv') (default: json) Test Coverage: - 28 tests added (234 total Modern API tests) - 73 assertions added (403 total assertions) - All tests passing (100%) - Tests cover all formatting scenarios - Integration tests with ImmutableParser Formatter Characteristics: - Readonly class (Principle #7) - Immutable builder pattern (Principle #5) - No side effects, pure output generation - Thread-safe and cacheable - Fast formatting (no reflection) Text Wrapping Algorithm: - Word-based wrapping (no mid-word breaks) - Respects indentation for wrapped lines - Handles long option strings gracefully - Falls back to next-line help for long options Terminal Width Detection: - Auto-detects via tput cols if available - Falls back to 80 columns default - Can be explicitly set via withWidth() - Zero width disables wrapping Principles Demonstrated: - Principle #5: Modern API is Immutable - formatter fully immutable - Principle #7: Immutable Throughout - readonly class, builder for config - Principle #6: Explicit Access - clear method calls, no magic - Clean separation between formatting logic and parser logic Next Steps: Modern API is now feature-complete with parsing and help formatting
Implement core context infrastructure for subcommand-style parsing.
Contexts allow command-specific option sets that don't conflict.
New Components:
- ContextConfig: Immutable context configuration
- Name, aliases, description, usage, help
- Context-specific options and groups
- Argument count validation (min/max)
- Sub-context support for nested commands
- ContextBuilder: Immutable builder for contexts
- Fluent API: withDescription(), withUsage(), withAliases()
- Add options with addOption() and addOptions()
- Argument requirements: requiresArguments(), acceptsArguments()
- Convenience methods: requiresExactly(), requiresAtLeast(), requiresAtMost()
- Sub-context nesting with addContext()
- ParseResult: Extended for context support
- globalOptions: Options available across all contexts
- contextOptions: Options specific to activated context
- context: Name of activated context (null if no context)
- Backward compatible: existing code works unchanged
- Convenience methods: hasContext(), getContext()
- Unified access: getOption() checks context then global
- ParserBuilder: Context support
- addContext() method for adding contexts
- addContexts() for bulk addition
- Contexts passed to ImmutableParser constructor
- ImmutableParser: Context infrastructure
- Accept contexts array in constructor
- Build context map for O(1) lookup
- toBuilder() includes contexts
- Ready for Phase 2 parsing logic
Tests:
- ContextConfigTest: 16 tests, 51 assertions
- ContextBuilderTest: 18 tests, 40 assertions
- ParseResultContextTest: 8 tests, 39 assertions
- All 421 existing tests passing (100% backward compatible)
Total: 42 new tests, 130 new assertions
Example Usage:
```php
$deploy = ContextBuilder::create('deploy')
->withDescription('Deploy application')
->withAliases(['dep'])
->addOption($forceOption)
->requiresArguments(1, 'environment')
->build();
$parser = ParserBuilder::create()
->addOption($globalVerbose)
->addContext($deploy)
->build();
// Phase 2 will implement actual parsing logic
```
Design Decisions:
- Follows all 8 guiding principles from argv-modernization-design-decisions.md
- Immutable configs (readonly classes)
- Builders are NOT readonly (internal mutability for clone-on-modify)
- Explicit access methods (no ArrayAccess)
- Type-safe with PHP 8.2+ features
- Backward compatible: no breaking changes to existing Modern API
- Context is optional: parsers work with or without contexts
Phase Status:
✅ Phase 1: Core Context Infrastructure (Complete)
⏳ Phase 2: Context Parsing Logic (Next)
⏳ Phase 3: Help Generation
⏳ Phase 4: Advanced Features
⏳ Phase 5: Polish and Documentation
Implement full context-aware parsing with global and context-specific options.
Context Detection:
- First positional argument checked against registered contexts
- Context name or any alias triggers context activation
- Global options parsed before context detected
- Context-specific options parsed after context activated
Parsing Modes:
1. Legacy Mode (no contexts):
- Standard POSIX parsing like before
- All options in result->options
- 100% backward compatible
2. Context Mode (contexts registered, none active):
- Parses global options
- No context detected in arguments
- Options in result->globalOptions
- result->context is null
3. Context Mode (context active):
- Parses global options first
- Detects context from positional arg
- Parses context-specific options
- result->globalOptions + result->contextOptions
- result->context contains context name
Context Option Lookup:
- Context options checked first when in context
- Falls back to global options if not found in context
- Allows same option name in different contexts without conflict
Argument Validation:
- Validates argument count against context requirements
- Throws InvalidArgumentCountException if count invalid
- Provides clear error messages (exact, at least, at most)
New Exceptions:
- UnknownContextException: Unknown context name provided
- InvalidArgumentCountException: Wrong number of arguments for context
ImmutableParser Changes:
- parse() delegates to parseLegacy() or parseWithContexts()
- parseLegacy(): Original parsing logic (no contexts)
- parseWithContexts(): Two-phase context-aware parsing
- processContextOption(): Parse context-specific options
- findContextOption(): O(1) lookup with partial matching
ParseResult Enhancements:
- getOption() checks context then global (unified access)
- hasOption() checks both context and global
- hasContext(), getContext() for context info
- globalOptions, contextOptions properties
Tests:
- ContextParsingTest: 15 comprehensive tests
- Basic context detection
- Context with arguments (single and multiple)
- Global options parsing
- Context-specific options parsing
- Combined global and context options
- getOption() fallback behavior
- Context aliases
- Multiple contexts
- No context detected (fallback to global)
- Argument count validation (exact, range)
- Context option overrides global
- Backward compatibility (no contexts)
Total: 436 tests, 887 assertions (100% passing)
Example Usage:
```php
$globalVerbose = OptionBuilder::create()
->long('--verbose')
->flag()
->build();
$contextForce = OptionBuilder::create()
->long('--force')
->flag()
->build();
$deployContext = ContextBuilder::create('deploy')
->withDescription('Deploy application')
->withAliases(['dep'])
->addOption($contextForce)
->requiresArguments(1, 'environment')
->build();
$parser = ParserBuilder::create()
->addOption($globalVerbose)
->addContext($deployContext)
->build();
// Parse: app --verbose deploy --force production
$result = $parser->parse(['--verbose', 'deploy', '--force', 'production']);
// Access results:
$result->getContext(); // 'deploy'
$result->globalOptions->get('verbose'); // true
$result->contextOptions->get('force'); // true
$result->arguments[0]; // 'production'
$result->getOption('force'); // true (checks context)
$result->getOption('verbose'); // true (fallback to global)
```
Phase Status:
✅ Phase 1: Core Context Infrastructure (Complete)
✅ Phase 2: Context Parsing Logic (Complete)
⏳ Phase 3: Help Generation (Next)
⏳ Phase 4: Advanced Features
⏳ Phase 5: Polish and Documentation
Add comprehensive help generation for contexts with proper separation
between data structure (Argv) and formatting (HelpFormatter).
HelpFormatter Enhancements:
- formatContexts(): Lists all available contexts with descriptions
- formatContext(): Formats single context name and description
- formatContextHelp(): Complete help for specific context
- formatArgumentRequirements(): Shows argument count requirements
- Handle null prog parameter gracefully (defaults to 'program')
Format Structure:
1. Main Help (parser->formatHelp()):
- Usage line
- Description
- Commands: (contexts list)
- Global Options:
- Epilog
2. Context Help (parser->formatContextHelp('context')):
- Usage: program context [options]
- Context description
- Detailed help text
- Context Options: (context-specific)
- Global Options: (available in context)
- Arguments: (requirements)
ImmutableParser API:
- formatHelp(): Generate main help with contexts
- formatContextHelp(name): Generate help for specific context
- getContexts(): Get all registered contexts
- getContext(name): Get specific context by name or alias
- hasContexts(): Check if contexts are registered
Help Content:
- Context names with aliases shown as: deploy (dep, depl)
- Context descriptions from ContextConfig
- Global vs context-specific options clearly separated
- Argument requirements formatted: "Requires exactly 2 arguments: source destination"
- Terminal width-aware text wrapping
Design Philosophy:
- Argv provides data structure and help data
- HelpFormatter handles text formatting
- horde/cli can add colors, interactive features later
- Clean separation of concerns maintained
Tests:
- ContextHelpTest: 11 comprehensive tests
- Format help with contexts
- Context aliases in help
- Context-specific help
- Invalid context exception
- Get contexts/context methods
- hasContexts() method
- Global and context options separation
- Argument requirements formatting (exact, range, at least)
- Backward compatibility (no contexts)
- Custom formatter
Total: 447 tests, 932 assertions (100% passing)
Example Output:
```
Usage: myapp [options] <command>
Application deployment tool
Commands:
deploy (dep) Deploy application to environment
rollback (roll) Rollback to previous version
Options:
-v, --verbose Verbose output
# Context-specific help:
$ myapp help deploy
Usage: myapp deploy [options] <environment>
Deploy application to environment
Deploys the application to the specified environment with health
checks and rollback capabilities.
Context Options:
--force Force deployment
--timeout INT Deployment timeout in seconds (default: 60)
Global Options:
-v, --verbose Verbose output
Arguments:
Requires exactly 1 argument: environment name
```
Phase Status:
✅ Phase 1: Core Context Infrastructure (Complete)
✅ Phase 2: Context Parsing Logic (Complete)
✅ Phase 3: Help Generation (Complete)
⏳ Phase 4: Advanced Features (Optional)
⏳ Phase 5: Polish and Documentation (Optional)
Core feature complete and production-ready!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Complete implementation of Modern API for Horde/Argv following 8 guiding principles established through design analysis. This provides a fully immutable, type-safe, and explicit API for command-line argument parsing in PHP 8.2+.
What's Included
Week 1: Enums and Config Objects
OptionAction,OptionType,ConflictHandlerParserConfig,OptionConfig,OptionGroupConfigInvalidOptionException,ValueValidationExceptionWeek 2: Result Objects, Exceptions, and Validators
ParseResult,OptionValuesAmbiguousOptionException,MissingValueException,ConflictingOptionExceptionValidatorsutility with 12 composable validatorsWeek 3: Immutable Builders
OptionBuilder: 14 setters, 3 convenience methods (flag, repeatable, counter)GroupBuilder: Group management with title and descriptionParserBuilder: Complete parser configurationWeek 4: ImmutableParser (Core Engine)
-v,-abc(bundling),-nVALUE(attached)--verbose,--name VALUE,--name=VALUEtoBuilder()Week 5: Help Formatting System
HelpFormatter: Immutable help text generatorTest Coverage
Test Breakdown
Design Principles
All 8 guiding principles fully implemented and verified:
Example Usage
Help Output
Features
Parsing Capabilities
-abc=-a -b -c)-nVALUEor--name=VALUE)--verbmatches--verbose)--) option terminatorType System
Actions
Store- Store single valueStoreTrue/StoreFalse- Boolean flagsAppend- Collect multiple valuesCount- Increment counter (e.g.,-vvv= 3)StoreConst/AppendConst- Store predefined constantsCallback- Custom processing functionValidation & Transformation
all(),any()Help Generation
Error Handling
InvalidOptionException- Unknown/invalid optionsAmbiguousOptionException- Partial match ambiguityMissingValueException- Required value not providedConflictingOptionException- Duplicate optionsValueValidationException- Type conversion/validation failuresArchitecture
Immutability
Performance
Design Patterns
toBuilder())Breaking Changes
None - Modern API is in separate namespace:
Horde\Argv\Modern\Horde_Argv_*(unchanged)Both APIs can coexist. Legacy API remains stable and supported.
Migration Path
Users can adopt Modern API gradually:
Documentation
Commits
chore: add test autoload configuration and upgrade guidedocs: mark under-development stub classes as internalfeat(modern): implement Week 1 - Enums and Config objectsfeat(modern): implement Week 2 - Result objects, exceptions, and validatorstest(modern): add comprehensive unit tests for Weeks 1-2feat(Modern): add immutable builders (Week 3)feat(Modern): implement ImmutableParser with full parsing engine (Week 4)feat(Modern): implement help formatting system (Week 5)Checklist
Future Enhancements
Possible future additions (not in this PR):
Status
Modern API is production-ready and feature-complete for command-line argument parsing in PHP 8.2+ applications!