| phpstan-rules | by dg | nette/database row type narrowing (table/related/ref/insert) Narrows return types based on a configurable table-to-entity-class mapping (nette.database.mapping.tables), which is not yet part of the nette/database distribution. Kept as the topmost commit so the whole feature can be excluded or rebased independently. Includes: - TableRowTypeResolver shared resolver - Explorer::table() -> Selection<EntityRow> - ActiveRow::related() -> GroupedSelection<EntityRow> - ActiveRow::ref() -> ?EntityRow - Selection::insert() -> mapped EntityRow | | | |
| phpstan-rules | by dg | added ExpectTypeReturnTypeExtension for Expect::type() Expect::type() declares Type (Type|AnyOf since schema 2.0), while the element it returns depends on the expression: 'int' is a NumberType with min() and max(), 'int|string' an AnyOf. The extension resolves a constant expression by asking Expect::type() itself, so it needs no knowledge of the expression language and its answer follows whatever the installed nette/schema builds; a non-constant expression, or one the installed version refuses, keeps the declared type. | | | |
| phpstan-rules | by dg | ExpectArrayReturnTypeExtension answers the plain array element instead of Type Since nette/schema 1.4 an Expect::array() without a shape is an ArrayType, not a plain Type, so the extension asks Expect::array() for the class the same way the Expect::type() one does; up to 1.3 the answer stays Type. | | | |
| phpstan-rules | by dg | fixed static analysis with PHPStan 2.3 | | | |
| phpstan-rules | by dg | uses dresscode | | | |
| phpstan-rules | by dg | cs | | | |
| phpstan-rules | by dg | cs: grouped use statements | | | |
| phpstan-rules | by dg | cs: only compiler-optimized functions in use-function imports | | | |
| code-checker | by dg | added AGENTS.md | | | |
| code-checker | by dg | uses nette/command-line 2.0 The command line is defined by Command and read by Parser, the help is drawn by HelpRenderer. The two ignored switches are defined as hidden switches. The requirement reads dev-master until 2.0 is tagged. Everything a person reads goes through one Console built in the bootstrap and handed to Checker and to HelpRenderer, so the colors, the width and the answer whether this is a terminal are decided once, by the stream itself; the static Console::detectTerminal() the progress used to be gated on is gone. A fatal error goes through it too, which is how it erases whatever was drawn in place. The progress dots are a status of that console: it cuts them to the width of the terminal, draws nothing when the output is redirected, gives the cursor back when the process dies, and the next line written erases them, so neither the padding to a fixed forty columns nor the carriage return is needed any more. A message of a task is stripped of escape sequences before it is colored, since it may quote the file being checked. | | | |
| code-checker | by dg | uses dresscode | | | |
| code-checker | by dg | cs: grouped use statements | | | |
| code-checker | by dg | cs: suppression comments | | | |
| code-checker | by dg | cs | | | |
| code-checker | by dg | removed strictTypesDeclarationChecker | | | |
| code-checker | by dg | tabIndentationChecker replaced by tabIndentationFixer Indentation with spaces is converted to tabs and a tabulator inside a line to a space. The indentation width is detected from the file, rare depths are ignored so that a mistyped line does not shrink it. A file whose indentation does not fit the detected width is reported as an error and left untouched. | | | |
| code-checker | by dg | CI: switched Coveralls upload to the official action The php-coveralls phar is replaced by coverallsapp/github-action, which resolves the token and the absolute paths in the Clover report on its own, so tests/.coveralls.yml is no longer needed. Coverage is now uploaded even when tests crash under phpdbg. Being phpdbg-compatible is not a goal, but until now the failing step aborted the job before the upload ever ran. The job-level continue-on-error is gone, so genuine failures, including a rejected upload, are visible. | | | |
| code-checker | by dg | requires PHP 8.2 | | | |
| command-line | by dg | Prompt asks the user at a terminal: choose() reads one of the choices A command line that asks has to know whether anybody is there to answer, so the prompt holds the console it writes the question to and the stream the answer comes from, and asks only when both are a terminal: an answer must never come from a pipe nobody watches, and isInteractive() tells the application beforehand. choose() asks until the answer is one of the choices, in any case, and gives it back as it was declared. An empty answer is the default, and without one it asks again; the end of the input is null, so a script reading a closed stream is not left in a loop. Only the default is shown in capitals, so that no other choice looks like it, and a map of a choice to its description is drawn as a menu above the question, which is not repeated. The console marks the line the terminal moved the cursor to by echoing the answer, so a status drawn afterwards does not erase a half-written line. | | | |
| command-line | by dg | added objects of options: a parameter typed by a class whose constructor declares inputs is built from them A handler with a dozen inputs has a dozen parameters, and the ones that belong together travel together through every layer below it. A parameter typed by a class whose constructor carries #[Flag], #[Option] or #[Argument] is an object of options instead: its inputs are defined and bound exactly as the parameters of the handler are, and the object is built for the call, so the handler takes one argument and passes it on as a whole. A class that cannot be created, or one that contains itself, is an error of the definition, and so is an object of options on a handler given to addCommand(), which reads no attributes at all. A normalizer written in an attribute may also be a static method as [class, name], which every PHP can write there, since a closure in an attribute needs PHP 8.5. | | | |
| command-line | by dg | added attributes: a handler describes its command by #[Command] and its inputs by #[Flag], #[Option] and #[Argument] A handler bound by name still needs its inputs defined somewhere above it, which for a command of its own means writing the same names twice. With the attributes the handler is the definition: addCommandFrom() reads #[Command] from the method and the inputs from its parameters, in their order, and addCommandsFrom() does it for every public method of an object that carries #[Command]. The name of the command is derived from the method, migrateSuppressions() is migrate-suppressions, and name: of the attribute overrides it; a closure, which has no name to derive, has to give one. The name of an input is derived the same way, $dryRun is --dry-run, the type of the parameter gives the enum when it is a backed one, and its default value becomes the default of the input, which an argument takes as being optional. The attributes are only read by addCommandFrom(), so a handler given to addCommand() that carries one is refused rather than silently ignored. They are read through reflection once, at definition time, and what they declare goes through the same add*() methods as a definition written by hand, so nothing is checked twice or checked differently. | | | |
| command-line | by dg | added handlers: a command holds a closure that ParseResult::run() calls with its inputs bound by name A command line that dispatches by hand is a match over the command and a read of every value; a handler says it once. The command holds the closure, the parser never calls it, and ParseResult::run() does, returning its exit code, 0 when it returns nothing. Each parameter is given the input it is named after, $maxWarnings the option --max-warnings or the argument max-warnings, or this result or the command when it is typed by one of them. HandlerBinding checks the binding before anything runs: that every parameter has exactly one input, and that it accepts every value that input can give, the default and the sentinel of an optional value included. The call itself is direct, never through reflection, which would coerce the values the type checking just made sure about. The binding is checked for the whole tree when parsing, since an input a handler binds to may be added after the handler; the signature alone, which cannot depend on the definitions, is refused as soon as the handler is given. | | | |
| command-line | by dg | Normalizers: added int(), float() and realPath() The normalizers an application writes over and over, ready made: a number refused unless it is written as one and within the range, and a path resolved to an absolute one or refused as not found. They report a bad value the way any normalizer does, by throwing with a message that fits after the name of the parameter, so the parser turns it into a ParseException of its own. The range is checked when the normalizer is built, not when it runs, so a minimum greater than its maximum is an error of the definition like any other. | | | |
| command-line | by dg | Parser: expanded bundled short flags and read a negative number as a value A token like -abc is replaced by -a -b -c and read again, but only when it is a clean bundle: every character a defined single-letter flag, with a value allowed for the last one alone. So a defined multi-letter short option such as -vf stays itself, and a token that is no bundle is reported as the unknown option it looked like. A negative number such as -5 or -.5 is a value, not an option, since that is what a user writing `--offset -5` means. Unless an option is named like a number: then every -5 is an option, as in argparse, and this is decided by the options valid at the command the line has reached, not by the program as a whole. An option that demands a value and finds a number is told that `--count=-5` gives it. | | | |
| command-line | by dg | Parser: suggested a close match for an unknown option or command, and named the owners of an option A name nobody knows is most often a typo, so the message offers the nearest candidate when one is close enough, measured by the edit distance and only where the name is long enough that the nearest is not just any other short flag. An option that is defined, but belongs to another command, is a different mistake and gets its own message: it names every command that owns an option of that name, so that a user who wrote it beside the wrong command sees where it belongs. A hidden option is never named and never suggested, since the help does not show it either. | | | |
| command-line | by dg | Flag: added standalone, the flag answers on its own like --help --help has to work before the configuration is read, the paths are resolved and the locks are taken, so a standalone flag answers between the first and the second phase, once the command is selected: the flag is true, the other flags are taken as given, since they need no conversion, and everything else is its default, nothing validated or converted. A required argument is not demanded either, and a command that requires a subcommand does not complain. The line still has to be a line: an unknown option or a value given to a flag is an error whatever answers. A negated flag does not answer, since --no-help asks for nothing. | | | |
| command-line | by dg | Parser: followed the commands down the tree The line is always read from the root: the first token that is no option picks a command, and from there the options of that command and of the ones above it are valid on both sides of its name. Given a subcommand, parse() still reads the whole line and refuses one that runs another command, so a caller cannot be handed values meant for somewhere else. A command that requires one of its subcommands and is given none is a missing command, and a token that names no command is an unknown one. The exception carries the command the line had reached, so the help drawn beside it is the help of that command. | | | |
| command-line | by dg | Parser: added, which reads the command line against the definition parse() takes a Command and returns a ParseResult, and what the line said is read from it like from an array. The parser keeps no state and checks no definition, since a Command refuses what is wrong when it is built. parse() works in three phases: collect the raw occurrences, convert what was supplied, fill in what was not. Presence is recorded apart from the value, so a normalizer may return null and an optional value is independent of the default one. A bare option yields the OptionPresent sentinel, which neither the enum check nor the normalizer sees, and only the last value of a parameter that is not repeatable is checked at all. The value itself is checked and converted by its own parameter, which does it for any caller, not only for the parser. A line that does not match the definition raises a ParseException, which says what was wrong as a ParseFailure and which parameter and token it concerns. Reading the result is strict where an array is not: an unknown name is refused, so that a typo cannot pass for a missing value, while isset() answers false for a known name with null. | | | |
| command-line | by dg | HelpRenderer: added, which draws the help of a command from its definition The help is drawn from the ordered items of a command, with a heading in front of every run of one kind where the author wrote none, and the options it inherits at the end. The usage is generated from the definitions unless the command gives its own lines, descriptions are wrapped to the width of the terminal and a syntax too long for the column keeps a line of its own rather than stretching the column for everybody. The colored output is the plain one wrapped in escape sequences, so stripping them gives the plain one back; the renderer colors through roles of its own, which a theme maps to colors, and the console knows no roles. Code written in backticks as Markdown does holds the role of code, which takes the place of the backticks where there are colors and stays as it is where a pipe needs to tell the code from the words; a color never spans two lines. formatCode() writes such a span, so that every message of the library speaks the same way, and splitCodeSpans() reads it back. renderError() writes a UsageException, the error of a command line used wrongly, followed by the help of the command in which it went wrong. | | | |
| command-line | by dg | Console: added setStatus(), which draws lines in place The status owns the drawing in place, so no caller moves the cursor itself: a line too long is cut, since a wrapped one cannot be redrawn, the cursor is hidden while the status stands and the next output of that console erases it. A status lives as long as its console, and a shutdown function holding a weak reference restores the cursor after a fatal error without keeping the console alive. | | | |
| command-line | by dg | Console: one console per stream, which writes in the colors the stream takes A console is one stream, and its colors, width and terminal follow that stream, so an application writing to stdout and stderr makes one for each. The depth of colors is asked from the stream and honors NO_COLOR, FORCE_COLOR and COLORTERM; what the application says wins over all. hasColors() and isTerminal() are separate on purpose: color is gated on the first, interactive-only features on the second, since a user may disable color yet still sit at a real terminal. The console writes what it is given, so color() and link() are the only places that add a sequence, and text from elsewhere is filtered by the caller. A color is a name of the sixteen the terminal draws in the colors of its theme, a number 0-255 of the extended palette or an RGB written as a hex. Color knows the RGB of each, a name its aixterm SGR code and an index its number, and toSgr() gives the parameters the depth allows: the code of a name, the index where the palette is taken, an RGB exactly in true color and otherwise the nearest color there is. The nearest of the sixteen is found by hue and lightness, since a plain distance of RGB gives most pastel colors a gray. | | | |
| command-line | by dg | docs: added internals, how the library works The classes are small and mostly clear from their signatures, so the internals keep only what an agent or a reader would get wrong or waste time rediscovering: the three phases of parse() with the command selection and the sentinel of a bare option, what isset() on ParseResult answers, how a handler binds and what is checked when, and the two questions a console answers about its stream. The how-to of add*() and the format of the help belongs to the manual, not here. AGENTS.md is rewritten for the same classes and points at docs/ instead of describing the parser of 1.x. | | | |