Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c18de99
squash-commit
mwsis Aug 28, 2026
b08f3e5
chore(doc): update documentation metadata for 0.39.11
mwsis Aug 28, 2026
431b69c
chore(doc): establish component documentation structure
mwsis Aug 28, 2026
7815cae
docs(components): document container selection and behaviour
mwsis Aug 28, 2026
de4d4cd
docs(components): document scalar conversion policies
mwsis Aug 28, 2026
7008f9f
docs(components): document string transformation and matching semantics
mwsis Aug 28, 2026
553ee44
docs(components): document diagnostic context and exception chaining
mwsis Aug 28, 2026
a02fedb
docs(components): document Ruby extension loading and semantics
mwsis Aug 28, 2026
cc0a451
docs(components): document hash transformation and matching semantics
mwsis Aug 28, 2026
387b927
docs(components): document parameter and option validation
mwsis Aug 28, 2026
12dd71b
docs(components): document human-readable array formatting
mwsis Aug 28, 2026
61e7c87
docs(components): document command-line option mapping
mwsis Aug 28, 2026
b4c4468
docs(components): document structured IO writing
mwsis Aug 28, 2026
ba01eae
docs(guides): add the xqsr3 getting-started workflow
mwsis Aug 28, 2026
a356bab
docs(guides): add component selection guidance
mwsis Aug 28, 2026
f280a9d
docs(guides): document external input processing
mwsis Aug 28, 2026
cddffd7
docs(reference): establish the generated API reference
mwsis Aug 28, 2026
77bf014
docs(reference): complete generated API documentation coverage
mwsis Aug 28, 2026
9411181
ci(docs): verify generated API reference coverage
mwsis Aug 28, 2026
4c5ce26
docs(reference): exclude the private BoolParser helper
mwsis Aug 28, 2026
233a2f2
docs(xqsr3): complete task-oriented workflow documentation
mwsis Aug 28, 2026
3ded5e1
Merge branch 'dev' into doc.2
mwsis Aug 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
## Documentation

* [ ] Curated component catalogue under **docs/components/**;
* [ ] Task-oriented user guide under **docs/guides/**;
* [x] ~~~task-oriented user guide under **docs/guides/**~~~;
* [ ] Expanded generated API reference;
* [ ] Executable cookbook and recipe documentation;
* [ ] Static documentation website with search and versioned references;
Expand Down
14 changes: 10 additions & 4 deletions docs/guides/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
# xqsr3 User Guides <!-- omit in toc -->

This directory is reserved for task-oriented guides to **xqsr3**. Guides will
explain how to combine components to solve common Ruby programming tasks.
This directory contains task-oriented guides to **xqsr3**. The guides explain
how to combine components to solve common Ruby programming tasks.

* [Getting Started](./getting-started.md) — install the gem and compose
components in a small application workflow;
* [Choosing a Component](./choosing-a-component.md) — select components and
loading scopes according to the problem being solved;
* [Formatting and Writing Output](./formatting-and-writing-output.md) —
format human-readable values and write them to paths or streams;
* [Getting Started](./getting-started.md) — install the gem and compose
components in a small application workflow;
* [Handling Failures](./handling-failures.md) — validate boundaries, preserve
causes, and produce safe diagnostics;
* [Parsing and Validating External Input](./parsing-and-validating-input.md)
— separate normalization, conversion, and validation at boundaries;
* [Processing Collections](./processing-collections.md) — count, group,
transform, and search collection data;

The component catalogue is available in
[`docs/components/`](../components/README.md).
Expand Down
182 changes: 182 additions & 0 deletions docs/guides/formatting-and-writing-output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# xqsr3 Formatting and Writing Output <!-- omit in toc -->

This guide shows how to turn application values into readable text and write
that text to a file or stream. It separates presentation choices from output
ownership so the same data can be displayed, tested, or persisted.


## Table of Contents <!-- omit in toc -->

- [Format alternatives](#format-alternatives)
- [Prepare strings](#prepare-strings)
- [Limit display width](#limit-display-width)
- [Write sequences](#write-sequences)
- [Write key-value data](#write-key-value-data)
- [Choose a stream or path](#choose-a-stream-or-path)
- [Compose a report](#compose-a-report)


## Format alternatives

Use `join_with_or` when text describes permitted alternatives:

```Ruby
require 'xqsr3/array_utilities/join_with_or'

formats = ['JSON', 'YAML', 'TOML']
message = Xqsr3::ArrayUtilities::JoinWithOr.join_with_or(formats)
# => 'JSON, YAML, or TOML'
```

Configure the conjunction, separator, Oxford comma, or quote character when
the surrounding presentation requires a different style:

```Ruby
Xqsr3::ArrayUtilities::JoinWithOr.join_with_or(
formats,
or: 'OR',
quote_char: '"',
oxford_comma: false,
)
# => '"JSON", "YAML" OR "TOML"'
```

This formatter is for human-readable messages, not machine-readable
serialization.


## Prepare strings

Use `quote_if` when values should be quoted only if they contain whitespace:

```Ruby
require 'xqsr3/string_utilities/quote_if'

formatter = Xqsr3::StringUtilities::QuoteIf
formatter.quote_if('simple') # => 'simple'
formatter.quote_if('two words') # => '"two words"'
```

The `quotables` option can replace whitespace with another trigger. Use
`quotes` when the opening and closing delimiters differ.

Use `nil_if_whitespace` to identify absent human-entered values, and
`nil_if_empty` when whitespace should remain meaningful. Neither method strips
a non-blank value; call `strip` explicitly when that is wanted.


## Limit display width

Use `truncate` for labels, logs, and fixed-width displays:

```Ruby
require 'xqsr3/string_utilities/truncate'

truncator = Xqsr3::StringUtilities::Truncate
truncator.string_truncate('a very long filename.txt', 16)
# => 'a very long f...'
```

The omission string is included within the requested width:

```Ruby
truncator.string_truncate(
'a very long filename.txt',
16,
omission: ' [...]',
)
# => 'a very lon [...]'
```

Choose an omission marker whose meaning is clear in the target interface.
Truncation is based on the string width used by the implementation and does
not provide escaping or format-specific encoding.


## Write sequences

Use `IO.writelines` for an array whose members should become separate output
lines:

```Ruby
require 'xqsr3/io/writelines'
require 'stringio'

output = StringIO.new
Xqsr3::IO.writelines(output, ['first', 'second'])
output.string # => "first\nsecond\n"
```

The method converts array elements with `to_s`, appends a line separator, and
returns the number of entries written. Pass `line_separator: ''` when the
records already contain all required separators.


## Write key-value data

Pass a hash when each output line consists of a key and value:

```Ruby
output = StringIO.new
Xqsr3::IO.writelines(
output,
{ 'host' => 'localhost', 'port' => 8080 },
column_separator: '=',
line_separator: "\n",
no_last_eol: true,
)
output.string # => "host=localhost\nport=8080"
```

Hash keys and values are converted with `to_s`. Use a serializer instead when
values require escaping, nesting, or a machine-readable format.


## Choose a stream or path

Use a path when xqsr3 should open and replace the file:

```Ruby
Xqsr3::IO.writelines('report.txt', ['first', 'second'])
```

Use a writable stream when the caller owns the destination:

```Ruby
File.open('report.txt', 'w') do |file|
Xqsr3::IO.writelines(file, ['first', 'second'])
end
```

The path form opens in write mode. The stream form leaves the stream open.
`StringIO` is useful for unit tests because it makes output directly
assertable without filesystem access.


## Compose a report

Keep formatting and writing separate when output may have several consumers:

```Ruby
require 'xqsr3/array_utilities/join_with_or'
require 'xqsr3/io/writelines'

supported = ['JSON', 'YAML', 'TOML']
summary = Xqsr3::ArrayUtilities::JoinWithOr.join_with_or(supported)

Xqsr3::IO.writelines(
'supported-formats.txt',
{ 'formats' => summary },
column_separator: ': ',
)
```

This keeps the choice of human-readable wording independent from the choice
of destination. For parsing and validation before formatting, see
[Parsing and Validating External Input](./parsing-and-validating-input.md).
For component-level details, see [Array Utilities](../components/array-utilities.md),
[IO](../components/io.md), and
[String Utilities](../components/string-utilities.md).


<!-- ########################### end of file ########################### -->
157 changes: 157 additions & 0 deletions docs/guides/handling-failures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# xqsr3 Handling Failures <!-- omit in toc -->

This guide shows how to make failures informative without coupling every
caller to the same recovery policy. It combines validation, option-aware
exceptions, inspection, and cause chaining.


## Table of Contents <!-- omit in toc -->

- [Reject invalid input early](#reject-invalid-input-early)
- [Attach structured context](#attach-structured-context)
- [Preserve the original cause](#preserve-the-original-cause)
- [Produce safe diagnostics](#produce-safe-diagnostics)
- [Choose a recovery boundary](#choose-a-recovery-boundary)


## Reject invalid input early

Validate values at the public boundary where their names and intended domain
are known:

```Ruby
require 'xqsr3/quality/parameter_checking'

def connect(port)
port = Xqsr3::Quality::ParameterChecking.check_parameter(
port,
'port',
type: Integer,
values: [1..65_535],
)

# connect using the validated port
end
```

This produces a failure close to the invalid input instead of allowing an
unrelated lower-level operation to report a less useful error. Use
`nothrow: true` only when the caller genuinely needs a validation probe.


## Attach structured context

When an exception class accepts keyword options, use
`ExceptionUtilities.raise_with_options` to retain machine-readable context:

```Ruby
require 'xqsr3/diagnostics/exception_utilities'

class ConfigurationError < ArgumentError
attr_reader :options

def initialize(message = nil, **options)
super(message)
@options = options
end
end

Xqsr3::Diagnostics::ExceptionUtilities.raise_with_options(
ConfigurationError,
'invalid port',
option: :port,
value: 'abc',
)
```

Keep the human-readable message concise and put structured values in options.
Do not place credentials, tokens, or other secrets into either messages or
options if the exception may be logged.


## Preserve the original cause

Use `WithCause` when a higher-level operation needs to add context while
retaining the lower-level exception:

```Ruby
require 'xqsr3/diagnostics/exceptions/with_cause'

class ImportError < StandardError
include Xqsr3::Diagnostics::Exceptions::WithCause
end

begin
Integer('not-a-number')
rescue ArgumentError => cause
raise ImportError.new('could not import configuration', cause: cause)
end
```

At the recovery boundary, the caller can inspect both levels:

```Ruby
rescue ImportError => error
warn error.chained_message
warn error.cause.class
end
```

`chained_message` combines messages from outer to inner exception. Pass
`separator` when another display format is required. `exceptions` returns the
whole chain; `chainees` returns every cause except the receiver.

Use the explicit `cause:` keyword when constructing a chain. This is clearer
than relying on positional exception inference and documents which exception
is intentionally being retained.


## Produce safe diagnostics

Use `InspectBuilder` when an object needs a stable diagnostic representation:

```Ruby
require 'xqsr3/diagnostics/inspect_builder'

class Request
include Xqsr3::Diagnostics::InspectBuilder

INSPECT_HIDDEN_FIELDS = ['token']

def initialize(path, token)
@path = path
@token = token
end
end

request = Request.new('/health', 'secret')
request.make_inspect(show_fields: true, no_object_id: true)
# => "#<Request: @path(String)='/health'>"
```

Use `shown_fields` for a positive allow-list when the object contains many
fields. A diagnostic inspection string is for human troubleshooting; it is
not a wire format or a persistence representation.


## Choose a recovery boundary

Apply recovery at the layer that has enough context to make a decision:

* A parser can return a default or `nil` for expected malformed input;
* A validator can raise a precise parameter or range failure;
* A component can wrap a lower-level error with `WithCause`;
* An application boundary can log, retry, or present the failure;
* A library should generally preserve the failure rather than silently log or
discard it.

Avoid catching `Exception` broadly. Catch the specific failures the operation
can reasonably recover from, and preserve the original exception when adding
context.

For component-level details, see [Quality](../components/quality.md),
[Diagnostics](../components/diagnostics.md), and
[Conversion](../components/conversion.md).


<!-- ########################### end of file ########################### -->
Loading