| Age | Commit message (Collapse) | Author |
|
Options and arguments were stored and passed as PhpMixed even though
Symfony only ever puts a string, a bool, a list of strings or null in
one. get_option already narrowed to InputOptionValue at the boundary;
this widens that enum into InputValue and pushes it through
InputInterface, InputOption/InputArgument defaults, the Input storage,
ArgvInput/ArrayInput/StringInput/CompletionInput, Command::add_option
and add_argument, and the Composer-side wrappers.
Two neighbouring string|int unions get types of their own:
InputDefinition::{get_argument,has_argument} take an ArgumentName, and
ArrayInput keys its parameters by ParameterName. has_parameter_option
and get_parameter_option take the values they look for as &[&str],
which is what PHP's `(array) $values` cast produced anyway.
Two behaviours change along the way. Input::set_option on a negated
option now negates with PHP's loose bool cast rather than treating a
non-bool as false, matching `!$value`. ArrayInput::parse now resolves
an integer key to an argument position instead of looking up an
argument literally named "0".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
`InputInterface::get_option` returned `PhpMixed`, so the negation branch
of `Input::get_option` reproduced PHP's `return !$value;` as
`!value.as_bool().unwrap_or(false)`, which inverts the result for a
string value instead of leaving it `false`. It now returns
`InputOptionValue`, whose `to_bool` is PHP's truthiness cast.
The narrowing also removes the `Vec<PhpMixed>` element handling the
commands carried for array options: `as_array` hands back `&[String]`,
so the `filter_map(|v| v.as_string())` chains at eight call sites
collapse. Its other accessors keep `PhpMixed`'s names and meanings
(`is_null`, `as_bool`, `as_string`, `to_bool`), and
`From<InputOptionValue> for PhpMixed` covers the callers that feed the
value back into an `IndexMap<String, PhpMixed>` or a `PhpMixed`
parameter.
`Input` keeps its parsed options and `InputOption` its defaults as
`PhpMixed`, so `Input::get_option` is where the narrowing happens and
where a value outside the domain panics.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
function_exists() returns false in PHP when a function is blocked by
disable_functions, when its extension is not compiled in, or when the
PHP version predates it. None of those apply to a native binary.
|
|
PHP's PREG_PATTERN_ORDER is column-oriented, but 7 of the 10 call sites
read it row-wise, rebuilding each occurrence by indexing every column at
the same offset. Return an iterator of PregMatches instead, which is also
what the set-order and offset-capture variants were carrying, so the
three functions collapse into one and PregMatchesAll,
PregMatchesAllWithOffsets, CaptureKey and preg_match_map! all go away.
The offset-capture call sites are served by the new PregMatches
get_offset/name_offset accessors.
The search stays eager: regex::Captures borrows only the subject, so the
matches outlive the pattern resolved for the call, and PHP's
preg_match_all is eager too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The capture groups were discarded at 162 of the preg_match call sites,
which only tested the Option. They now call preg_is_match, which lets the
regex engine skip capture tracking.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Every call site but one passed offset 0. The remaining one, the UTF-8
chunking loop in Application, slices the subject instead: its pattern has
no anchor or lookaround, so matching a suffix is equivalent to starting
the search at that offset.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The shim carried the PCRE A (anchored) modifier alongside every compiled
pattern so preg_match2 could honour it by searching the sub-slice at the
offset. Only two call sites ever passed such a pattern, and each can cut
that slice itself, so the flag is gone from the cache, ResolvedPattern,
PregPattern and the php_regex! macro, and a pattern still carrying A is
now rejected rather than silently searched unanchored.
StringInput::tokenize and PhpFileCleaner::match search from their cursor
with a `^`-prefixed pattern instead. The rule is written down in
docs/dev/regex-porting.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The two preg_match_all variants took the same arguments and differed
only in what they returned: a Vec of columns, or the named-and-numbered
PregMatchesAll. The latter is the one all but two call sites already
used, so preg_match_all2 takes over the plain PHP name and the Vec
variant goes away.
Its remaining readers only ever wanted group 0's column, which they now
take through CaptureKey::ByIndex(0); in the formatter this replaces the
array_shift that popped that column off the PREG_PATTERN_ORDER array.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
preg_match copied every group into a Vec<Option<String>> while
preg_match2 handed back the borrowed captures. They now differ only in
the offset argument, so preg_match delegates with offset 0 and its
callers read groups through PregMatches::get.
Going through preg_match2 also makes preg_match honour the PCRE A
modifier, which it used to ignore; no caller passes such a pattern.
VersionParser::manipulate_version_string takes an index accessor instead
of a slice, and VersionParser::normalize matches against a copy of the
subject because the captures outlive the assignments to $version.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Preg had shed everything it owned: after the last few rounds its methods
were one-line forwards to the shim's preg_*(), differing only in a
default argument or a wrapper the caller unwrapped anyway. The 460 call
sites now name the shim function, and shirabe-pcre is gone from the
workspace along with its LICENSE entry.
The forwards expand as they read: isMatch becomes
preg_match2(.., 0).is_some() (is_none() where PHP negates it), isMatch3
and match3 drop the .is_some(), matchAll counts through
preg_match_all2(..).occurrence_count(), and replace4/replace5 spell out
the limit and count arguments preg_replace2 takes. Callbacks are the one
place the shapes differ: preg_replace_callback carries an error out of
the callback, so the fourteen infallible closures wrap their result in
Ok() and expect() it back.
Config::process() is the fifteenth, and it drops the `error` cell it
captured to smuggle a failure past a closure that could only return a
String. The `?` in the closure now carries it, which is what the PHP
does -- a throw from the callback leaves preg_replace_callback at the
failing match rather than running the remaining replacements and
reporting the last error.
The module doc that explained why composer/pcre's exceptions and
*StrictGroups() variants have no counterpart moves to the shim's preg
module, where the functions it describes live.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
PregMatches keyed both forms of a capture group through CaptureKey, so
every read built one: a usize wrapped in an enum, or worse, a String
allocated to name a group that regex::Captures can look up from a &str.
It now mirrors regex::Captures instead -- get() takes the group number,
name() the group name -- and the enum drops out of the type entirely.
That is 285 call sites across 59 files, and the named ones carry most of
the win: `matches.get(&CaptureKey::ByName("host".to_string()))` reads as
`matches.name("host")`. ProcessExecutor loses a `user_key` binding that
existed only to build the key once.
CaptureKey stays as the key type of PregMatchesAll and
PregMatchesAllWithOffsets, where numbered and named entries share one
IndexMap and a key type is the point. Five files still name it.
Also retargets the two preg_match_all comments that described the
occurrence count through `matches[&CaptureKey::ByIndex(0)].len()`, an
Index impl these types no longer carry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
PregMatches was an IndexMap of owned Strings copied out of the match, so
every preg_match2/preg_replace_callback call allocated a String per
capture group (twice over for a named group) whether or not the caller
read it. It now wraps the regex::Captures itself, held alongside the
pattern it came from so groups stay reachable by both their named and
their numbered form, and hands out &str borrowed from the subject. The
subject's lifetime becomes a parameter of the type.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
PHP fills `$matches` through a by-ref parameter, which the port mirrored
with a `&mut` out-param plus a bool or count return. Every caller then
had to declare an empty binding one line ahead of the call, and nothing
in the type said the binding is only meaningful when the call succeeded.
Return the matches instead: preg_match() and preg_match2() hand back an
Option, and the three preg_match_all* functions hand back the collection
they used to fill.
The occurrence count the two map-shaped preg_match_all* functions used
to return is the length of any one of the map's columns, so it is not
lost -- Preg::match_all() and friends derive it via occurrence_count().
preg_replace2() keeps its `count: Option<&mut usize>`: that one is not
derivable from the replaced string, and callers that do not want it pay
nothing for passing None.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
preg_match_all() and preg_match_all_set_order() were the last preg_*
functions handing back a bare Vec<String>, where a group that did not
participate is indistinguishable from one that captured "". Hand back
Option<String> as the map-shaped functions already do; php_match_row(),
the last of the truncate-then-pad helpers, goes with them.
preg_split_delim_capture() keeps its Vec<String>. preg_split() accepts
no PREG_UNMATCHED_AS_NULL, so there is no null form to move it to, and
its result interleaves split segments -- which can never be absent --
with the captured delimiters.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The possessive quantifiers in Symfony's tag-matching patterns only
suppress backtracking: the open-tag class excludes backslash, so giving
a character back can never let the \\. alternative or the closing >
match, and the same holds for the close-tag class and >. Removing them
is the documented port for performance-only possessive quantifiers, not
an approximation waiting on a PCRE engine.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The five IndexMap shapes that the preg_* functions and Preg fill in are
now distinct types generated by preg_match_map!, so a matches map no
longer interchanges with any other map of the same key and value type.
Index<usize> is kept alongside Index<&Q> because call sites such as
config_command and event_dispatcher reach for a group by its position in
the map rather than by its capture key.
|
|
preg_match2() and preg_match_all_offset_capture() each become a pair over
a shared private impl, and their flags arguments are gone: no caller passed
anything but 0 or PREG_UNMATCHED_AS_NULL. Preg::match5()/is_match5() lose
their own flags argument for the same reason -- both call sites passed 0,
and the value had nowhere left to go -- so they are renumbered to
match4()/is_match4(). PREG_UNMATCHED_AS_NULL itself is now unreferenced.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The two functions ran the same search and differed only in how they
reported a non-participating group: one as ("", 0) in a bespoke struct,
the other as (null, -1) in a capture-key map. Neither shape covered both
callers, because PHP reaches preg_match_all() with different flags from
each: Preg::matchAllWithOffsets() always ORs in PREG_UNMATCHED_AS_NULL,
while OutputFormatter::formatAndWrap() passes PREG_OFFSET_CAPTURE alone.
Keep the capture-key map, which also exposes named groups, and take the
flags that decide between null and "" for an unmatched group. The offset
is -1 either way, so the ("", 0) approximation is gone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
preg_replace_callback2 had one caller, Preg::replace_callback6, which
was itself reached only from Preg::replace_callback with the default
limit, count and flags. Fold the two shim functions into one and drop
those parameters along with replace_callback6.
The surviving callback takes callback2's IndexMap of matches: it can be
keyed by group name, and it omits trailing non-participating groups the
way PHP does, which is what the callbacks in Process and ProgressBar
test for.
|
|
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
SymfonyStyle modelled PHP's string|array message parameters, its array of
listing elements and its table headers/rows as PhpMixed, then normalised
and stringified them at every entry point. Take &[String] for the message
and listing parameters, Vec<Cell>/Vec<Row> for table (matching the
horizontal_table signature) and Vec<String> for the choice options, so the
is_array/is_iterable normalisation and the php_string helper both go away.
PhpMixed stays where PHP is genuinely mixed: the question answers returned
by ask/ask_hidden/choice, their validators, choice's string|int|null
default, and the progress_iterate elements.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Porting mapped every PHP `protected` member onto `pub(crate)`, which is
wider than nearly all of them need. Each item demoted here is reached
only from the module that defines it, so the crate-wide visibility
conveyed nothing.
Every `pub(crate)` that survives has at least one reader in another
module of the same crate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Split date() into date_utc() and date_local(), the latter resolving the
system's local timezone through the tzfile crate ($TZ, then
/etc/localtime, falling back to UTC when neither is readable). The
timestamps Composer renders for humans -- the GitHub OAuth token note,
the GitHub API rate limit reset time, the Perforce client spec fields and
the "today" check of the show command -- now go through date_local().
PHP resolves its default timezone from the date.timezone ini setting,
which Shirabe does not read, so date_default_timezone_get/set have no
input left to model and are dropped from the shim and its callers. The
resulting difference is recorded in docs/known-incompatibilities.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
str_contains(), str_starts_with() and str_ends_with() were thin wrappers
over the str methods of the same semantics. Call sites now use
contains()/starts_with()/ends_with() directly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
The SignalHandler port was a no-op stub, so all four of Composer's abort
paths were dead code: nothing removed a half-created project, reverted
composer.json, or cleaned up half-installed packages.
Composer runs those handlers from pcntl callbacks, which a Rust signal
handler cannot do -- it may touch nothing beyond atomics. SignalSubscription
records the signal instead, and the abort runs from checkpoints on the normal
call stack, where the clean-up can borrow the state it needs. That also
resolves the closure-capture TODO(phase-c)s in RequireCommand and
InstallationManager, and replaces exit_with_last_signal's exit(0) with the
restore-and-re-raise Seld\Signal does.
A subscription is live only inside the four abort regions, so elsewhere the
signals keep their default disposition and kill the process at once. It is
installed without SA_RESTART so a signal interrupts an interactive prompt
rather than resuming the read. A signal reaches only the innermost
subscription, reproducing SignalHandler's single-stack dispatch.
Drop SignalRegistry, SignalableCommandInterface and the Application wiring
for them: nothing in Composer reaches that path, and SignalHandler discards
whatever they register. Signal handling from plugins and scripts is
undefined behavior; see docs/dev/signals.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Command::setProcessTitle has no caller: neither Composer nor the ported
Symfony Console code sets a process title, so run()'s title branch could
never fire. The shim it called into was unimplementable anyway — PHP
rewrites its own argv block, which Rust hands out only as owned copies.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Every parent module in the symfony-* crates already re-exported its leaf
modules with `pub use`, so each item was reachable by two paths. Make the
leaf modules private and route all callers through the single re-exported
path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
|
Table titles, per-column styles and appendRow have no callers: a
plugin-provided command runs inside the worker-side Symfony console
application, so its table rendering resolves against the real PHP
Table rather than this port. Remove those methods together with the
fields they were the only writers of, and the render branches those
fields made unreachable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
shirabe-symfony-console crate
Move `Symfony\Component\Console` out of shirabe-external-packages and
into its own crate, so the path is
`shirabe_symfony_console::application::Application` instead of
`shirabe_external_packages::symfony::console::application::Application`.
The `delegate_to_inner!` and `delegate_command_trait_impls_to_inner!`
macros move with it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|