addDescription( 'Generates various config schema files.' ); $this->addOption( 'vars', 'Path to output variable stubs to. ' . 'Default if none of the options is given: ' . self::DEFAULT_VARS_PATH, false, true ); $this->addOption( 'schema', 'Path to output the schema array to. ' . 'Default if none of the options is given: ' . self::DEFAULT_ARRAY_PATH, false, true ); $this->addOption( 'names', 'Path to output the name constants to. ' . 'Default if none of the options is given: ' . self::DEFAULT_NAMES_PATH, false, true ); $this->addOption( 'yaml', 'Path to output the schema YAML to. ' . 'Default if none of the options is given: ' . self::DEFAULT_SCHEMA_PATH, false, true ); } public function canExecuteWithoutLocalSettings(): bool { return true; } public function getDbType() { return self::DB_NONE; } /** * Loads the config schema from the MainConfigSchema class. * * @return array An associative array with a single key, 'config-schema', * containing the config schema definition. */ private function getSettings(): array { if ( !$this->settingsArray ) { $source = new ReflectionSchemaSource( MainConfigSchema::class, true ); $this->settingsArray = $source->load(); } return $this->settingsArray; } /** * @param string $path * @param string $content */ private function writeOutput( $path, $content ) { // ensure a single line break at the end of the file $content = trim( $content ) . "\n"; file_put_contents( $path, $content ); } /** * @param string $name The name of the option * * @return ?string */ private function getOutputPath( string $name ): ?string { $outputPath = $this->getOption( $name ); if ( $outputPath === '-' ) { $outputPath = self::STDOUT; } return $outputPath; } public function execute() { $settings = $this->getSettings(); $allSchemas = $settings['config-schema']; $obsolete = $settings['obsolete-config'] ?? []; $schemaPath = $this->getOutputPath( 'schema' ); $varsPath = $this->getOutputPath( 'vars' ); $yamlPath = $this->getOutputPath( 'yaml' ); $namesPath = $this->getOutputPath( 'names' ); if ( $schemaPath === null && $varsPath === null && $yamlPath === null && $namesPath === null ) { // If no output path is specified explicitly, use the default path for all. $schemaPath = self::DEFAULT_ARRAY_PATH; $varsPath = self::DEFAULT_VARS_PATH; $yamlPath = self::DEFAULT_SCHEMA_PATH; $namesPath = self::DEFAULT_NAMES_PATH; } if ( $schemaPath === self::STDOUT || $varsPath === self::STDOUT || $yamlPath === self::STDOUT || $namesPath === self::STDOUT ) { // If any of the output is stdout, switch to quiet mode. $this->mQuiet = true; } if ( $schemaPath !== null ) { $this->output( "Writing schema array to $schemaPath\n" ); $this->writeOutput( $schemaPath, $this->generateSchemaArray( $allSchemas, $obsolete ) ); } if ( $varsPath !== null ) { $this->output( "Writing variable stubs to $varsPath\n" ); $this->writeOutput( $varsPath, $this->generateVariableStubs( $allSchemas ) ); } if ( $yamlPath !== null ) { $this->output( "Writing schema YAML to $yamlPath\n" ); $this->writeOutput( $yamlPath, $this->generateSchemaYaml( $allSchemas ) ); } if ( $namesPath !== null ) { $this->output( "Writing name constants to $namesPath\n" ); $this->writeOutput( $namesPath, $this->generateNames( $allSchemas ) ); } } public function generateSchemaArray( array $allSchemas, array $obsolete ) { $aggregator = new ConfigSchemaAggregator(); foreach ( $allSchemas as $key => $schema ) { $aggregator->addSchema( $key, $schema ); } $schemaInverse = [ 'default' => $aggregator->getDefaults(), 'type' => $aggregator->getTypes(), 'mergeStrategy' => $aggregator->getMergeStrategyNames(), 'dynamicDefault' => $aggregator->getDynamicDefaults(), ]; $keyMask = array_flip( [ 'default', 'type', 'mergeStrategy', 'dynamicDefault', 'description', 'properties' ] ); $schemaExtra = []; foreach ( $aggregator->getDefinedKeys() as $key ) { $sch = $aggregator->getSchemaFor( $key ); $sch = array_diff_key( $sch, $keyMask ); if ( $sch ) { $schemaExtra[ $key ] = $sch; } } $content = ( new StaticArrayWriter() )->write( [ 'config-schema-inverse' => $schemaInverse, 'config-schema' => $schemaExtra, 'obsolete-config' => $obsolete ], "This file is automatically generated using maintenance/generateConfigSchema.php.\n" . "Do not modify this file manually, edit includes/MainConfigSchema.php instead.\n" . "phpcs:disable Generic.Files.LineLength" ); return $content; } public function generateNames( array $allSchemas ) { $code = " $configSchema ) { $code .= "\n"; $code .= $this->getConstantDeclaration( $configKey, $configSchema ); } $code .= "\n}\n"; return $code; } /** * @param string $name * @param array $schema * * @return string */ private function getConstantDeclaration( string $name, array $schema ): string { $chunks = []; $chunks[] = "Name constant for the $name setting, for use with Config::get()"; $chunks[] = "@see MainConfigSchema::$name"; if ( isset( $schema['since'] ) ) { $chunks[] = "@since {$schema['since']}"; } if ( isset( $schema['deprecated'] ) ) { $deprecated = str_replace( "\n", "\n\t * ", wordwrap( $schema['deprecated'] ) ); $chunks[] = "@deprecated {$deprecated}"; } $code = "\t/**\n\t * "; $code .= implode( "\n\t * ", $chunks ); $code .= "\n\t */\n"; $code .= "\tpublic const $name = '$name';\n"; return $code; } public function generateSchemaYaml( array $allSchemas ) { foreach ( $allSchemas as &$sch ) { // Cast empty arrays to objects if they are declared to be of type object. // This ensures they get represented in yaml as {} rather than []. if ( isset( $sch['default'] ) && isset( $sch['type'] ) ) { $types = (array)$sch['type']; if ( $sch['default'] === [] && in_array( 'object', $types ) ) { $sch['default'] = new stdClass(); } } // Wrap long deprecation messages if ( isset( $sch['deprecated'] ) ) { $sch['deprecated'] = wordwrap( $sch['deprecated'] ); } } // Dynamic defaults are not relevant to yaml consumers unset( $sch['dynamicDefault'] ); $yamlFlags = Yaml::DUMP_OBJECT_AS_MAP | Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK | Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE; $array = [ 'config-schema' => $allSchemas ]; $yaml = Yaml::dump( $array, 4, 4, $yamlFlags ); $header = "# This file is automatically generated using maintenance/generateConfigSchema.php.\n"; $header .= "# Do not modify this file manually, edit includes/MainConfigSchema.php instead.\n"; return $header . $yaml; } public function generateVariableStubs( array $allSchemas ) { $content = " $schema ) { $content .= "\n"; $content .= $this->getVariableDeclaration( $name, $schema ); } return $content; } /** * @param string $name * @param array $schema * * @return string */ private function getVariableDeclaration( string $name, array $schema ): string { $chunks = []; $chunks[] = "Config variable stub for the $name setting, for use by phpdoc and IDEs."; $chunks[] = "@see MediaWiki\\MainConfigSchema::$name"; if ( isset( $schema['since'] ) ) { $chunks[] = "@since {$schema['since']}"; } if ( isset( $schema['deprecated'] ) ) { $deprecated = str_replace( "\n", "\n * ", wordwrap( $schema['deprecated'] ) ); $chunks[] = "@deprecated {$deprecated}"; } $code = "/**\n * "; $code .= implode( "\n * ", $chunks ); $code .= "\n */\n"; $code .= "\$wg{$name} = null;\n"; return $code; } } // @codeCoverageIgnoreStart $maintClass = GenerateConfigSchema::class; require_once RUN_MAINTENANCE_IF_MAIN; // @codeCoverageIgnoreEnd