Skip to content

Commit

Permalink
Merge branch 'master' into integration-test
Browse files Browse the repository at this point in the history
Sync with master
  • Loading branch information
overclokk committed May 30, 2024
2 parents d788416 + 9a384b4 commit fe371b2
Show file tree
Hide file tree
Showing 66 changed files with 1,611 additions and 652 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ codeception.yml
*.phar
*.bak
infection.json.dist
c3.php
c3.php

# Theme JSON Schema
theme.schema.json
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,21 @@ composer/update: up ### Update the composer dependencies
@echo "Updating the composer dependencies"
@$(DOCKER_DIR) ./composer update

.PHONY: composer/update/nodev
composer/update/nodev: up ### Update the composer dependencies
@echo "Updating the composer dependencies"
@$(DOCKER_DIR) ./composer update --no-dev

.PHONY: composer/dump
composer/dump: up ### Dump the composer autoload
@echo "Dumping the composer autoload"
@$(DOCKER_DIR) ./composer dump-autoload

.PHONY: composer/validate
composer/validate: up ### Validate the composer.json file
@echo "Validating the composer.json file"
@$(DOCKER_DIR) ./composer validate

# Codestyle commands

.PHONY: cs
Expand Down
2 changes: 0 additions & 2 deletions bin/theme-json.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@

namespace ItalyStrap\ThemeJsonGenerator;

use ItalyStrap\ThemeJsonGenerator\Infrastructure\Cli\Composer\Bootstrap;

/** @psalm-suppress UnresolvableInclude */
require $_composer_autoload_path ?? __DIR__ . '/../vendor/autoload.php';

Expand Down
16 changes: 7 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
"require": {
"php" : ">=7.4",
"ext-json": "*",
"composer/composer": "^2.1",
"composer-plugin-api": "^2.0",

"italystrap/config": "^2.4",
"italystrap/empress": "^2.0",
Expand All @@ -30,10 +28,15 @@

"justinrainbow/json-schema": "^5.2",
"scssphp/scssphp": "^1.12.1",
"sabberworm/php-css-parser": "^8.5",
"brick/varexporter": "^0.3.8",
"webimpress/safe-writer": "^2.2",

"symfony/polyfill-php80": "^1.22"
"symfony/console": "^v5.4",
"symfony/process": "^v5.4",
"symfony/polyfill-php80": "^1.22",
"symfony/event-dispatcher": "^5.4",
"webmozart/assert": "^1.11"
},
"require-dev": {
"lucatume/wp-browser": "~3.2.3",
Expand Down Expand Up @@ -61,11 +64,7 @@
"rector/rector": "^0.19.0",
"symplify/easy-coding-standard": "^12.0",

"italystrap/debug": "dev-master",
"wp-cli/wp-cli": "^2.7",

"psr/event-dispatcher": "^1.0",
"sabberworm/php-css-parser": "^8.0"
"italystrap/debug": "dev-master"
},
"autoload": {
"psr-4": {
Expand All @@ -86,7 +85,6 @@
}
},
"suggest": {
"spatie/color": "Allows to convert CSS color"
},
"bin": [
"bin/theme-json"
Expand Down
120 changes: 109 additions & 11 deletions docs/02-advanced-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,15 @@ Once you've added the various Preset instances to the `Presets` collection, the

## Styles

The `Styles` directory offers builder classes implementing the `Fluent Interface` pattern, enabling intuitive and chainable configuration of your theme's styles. Importantly, each class is immutable, ensuring robustness by returning new instances for every method call. This design allows for clear and concise style definitions within your entrypoint file.
The `Styles` directory offers builder classes implementing the `Fluent Interface` pattern, enabling intuitive and chainable configuration of your theme's styles. Importantly, each class is immutable, ensuring robustness by returning new instance on every method call. This design allows for clear and concise style definitions within your entrypoint file.

In this directory, you'll find classes tailored to different aspects of theme styling, aligned with the `theme.json` structure. These classes, such as `Border`, `Color`, `Css`, `Outline`, `Spacing`, `Typography`, and more, offer specific methods for configuring corresponding style properties, all method declared in each class follows the `theme.json` schema and each class has its own methods following the properties they represent.

These classes support three primary methods of utilization, each offering a unique approach to styling:

Directly create an instance of a style class, such as `Color`, and chain methods to define properties. This approach is straightforward and effective for setting styles directly:
We will take the `Color` class as an example, but all the other classes follow the same pattern.

Directly create an instance of a `Color::class`, and chain methods to define properties. This approach is straightforward and effective for setting styles directly:

```php
use ItalyStrap\ThemeJsonGenerator\Domain\Input\Styles\Color;
Expand Down Expand Up @@ -263,13 +267,13 @@ use ItalyStrap\ThemeJsonGenerator\Domain\Input\Styles\Color;
[
SectionNames::STYLES => [
'color' => (new Color($presets))
->background(Palette::CATEGORY . '.base')
->text(Palette::CATEGORY . '.bodyColor'),
->background(Palette::TYPE . '.base')
->text(Palette::TYPE . '.bodyColor'),
],
];
```

In this case the key passed to the method will be used as a key in the `$presets` collection.
In this case the key passed to the method will be used as a key in the `$presets` collection, the `key` has the format `type.slug` where `type` is the type of the preset and `slug` is the slug of the preset.

In JSON format:

Expand Down Expand Up @@ -309,19 +313,113 @@ use ItalyStrap\ThemeJsonGenerator\Domain\Input\Styles\Color;
[
SectionNames::STYLES => [
'color' => $container->get(Color::class)
->background(Palette::CATEGORY . '.base')
->text(Palette::CATEGORY . '.bodyColor'),
->background(Palette::TYPE . '.base')
->text(Palette::TYPE . '.bodyColor'),
],
];
```

As you can see with the `$container` object you can use a `Color` class without the need to pass the `$presets` object to the constructor because all the dependencies of the `Color` are already registered in the container.
As you can see with the `$container` object you can use a `Color` class without the need to pass the `$presets` object to the constructor because all the dependencies of the `Color` (and all other classes in the `Styles` directory) are already registered in the container.

You can find an implementation example in the [tests/_data/fixtures/advanced-example.json.php](../tests/_data/fixtures/advanced-example.json.php) file.

### Custom CSS for Global Styles and per Block

More information about the `css` field can be found in:

* [WordPress 6.2 release notes](https://wordpress.org/news/2023/03/dolphy/).
* [Custom CSS for Global Styles and per Block](https://make.wordpress.org/core/2023/03/06/custom-css-for-global-styles-and-per-block/).
* [Per Block CSS with theme.json](https://developer.wordpress.org/news/2023/04/21/per-block-css-with-theme-json/).
* [Global Settings and Styles](https://developer.wordpress.org/themes/global-settings-and-styles/).
* [How to use custom CSS in theme.json - fullsiteediting.com](https://fullsiteediting.com/lessons/how-to-use-custom-css-in-theme-json/).

The introduction of the `css` field in [WordPress 6.2](https://wordpress.org/news/2023/03/dolphy/) enables the addition of [custom CSS](https://make.wordpress.org/core/2023/03/06/custom-css-for-global-styles-and-per-block/) directly within the `theme.json` file, both globally under `styles.css` and per block within `styles.blocks.[block-name].css`. Utilizing the {`\ItalyStrap\ThemeJsonGenerator\Domain\Input\Styles\Css`|`\ItalyStrap\ThemeJsonGenerator\Domain\Input\Styles\Scss`} classes and theirs `parse(string $css, string $selector = ''): string` method, developers can now seamlessly integrate custom styles without the need to remember the format to use with `&` separator, just write your CSS (or Scss) as you would in a regular CSS|SCSS file and let the `Css`|`Scss` class handle the rest.

This method accepts two parameters: the CSS to parse and an optional selector to scope the CSS rules accordingly.

How It Works

The `Css`|`Scss` class efficiently parses the provided CSS, extracting and formatting rules based on the specified selector. This functionality ensures that the output is fully compatible with the `theme.json` structure, enhancing the flexibility and customization of theme development.

So, let's see some examples:

```php
// Result: 'height: 100%;'
echo (new Css($presets))->parse('.test-selector{height: 100%;}', '.test-selector');
```

```php
// Result: 'height: 100%;width: 100%;color: red;&:hover {color: red;}&::placeholder {color: red;}'
echo (new Css($presets))->parse('.test-selector{height: 100%;width: 100%;color: red;}.test-selector:hover {color: red;}.test-selector::placeholder {color: red;}', '.test-selector');
```

Like the other classes in the `Styles` directory, you can use the `Css` class directly or pass the `$presets` collection to the constructor or use the `$container` object to get the instance you need, the only exception is for the `Scss` class that need an instance of `Css` class and an instance of `ScssPhp\ScssPhp\Compiler` class to work, but if you use the `$container` object you don't need to worry about it because all the dependencies are already registered in the container.

As the name suggests, the `Scss` class is used to parse SCSS styles, so you are free to write your styles in SCSS format and let the class handle the conversion for you.

Let's see it in action:

```php
use ItalyStrap\ThemeJsonGenerator\Domain\Input\Styles\Css;

[
SectionNames::STYLES => [
'css' => $container->get(Css::class) // Or (new Css($presets)) or (new Scss(new Css($presets), $scssCompiler, $presets))
->parse('.test-selector{height: 100%;width: 100%;color: red;}.test-selector:hover {color: red;}.test-selector::placeholder {color: red;}', '.test-selector'),
],
];
```

For block style:

```php
use ItalyStrap\ThemeJsonGenerator\Domain\Input\Styles\Css;

[
SectionNames::STYLES => [
'blocks' => [
'my-namespace/test-block' => [
'css' => $container->get(Css::class) // Or (new Css($presets)) or (new Scss(new Css($presets), $scssCompiler, $presets))
->parse('.test-selector{height: 100%;width: 100%;color: red;}.test-selector:hover {color: red;}.test-selector::placeholder {color: red;}', '.test-selector'),
],
],
],
];
```

All methods also support a special syntax to resolve value in the `$presets` collection, `{{type.slug}}`, this syntax will be used internally to find the value in the `$presets` collection registered before.


```php
use ItalyStrap\ThemeJsonGenerator\Domain\Input\Styles\Css;

[
SectionNames::STYLES => [
'css' => $container->get(Css::class) // Or (new Css($presets))
->parse('.test-selector{color: {{color.base}};}', '.test-selector'),
],
];
```

The `{{color.base}}` will be replaced with the value of the `color.base` previously set in the `$presets` collection.

```json
{
"styles": {
"css": ".test-selector{color: var(--wp--preset--color--base);}"
}
}
```

### Available Style Classes
More examples can be found in the [tests/_data/fixtures/advanced-example.json.php](../tests/_data/fixtures/advanced-example.json.php) file.

The `Styles` directory includes various classes tailored to different aspects of theme styling, aligned with the `theme.json` structure. These classes, such as `Border`, `Color`, `Css`, `Outline`, `Spacing`, `Typography`, offer specific methods for configuring corresponding style properties, all method declared in each class follows the `theme.json` schema and each class has its own methods following the properties they represent.
To know more about `css` field:

You can see more examples in the [tests/_data/fixtures/advanced-example.json.php](../tests/_data/fixtures/advanced-example.json.php) file.
* https://make.wordpress.org/core/2023/03/06/custom-css-for-global-styles-and-per-block/
* https://developer.wordpress.org/news/2023/04/21/per-block-css-with-theme-json/
* https://developer.wordpress.org/themes/global-settings-and-styles/
* https://fullsiteediting.com/lessons/how-to-use-custom-css-in-theme-json/
* https://make.wordpress.org/core/2022/12/22/whats-new-in-gutenberg-14-8-21-december/#Add-custom-CSS-rules-for-your-site

### Advanced Service Injection with Empress and PSR-11 Container

Expand Down
4 changes: 0 additions & 4 deletions functions/autoload.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,3 @@
declare(strict_types=1);

namespace ItalyStrap\ThemeJsonGenerator;

use ItalyStrap\ThemeJsonGenerator\Infrastructure\Cli\WPCLI\Bootstrap;

(new Bootstrap())();
1 change: 0 additions & 1 deletion psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
<directory name="functions" />
<directory name="src" />
<ignoreFiles>
<directory name="src/Application/Commands/WPCLI" />
<directory name="vendor" />
<file name="src/**/*Experimental*" />
</ignoreFiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

declare(strict_types=1);

namespace ItalyStrap\ThemeJsonGenerator\Application\Commands\Composer;
namespace ItalyStrap\ThemeJsonGenerator\Application\Commands;

use Composer\Command\BaseCommand;
use ItalyStrap\Config\ConfigInterface;
use ItalyStrap\ThemeJsonGenerator\Application\Commands\DumpMessage;
use ItalyStrap\ThemeJsonGenerator\Application\Commands\Utils\RootFolderTrait;
use ItalyStrap\ThemeJsonGenerator\Application\DumpMessage;
use ItalyStrap\ThemeJsonGenerator\Domain\Output\Dump;
use ItalyStrap\ThemeJsonGenerator\Domain\Output\Events\GeneratedFile;
use ItalyStrap\ThemeJsonGenerator\Domain\Output\Events\GeneratingFile;
use ItalyStrap\ThemeJsonGenerator\Domain\Output\Events\NoFileFound;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
Expand All @@ -20,7 +19,7 @@
/**
* @psalm-api
*/
final class DumpCommand extends BaseCommand
final class DumpCommand extends Command
{
use RootFolderTrait;

Expand All @@ -44,20 +43,21 @@ final class DumpCommand extends BaseCommand
*/
public const PATH_FOR_THEME_SASS = 'path-for-theme-sass';

private Dump $dump;
/**
* @var string
*/
public const FILE = 'file';

private ConfigInterface $config;
private Dump $dump;

private \Symfony\Component\EventDispatcher\EventDispatcher $subscriber;

public function __construct(
\Symfony\Component\EventDispatcher\EventDispatcher $subscriber,
Dump $dump,
ConfigInterface $config
Dump $dump
) {
$this->subscriber = $subscriber;
$this->dump = $dump;
$this->config = $config;
parent::__construct();
}

Expand Down Expand Up @@ -87,6 +87,26 @@ protected function configure(): void
)
);

$this->addOption(
'path',
'p',
InputOption::VALUE_OPTIONAL,
\sprintf(
'If set, %s will generate the json file in the specified path.',
self::NAME
)
);

$this->addOption(
self::FILE,
null,
InputOption::VALUE_OPTIONAL,
\sprintf(
'If set, %s will generate only the specified file.',
self::NAME
)
);

/**
* @todo other options:
* --no-pretty-print
Expand All @@ -99,18 +119,6 @@ protected function configure(): void

protected function execute(InputInterface $input, OutputInterface $output): int
{
$composer = $this->requireComposer();
$rootFolder = $this->rootFolder();

$package = $composer->getPackage();
/** @psalm-suppress MixedArgument */
$this->config->merge($package->getExtra()[self::COMPOSER_EXTRA_THEME_JSON_KEY] ?? []);

/**
* @todo The callback is temporary until the new files generation are in place.
* @psalm-suppress MixedAssignment
*/
$callback = $this->config->get(self::CALLBACK);

$this->subscriber->addListener(
GeneratingFile::class,
Expand All @@ -133,23 +141,23 @@ static function (GeneratedFile $event) use ($output): void {
}
);

$this->subscriber->addListener(
NoFileFound::class,
/** @psalm-suppress UnusedClosureParam */
static function (NoFileFound $event) use ($output): void {
$output->writeln(NoFileFound::M_NO_FILE_FOUND);
}
);

$rootFolder = $this->rootFolder((string)$input->getOption('path'));

$message = new DumpMessage(
$rootFolder,
(string)$this->config->get(self::PATH_FOR_THEME_SASS, ''),
(bool)$input->getOption('dry-run')
'',
(bool)$input->getOption('dry-run'),
(string)$input->getOption(self::FILE)
);

try {
if (\is_callable($callback)) {
$output->writeln('<info>Generating theme.json file</info>');
$this->dump->processBlueprint($message, 'theme', $callback);
$output->writeln('<info>Generated theme.json file</info>');
$output->writeln('========================');
}
} catch (\Exception $exception) {
$output->writeln($exception->getMessage());
}

$this->dump->handle($message);

if ($input->getOption(ValidateCommand::NAME)) {
Expand Down
Loading

0 comments on commit fe371b2

Please sign in to comment.