├── .github
└── workflows
│ ├── phpstan.yml
│ └── test.yml
├── .gitignore
├── .php-cs-fixer.php
├── .phpactor.json
├── .phpactor.yml
├── .phpactor
└── templates
│ ├── default
│ └── SourceCode.php.twig
│ ├── interface
│ └── SourceCode.php.twig
│ ├── phpunit_test
│ └── SourceCode.php.twig
│ └── zest
│ └── SourceCode.php.twig
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── Untitled
├── composer.json
├── devtools
├── app.php
├── app_doc.php
├── lines.php
└── staticdoc.php
├── phpstan.neon
├── phpunit.xml
├── src
└── Lemon
│ ├── Cache.php
│ ├── Cache
│ ├── Cache.php
│ ├── Exceptions
│ │ ├── CacheException.php
│ │ └── InvalidArgumentException.php
│ ├── README.md
│ └── config.php
│ ├── Config.php
│ ├── Config
│ ├── Config.php
│ ├── Exceptions
│ │ └── ConfigException.php
│ └── README.md
│ ├── Contracts
│ ├── Cache
│ │ └── Cache.php
│ ├── Config
│ │ └── Config.php
│ ├── Database
│ │ └── Database.php
│ ├── Debug
│ │ ├── Dumper.php
│ │ └── Handler.php
│ ├── Events
│ │ └── Dispatcher.php
│ ├── Highlighter
│ │ └── Highlighter.php
│ ├── Http
│ │ ├── CookieJar.php
│ │ ├── ResponseFactory.php
│ │ └── Session.php
│ ├── Kernel
│ │ └── Injectable.php
│ ├── Logging
│ │ └── Logger.php
│ ├── Protection
│ │ └── Csrf.php
│ ├── README.md
│ ├── Routing
│ │ └── Router.php
│ ├── Support
│ │ └── Env.php
│ ├── Templating
│ │ ├── Compiler.php
│ │ └── Factory.php
│ ├── Terminal
│ │ └── Terminal.php
│ ├── Translating
│ │ └── Translator.php
│ └── Validation
│ │ └── Validator.php
│ ├── Csrf.php
│ ├── DB.php
│ ├── DataMapper
│ └── DataMapper.php
│ ├── Database
│ ├── Database.php
│ ├── Drivers
│ │ ├── Driver.php
│ │ ├── Mysql.php
│ │ ├── Postre.php
│ │ └── Sqlite.php
│ ├── README.md
│ └── config.php
│ ├── Debug.php
│ ├── Debug
│ ├── Dumper.php
│ ├── Exceptions
│ │ └── DebugerException.php
│ ├── Handling
│ │ ├── Consultant.php
│ │ ├── Handler.php
│ │ ├── README.md
│ │ ├── Reporter.php
│ │ ├── TerminalReporter.php
│ │ └── templates
│ │ │ └── reporter.phtml
│ ├── README.md
│ ├── Style.php
│ ├── config.php
│ └── stubs
│ │ └── style.html.stub
│ ├── Env.php
│ ├── Event.php
│ ├── Events
│ ├── Dispatcher.php
│ └── README.md
│ ├── Highlighter.php
│ ├── Highlighter
│ ├── Highlighter.php
│ ├── README.md
│ └── config.php
│ ├── Http
│ ├── CookieJar.php
│ ├── Exceptions
│ │ ├── CookieException.php
│ │ └── SessionException.php
│ ├── File.php
│ ├── Middlewares
│ │ ├── Cors.php
│ │ └── TrimStrings.php
│ ├── README.md
│ ├── Request.php
│ ├── Response.php
│ ├── ResponseFactory.php
│ ├── Responses
│ │ ├── CustomResponse.php
│ │ ├── EmptyResponse.php
│ │ ├── HtmlResponse.php
│ │ ├── JsonResponse.php
│ │ ├── RedirectResponse.php
│ │ ├── TemplateResponse.php
│ │ └── TextResponse.php
│ ├── Session.php
│ ├── config.php
│ └── templates
│ │ └── error.phtml
│ ├── Juice.php
│ ├── Kernel
│ ├── Application.php
│ ├── Commands.php
│ ├── Container.php
│ ├── Exceptions
│ │ ├── ContainerException.php
│ │ ├── LifecycleException.php
│ │ └── NotFoundException.php
│ ├── README.md
│ └── templates
│ │ ├── help.phtml
│ │ └── maintenance.php
│ ├── Log.php
│ ├── Logging
│ ├── Logger.php
│ ├── README.md
│ └── config.php
│ ├── Protection
│ ├── Csrf.php
│ ├── Middlwares
│ │ └── Csrf.php
│ └── README.md
│ ├── ResponseFactory.php
│ ├── Route.php
│ ├── Routing
│ ├── Attributes
│ │ └── AfterAction.php
│ ├── Collection.php
│ ├── Exceptions
│ │ └── RouteException.php
│ ├── MiddlewareCollection.php
│ ├── README.md
│ ├── Route.php
│ └── Router.php
│ ├── Session.php
│ ├── Support
│ ├── CaseConverter.php
│ ├── Env.php
│ ├── Exceptions
│ │ └── FilesystemException.php
│ ├── Filesystem.php
│ ├── Macros.php
│ ├── Pipe.php
│ ├── Properties
│ │ ├── Properties.php
│ │ └── Read.php
│ ├── README.md
│ ├── Regex.php
│ ├── Types
│ │ ├── Arr.php
│ │ ├── Just.php
│ │ ├── Maybe.php
│ │ ├── Nothing.php
│ │ ├── Proxy.php
│ │ ├── Stack.php
│ │ ├── Str.php
│ │ └── Type.php
│ └── helpers.php
│ ├── Template.php
│ ├── Templating
│ ├── Environment.php
│ ├── Exceptions
│ │ ├── CompilerException.php
│ │ ├── SyntaxException.php
│ │ └── TemplateException.php
│ ├── Factory.php
│ ├── Juice
│ │ ├── Compiler.php
│ │ ├── Compilers
│ │ │ ├── DirectiveCompiler.php
│ │ │ ├── Directives
│ │ │ │ ├── CaseDirective.php
│ │ │ │ ├── CsrfDirective.php
│ │ │ │ ├── Directive.php
│ │ │ │ ├── ElseDirective.php
│ │ │ │ ├── ElseIfDirective.php
│ │ │ │ ├── ErrorDirective.php
│ │ │ │ ├── ForDirective.php
│ │ │ │ ├── ForeachDirective.php
│ │ │ │ ├── IfDirective.php
│ │ │ │ ├── IfErrorDirective.php
│ │ │ │ ├── IncludeDirective.php
│ │ │ │ ├── Layout
│ │ │ │ │ ├── BlockDirective.php
│ │ │ │ │ ├── ExtendsDirective.php
│ │ │ │ │ ├── Layout.php
│ │ │ │ │ └── YieldDirective.php
│ │ │ │ ├── SwitchDirective.php
│ │ │ │ ├── TranslationDirective.php
│ │ │ │ ├── UnlessDirective.php
│ │ │ │ └── WhileDirective.php
│ │ │ └── OutputCompiler.php
│ │ ├── Lexer.php
│ │ ├── Parser.php
│ │ ├── README.md
│ │ ├── Syntax.php
│ │ └── Token.php
│ ├── Template.php
│ └── config.php
│ ├── Terminal.php
│ ├── Terminal
│ ├── Commands
│ │ ├── Command.php
│ │ └── Dispatcher.php
│ ├── Exceptions
│ │ ├── CommandException.php
│ │ ├── HtmlException.php
│ │ └── IOException.php
│ ├── IO
│ │ ├── Html
│ │ │ ├── Components.php
│ │ │ ├── HtmlOutput.php
│ │ │ ├── README.md
│ │ │ └── Styles.php
│ │ └── Output.php
│ ├── README.md
│ └── Terminal.php
│ ├── Testing
│ ├── Mock.php
│ ├── SessionMock.php
│ ├── TestCase.php
│ └── TestResponse.php
│ ├── Translating
│ ├── Exceptions
│ │ └── TranslatorException.php
│ ├── Middlewares
│ │ └── TranslationLocalizer.php
│ ├── Translator.php
│ ├── config.php
│ └── translations
│ │ ├── README.md
│ │ └── en
│ │ └── validation.php
│ ├── Translator.php
│ ├── Validation
│ ├── Exceptions
│ │ └── ValidatorException.php
│ ├── README.md
│ ├── Rules.php
│ └── Validator.php
│ ├── Validator.php
│ └── Zest.php
└── tests
├── Cache
└── CacheTest.php
├── Config
├── ConfigTest.php
└── config
│ ├── cache.php
│ ├── schnitzels.php
│ └── something.txt
├── DataMapper
└── DataMapperTest.php
├── Database
├── .env
├── DatabaseTest.php
└── Drivers
│ ├── .env
│ └── SqliteTest.php
├── Debug
├── ConsultantTest.php
├── DumperTest.php
├── StyleTest.php
├── TerminalReporterTest.php
└── foo.php
├── Events
└── DispatcherTest.php
├── Highlighter
└── HighlighterTest.php
├── Http
├── CookieJarTest.php
├── Middlewares
│ ├── CorsTest.php
│ └── TrimStringsTest.php
├── RequestTest.php
├── ResponseFactoryTest.php
├── ResponseTest.php
├── storage
│ └── templates
│ │ └── .gitignore
├── templates
│ └── errors
│ │ └── 404.phtml
└── tmp
│ └── foo.php
├── Kernel
├── ContainerTest.php
├── LifecycleTest.php
├── Resources
│ ├── IFoo.php
│ ├── Units
│ │ ├── Bar.php
│ │ ├── Baz.php
│ │ ├── Foo.php
│ │ ├── User.php
│ │ └── UserFactory.php
│ └── Zests
│ │ └── Bar.php
└── ZestTest.php
├── Logging
└── LoggerTest.php
├── Protection
└── CsrfTest.php
├── README.md
├── Routing
├── CollectionTest.php
├── MiddlewareTest.php
├── RouteTest.php
├── RouterTest.php
└── routes
│ └── web.php
├── Support
├── CaseConverterTest.php
├── EnvTest.php
├── FilesystemTest.php
├── Helpers.php
├── HelpersTest.php
├── MacrosTest.php
├── PipeTest.php
├── Properties
│ └── PropertiesTest.php
├── RegexTest.php
└── Types
│ ├── ArrTest.php
│ ├── MaybeTest.php
│ ├── StackTest.php
│ └── TypeTest.php
├── Templating
├── FactoryTest.php
├── Juice
│ ├── CompilerTest.php
│ ├── Compilers
│ │ ├── DirectiveCompilerTest.php
│ │ ├── Directives
│ │ │ ├── CaseDirectiveTest.php
│ │ │ ├── CsrfDirectiveTest.php
│ │ │ ├── ElseDirectiveTest.php
│ │ │ ├── ElseIfDirectiveTest.php
│ │ │ ├── ErrorDirectiveTest.php
│ │ │ ├── ForDirectiveTest.php
│ │ │ ├── ForeachDirectiveTest.php
│ │ │ ├── IfDirectiveTest.php
│ │ │ ├── IfErrorDirectiveTest.php
│ │ │ ├── IncludeDirectiveTest.php
│ │ │ ├── Layout
│ │ │ │ ├── BlockTest.php
│ │ │ │ ├── ExtendsTest.php
│ │ │ │ ├── LayoutTest.php
│ │ │ │ ├── YieldTest.php
│ │ │ │ ├── storage
│ │ │ │ │ └── templates
│ │ │ │ │ │ └── .gitignore
│ │ │ │ └── templates
│ │ │ │ │ ├── bar.php
│ │ │ │ │ ├── foo.php
│ │ │ │ │ └── foo
│ │ │ │ │ └── bar.juice
│ │ │ ├── SwitchDirectiveTest.php
│ │ │ ├── TranslationDirectiveTest.php
│ │ │ ├── UnlessDirectiveTest.php
│ │ │ ├── WhileDirectiveTest.php
│ │ │ ├── storage
│ │ │ │ └── templates
│ │ │ │ │ └── .gitignore
│ │ │ └── templates
│ │ │ │ └── foo
│ │ │ │ └── bar.juice
│ │ └── OutputCompilerTest.php
│ ├── ContextTest.php
│ ├── EnviromentTest.php
│ ├── LexerTest.php
│ └── ParserTest.php
├── TemplateTest.php
├── bar.juice
├── bar.php
├── fixtures
│ └── test.foo
├── foo.juice
├── foo.php
└── templates
│ └── foo
│ ├── bar.foo
│ └── baz.foo
├── Terminal
├── Commands
│ ├── CommandTest.php
│ └── DispatcherTest.php
└── IO
│ ├── Html
│ ├── ComponentsTest.php
│ ├── HtmlOutputTest.php
│ └── StylesTest.php
│ ├── OutputTest.php
│ └── templates
│ └── foo.phtml
├── TestCase.php
├── Testing
├── TestCase.php
├── TestCaseTest.php
└── TestResponseTest.php
├── Translating
├── TranslatorTest.php
└── translations
│ ├── cs
│ ├── base.php
│ └── foo.php
│ └── en
│ ├── base.php
│ └── foo.php
└── Validation
├── RulesTest.php
└── ValidationTest.php
/.github/workflows/phpstan.yml:
--------------------------------------------------------------------------------
1 | name: PHPStan static analysis
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 |
17 | - run: composer install
18 | - run: ./vendor/bin/phpstan
19 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Unit Tests
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 |
17 | - run: composer install
18 | - run: ./vendor/bin/phpunit
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /testing/
3 | **/*.sw*
4 | **/*.cache
5 | composer.lock
6 |
--------------------------------------------------------------------------------
/.php-cs-fixer.php:
--------------------------------------------------------------------------------
1 | in(__DIR__)
5 | ;
6 |
7 | $config = new PhpCsFixer\Config();
8 | return $config->setRules([
9 | '@PhpCsFixer' => true,
10 | 'declare_strict_types' => true
11 | ])
12 | ->setFinder($finder)
13 | ->setRiskyAllowed(true)
14 | ;
15 |
--------------------------------------------------------------------------------
/.phpactor.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "/home/majkel/.config/nvim/plugged/phpactor/phpactor.schema.json",
3 | "language_server_phpstan.enabled": false,
4 | "language_server_php_cs_fixer.enabled": false
5 | }
--------------------------------------------------------------------------------
/.phpactor.yml:
--------------------------------------------------------------------------------
1 | code_transform.class_new.variants:
2 | default: default
3 | interface: interface
4 | unit: phpunit_test
5 | zest: zest
6 |
--------------------------------------------------------------------------------
/.phpactor/templates/default/SourceCode.php.twig:
--------------------------------------------------------------------------------
1 | $aliases) {
12 | $result .= ' * @property-read '.$class.' $'.$class.PHP_EOL;
13 | foreach ($aliases as $alias) {
14 | $result .= ' * @property-read '.$class.' $'.$alias.PHP_EOL;
15 | }
16 | }
17 |
18 | echo $result;
19 |
--------------------------------------------------------------------------------
/devtools/lines.php:
--------------------------------------------------------------------------------
1 | getDocComment();
29 | preg_match('/@see\s+(\\\\.+)/', $doc, $matches);
30 | $target = $matches[1];
31 |
32 | $ref_target = new ReflectionClass($target);
33 |
34 | $target_filename = $ref_target->getFileName();
35 |
36 | $target_file = Filesystem::read($target_filename);
37 |
38 | $functions = [];
39 |
40 | preg_replace_callback('/\s*\/\*\*\n\s*\*\s(.+?)\n[\s\S]+?\*\/\n\s*public\s*function\s+(.*?\(.*?\))(?:\s*:\s*(.*?))\s/m', static function ($matches) use (&$functions, $target): void {
41 | $functions[] = ['self' === $matches[3] ? $target : $matches[3], $matches[2], $matches[1]];
42 | }, $target_file);
43 |
44 | $file = $ref->getFileName();
45 |
46 | Filesystem::write($file, preg_replace(
47 | '/\s*\*\s*@see.+/',
48 | PHP_EOL
49 | .implode(
50 | PHP_EOL,
51 | array_map($functions, static fn ($item) => ' * @method static '.implode(' ', $item))->content
52 | )
53 | .PHP_EOL
54 | .' *'
55 | .PHP_EOL
56 | .' * @see '
57 | .$target,
58 | Filesystem::read($file)
59 | ));
60 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 5
3 | paths:
4 | - src
5 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 | ./tests
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/Lemon/Cache.php:
--------------------------------------------------------------------------------
1 | 'storage.cache',
7 | ];
8 |
--------------------------------------------------------------------------------
/src/Lemon/Config.php:
--------------------------------------------------------------------------------
1 | $class
17 | *
18 | * @return ?T
19 | */
20 | public static function mapTo(array $data, string $class): ?object
21 | {
22 | $reflection = new \ReflectionClass($class);
23 | $params = [];
24 | foreach ($reflection->getConstructor()->getParameters() as $property) {
25 | $item = $data[$property->getName()] ?? null;
26 | $value = static::typeCheck($item, $property->getType());
27 | if ($value instanceof Nothing) {
28 | return null;
29 | }
30 |
31 | $params[$property->getName()] = $value->unwrap();
32 | }
33 |
34 | return new $class(...$params);
35 | }
36 |
37 | public static function typeCheck(mixed $value, ReflectionType $type): Maybe
38 | {
39 | $type_name = trim((string) $type, '?');
40 | if (class_exists($type_name)) {
41 | if ($type->allowsNull() && null === $value) {
42 | return Maybe::just(null);
43 | }
44 |
45 | if (!is_array($value)) {
46 | return Maybe::nothing();
47 | }
48 |
49 | return ($v = static::mapTo($value, $type_name)) === null ? Maybe::nothing() : Maybe::just($v);
50 | }
51 |
52 | if (!$type->allowsNull() && null === $value) {
53 | return Maybe::nothing();
54 | }
55 |
56 | $ok = @settype($value, $type_name);
57 |
58 | return $ok ? Maybe::just($value) : Maybe::nothing();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Lemon/Database/Database.php:
--------------------------------------------------------------------------------
1 | \Lemon\Database\Drivers\Sqlite::class,
17 | 'postgre' => \Lemon\Database\Drivers\Postre::class,
18 | 'mysql' => \Lemon\Database\Drivers\Mysql::class,
19 | ];
20 |
21 | public function __construct(
22 | public readonly Config $config
23 | ) {
24 | }
25 |
26 | /**
27 | * Returns current driver and creates new if isn't already.
28 | */
29 | public function getConnection(): Driver
30 | {
31 | if (!$this->connection) {
32 | $this->connect();
33 | }
34 |
35 | return $this->connection;
36 | }
37 |
38 | /**
39 | * Sends query to database.
40 | *
41 | * @phpstan-param literal-string $query
42 | */
43 | public function query(string $query, ...$params): \PDOStatement
44 | {
45 | $statement = $this->getConnection()->prepare($query);
46 | $statement->execute($params);
47 |
48 | return $statement;
49 | }
50 |
51 | /**
52 | * Creates new Driver and connects to database.
53 | */
54 | private function connect(): void
55 | {
56 | $driver = $this->drivers[$this->config->get('database.driver')];
57 |
58 | $this->connection = new $driver($this->config);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Lemon/Database/Drivers/Driver.php:
--------------------------------------------------------------------------------
1 | getConnection());
16 | }
17 |
18 | /**
19 | * Returns array of pdo construct arguments.
20 | */
21 | abstract protected function getConnection(): array;
22 | }
23 |
--------------------------------------------------------------------------------
/src/Lemon/Database/Drivers/Mysql.php:
--------------------------------------------------------------------------------
1 | config->get('database.'.$key);
17 |
18 | $result[] = [$key, $value];
19 | }
20 |
21 | return [
22 | 'mysql:'
23 | .implode(
24 | ';',
25 | array_map(
26 | fn ($item) => $item[0].'='.$item[1],
27 | array_slice($result, 0, -2)
28 | )
29 | ),
30 | $result[5][1],
31 | $result[6][1],
32 | ];
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Lemon/Database/Drivers/Postre.php:
--------------------------------------------------------------------------------
1 | config->get('database'.$key);
17 |
18 | $result .= $key.'='.$value.';';
19 | }
20 |
21 | return [$result];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Lemon/Database/Drivers/Sqlite.php:
--------------------------------------------------------------------------------
1 | config->get('database.file');
15 |
16 | return ['sqlite:'.$file];
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Lemon/Database/README.md:
--------------------------------------------------------------------------------
1 | # Lemon Database
2 |
3 | Simple database layer for lemon.
4 |
5 | ## Plans
6 |
7 | - [ ] explorer
8 | - [ ] orm?
9 |
10 |
--------------------------------------------------------------------------------
/src/Lemon/Database/config.php:
--------------------------------------------------------------------------------
1 | Env::get('DB_DRIVER'),
9 |
10 | // --- Sqlite specific ---
11 | 'file' => Env::file('DB_FILE', 'sqlite', 'database'),
12 |
13 | 'host' => Env::get('DB_HOST'),
14 | 'port' => Env::get('DB_PORT'),
15 | 'dbname' => Env::get('DB_NAME'),
16 | 'user' => Env::get('DB_USER'),
17 | 'password' => Env::get('DB_PASSWORD'),
18 |
19 | // --- Postgre specific ---
20 | 'sslmode' => Env::get('DB_SSLMODE', 'prefer'),
21 |
22 | // --- Mysql specific ---
23 | 'unix_socket' => Env::get('DB_UNIX_SOCKET', ''),
24 | 'charset' => Env::get('DB_CHARSET', 'utf8mb4'),
25 | ];
26 |
--------------------------------------------------------------------------------
/src/Lemon/Debug.php:
--------------------------------------------------------------------------------
1 | application->runsInTerminal()) {
28 | (new TerminalReporter($problem, $this->application))->report();
29 | }
30 |
31 | if ($this->config->get('debug.debug')) {
32 | $request = $this->application->has('request') ? $this->application->get('request') : null;
33 | (new Reporter($problem, $request, $this->application))->report();
34 | } else {
35 | $this->response->error(500)->send($this->application);
36 | $this->logger->error((string) $problem);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Lemon/Debug/Handling/README.md:
--------------------------------------------------------------------------------
1 | # Lemon Error handler
2 |
3 | This part contains error debug panel similar to spatie/laravel-ignition.
4 |
5 | Debug panel is based on vue frontend along with Consultatnt which is class that shows hits based on error message.
6 |
7 |
8 | ## Adding hints
9 |
10 | Start by defining hint in Consultant::$signatures like so:
11 |
12 | ```php
13 | 'Call to undefined function (\w+?)\(\)' => 'function'
14 | ```
15 |
16 | Where key is regex and value name of handling method.
17 |
18 | Handling method should be named in format `handleTarget` where Target is the same as value in $signatures.
19 |
20 | The method should return array where all keys are possible hits, lower the index - higher the priority.
21 |
--------------------------------------------------------------------------------
/src/Lemon/Debug/Handling/templates/reporter.phtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Exception | Lemon
6 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
= $problem ?> - = $file ?>:= $line ?>
29 |
= $message ?>
30 |
= $hint[0] ?? '' ?>
31 |
32 |
33 |
34 |
35 |
36 |
37 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/Lemon/Debug/README.md:
--------------------------------------------------------------------------------
1 | # Lemon debugger
2 |
3 | Debugging tools for lemon.
4 |
--------------------------------------------------------------------------------
/src/Lemon/Debug/Style.php:
--------------------------------------------------------------------------------
1 | {$matches[1]};
31 | }, file_get_contents(__DIR__.'/stubs/style.html.stub')); // TODO stub generator, caching?
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Lemon/Debug/config.php:
--------------------------------------------------------------------------------
1 | [
9 | 'style' => new Style(),
10 | ],
11 | 'debug' => false,
12 | ];
13 |
--------------------------------------------------------------------------------
/src/Lemon/Debug/stubs/style.html.stub:
--------------------------------------------------------------------------------
1 |
53 |
--------------------------------------------------------------------------------
/src/Lemon/Env.php:
--------------------------------------------------------------------------------
1 | events[$name][] = $action;
25 |
26 | return $this;
27 | }
28 |
29 | /**
30 | * Registers event handling function.
31 | * Alias for self::on().
32 | */
33 | public function when(string $name, callable $action): static
34 | {
35 | return $this->on($name, $action);
36 | }
37 |
38 | /**
39 | * Calls all event handling functions with given arguments.
40 | */
41 | public function fire(string $name, mixed ...$args): static
42 | {
43 | foreach ($this->events[$name] as $event) {
44 | $this->application->call($event, $args);
45 | }
46 |
47 | return $this;
48 | }
49 |
50 | /**
51 | * Returns all events.
52 | */
53 | public function events(): array
54 | {
55 | return $this->events;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Lemon/Events/README.md:
--------------------------------------------------------------------------------
1 | # Lemon Events
2 |
3 | Simple event dispatcher for Lemon.
4 |
--------------------------------------------------------------------------------
/src/Lemon/Highlighter.php:
--------------------------------------------------------------------------------
1 | 'style="color: #689d6a"',
9 | Highlighter::Statement => 'style="color: #cc241d"',
10 | Highlighter::Number => 'style="color: #b16286"',
11 | Highlighter::String => 'style="color: #98971a"',
12 | Highlighter::Type => 'style="color: #d79921"',
13 | Highlighter::Comment => 'style="color: ##28374"',
14 | Highlighter::Variable => 'style="color: #458588"',
15 | Highlighter::Default => 'style="color: #ebdbb2"',
16 | ];
17 |
--------------------------------------------------------------------------------
/src/Lemon/Http/CookieJar.php:
--------------------------------------------------------------------------------
1 | request->getCookie($name);
21 | }
22 |
23 | public function set(string $name, string $value, int $expires = 0, string $samesite = 'None'): static
24 | {
25 | $this->set_cookies[] = [[$name, $value], ['expires' => $expires, 'SameSite' => $samesite]];
26 |
27 | return $this;
28 | }
29 |
30 | public function delete(string $name): static
31 | {
32 | if (!$this->request->hasCookie($name)) {
33 | return $this;
34 | }
35 | $this->set_cookies[] = [$name, '', -1];
36 |
37 | return $this;
38 | }
39 |
40 | public function has(string $name): bool
41 | {
42 | return $this->request->hasCookie($name);
43 | }
44 |
45 | public function cookies(): array
46 | {
47 | return $this->set_cookies;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Lemon/Http/Exceptions/CookieException.php:
--------------------------------------------------------------------------------
1 | tmp_path);
21 | }
22 |
23 | public function copy(string $new): static
24 | {
25 | move_uploaded_file($this->tmp_path, $new);
26 |
27 | return $this;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Lemon/Http/Middlewares/TrimStrings.php:
--------------------------------------------------------------------------------
1 | replace(array_map(trim(...), $request->data()));
14 | $request->replaceQuery(array_map(trim(...), $request->query() ?? []));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Lemon/Http/README.md:
--------------------------------------------------------------------------------
1 | # Lemon Http
2 |
3 | Http abstraction for Lemon.
4 |
5 |
--------------------------------------------------------------------------------
/src/Lemon/Http/Responses/CustomResponse.php:
--------------------------------------------------------------------------------
1 | body;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Lemon/Http/Responses/EmptyResponse.php:
--------------------------------------------------------------------------------
1 | header('Content-Type', 'text/html');
17 |
18 | return $this->body;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Lemon/Http/Responses/JsonResponse.php:
--------------------------------------------------------------------------------
1 | header('Content-Type', 'application/json');
17 |
18 | return json_encode($this->body);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Lemon/Http/Responses/RedirectResponse.php:
--------------------------------------------------------------------------------
1 | code(302);
17 |
18 | return '';
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Lemon/Http/Responses/TemplateResponse.php:
--------------------------------------------------------------------------------
1 | header('Content-Type', 'text/html');
17 |
18 | return (string) $this->body;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Lemon/Http/Responses/TextResponse.php:
--------------------------------------------------------------------------------
1 | header('Content-Type', 'text/plain');
17 |
18 | return $this->body;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Lemon/Http/config.php:
--------------------------------------------------------------------------------
1 | [
7 | ],
8 | 'session' => [
9 | 'name' => 'PHP_SESSION',
10 | ],
11 | ];
12 |
--------------------------------------------------------------------------------
/src/Lemon/Http/templates/error.phtml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/Lemon/Juice.php:
--------------------------------------------------------------------------------
1 | foo = $foo;
28 | }
29 |
30 | public function sayHi(string $name): void
31 | {
32 | echo $this->foo->getGreeting() . ' ' . $name;
33 | }
34 | }
35 |
36 | $container = new \Lemon\Kernel\Container();
37 | $container->addService(Foo::class);
38 | $container->addService(Bar::class);
39 |
40 | $container->getService(Bar::class)->sayHi('Majkel');
41 | ```
42 |
43 | If you try to get unknown service, you'l get Exception
44 |
45 | ## Application
46 |
47 | Application is main part of Lemon. It allows container-based service management, bootstraping, initialization, filesystem etc.
48 |
--------------------------------------------------------------------------------
/src/Lemon/Kernel/templates/help.phtml:
--------------------------------------------------------------------------------
1 | Help
2 |
3 | $commands */
5 | foreach ($commands as $command) {
6 | ?>
7 |
8 | = $command->name ?>
- = $command->description?>
9 |
10 |
--------------------------------------------------------------------------------
/src/Lemon/Kernel/templates/maintenance.php:
--------------------------------------------------------------------------------
1 | 503
9 | Service Unavailable
10 | HTML;
11 |
--------------------------------------------------------------------------------
/src/Lemon/Log.php:
--------------------------------------------------------------------------------
1 | resolveDestination();
22 | }
23 |
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function log($level, string|\Stringable $message, array $context = []): void
28 | {
29 | $level = strtoupper($level);
30 | if (!defined(LogLevel::class.'::'.$level)) {
31 | throw new InvalidArgumentException('Log level '.$level.' is not valid');
32 | }
33 | $message = $this->interpolate((string) $message, $context);
34 |
35 | $now = (new \DateTime())->format('D M d h:i:s Y');
36 | file_put_contents($this->destination, sprintf("[%s] %s: %s\n", $now, $level, $message), FILE_APPEND);
37 | }
38 |
39 | /**
40 | * Compiles message with given context.
41 | */
42 | public function interpolate(string $message, array $context): string
43 | {
44 | foreach ($context as $key => $value) {
45 | $message = str_replace('{'.$key.'}', (string) $value, $message);
46 | }
47 |
48 | return $message;
49 | }
50 |
51 | /**
52 | * Resolves logging destination.
53 | */
54 | private function resolveDestination()
55 | {
56 | $this->destination = $this->config->file('logging.file', 'log');
57 | if (!Filesystem::isFile($this->destination)) {
58 | $dir = Filesystem::parent($this->destination);
59 | if (!is_dir($dir)) {
60 | Filesystem::makeDir($dir);
61 | }
62 | Filesystem::write(Filesystem::join($dir, '.gitignore'), "*\n!.gitignore");
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Lemon/Logging/README.md:
--------------------------------------------------------------------------------
1 | # Lemon Logging
2 |
3 | Simple api for logging.
4 |
--------------------------------------------------------------------------------
/src/Lemon/Logging/config.php:
--------------------------------------------------------------------------------
1 | 'storage.logs.lemon',
7 | ];
8 |
--------------------------------------------------------------------------------
/src/Lemon/Protection/Csrf.php:
--------------------------------------------------------------------------------
1 | token) {
20 | $this->token = Str::random(32);
21 | }
22 |
23 | return $this->token;
24 | }
25 |
26 | /**
27 | * Returns whenever token is created.
28 | */
29 | public function created(): bool
30 | {
31 | return !is_null($this->token);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Lemon/Protection/Middlwares/Csrf.php:
--------------------------------------------------------------------------------
1 | method) {
17 | $cookie = $request->getCookie('CSRF_TOKEN');
18 | if ($cookie !== $request->get('CSRF_TOKEN') || null === $cookie) {
19 | return $responseFactory->error(400);
20 | }
21 | }
22 |
23 | $cookies->set('CSRF_TOKEN', $csrf->getToken(), 0, 'Strict');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Lemon/Protection/README.md:
--------------------------------------------------------------------------------
1 | # Lemon Protection
2 |
3 | Protection utilities.
4 |
--------------------------------------------------------------------------------
/src/Lemon/ResponseFactory.php:
--------------------------------------------------------------------------------
1 | is_array($item)
18 | ? [new $item[0](), $item[1]]
19 | : [new $item(), 'handle'],
20 | $this->middlewares
21 | );
22 | }
23 |
24 | /**
25 | * Adds middleware.
26 | */
27 | public function add(string|array $name): static
28 | {
29 | $this->middlewares[] = $name;
30 |
31 | return $this;
32 | }
33 |
34 | /**
35 | * Returns all middlewares.
36 | */
37 | public function middlewares(): array
38 | {
39 | return $this->middlewares;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Lemon/Routing/README.md:
--------------------------------------------------------------------------------
1 | # Lemon Routing
2 |
3 | Regex-based router which skips routes depending on prefix.
4 |
--------------------------------------------------------------------------------
/src/Lemon/Session.php:
--------------------------------------------------------------------------------
1 | $cary.ucfirst($item),
39 | ''
40 | );
41 | }
42 |
43 | public static function toCamel(string $target): string
44 | {
45 | $array = static::toArray($target);
46 |
47 | return array_reduce(
48 | array_slice($array, 1),
49 | fn ($cary, $item) => $cary.ucfirst($item),
50 | $array[0]
51 | );
52 | }
53 |
54 | public static function toSnake(string $target): string
55 | {
56 | $array = static::toArray($target);
57 |
58 | return implode('_', $array);
59 | }
60 |
61 | public static function toKebab(string $target): string
62 | {
63 | $array = static::toArray($target);
64 |
65 | return implode('-', $array);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Lemon/Support/Exceptions/FilesystemException.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | private array $macros = [];
15 |
16 | public function __call($name, $arguments)
17 | {
18 | if (!isset($this->macros[$name])) {
19 | throw new \Exception('Call to undefined function '.$name);
20 | }
21 |
22 | $macro = $this->macros[$name];
23 | if ($macro instanceof \Closure) {
24 | $macro = $macro->bindTo($this);
25 | }
26 |
27 | return $macro(...$arguments);
28 | }
29 |
30 | /**
31 | * Adds new macro.
32 | */
33 | public function macro(string $name, callable $action)
34 | {
35 | $this->macros[$name] = $action;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Lemon/Support/Pipe.php:
--------------------------------------------------------------------------------
1 | >>=' !== $name) { // haha
19 | throw new \Exception('Call to undefined method '.static::class.'::'.$name.'()');
20 | }
21 |
22 | return $this->then(...$arguments);
23 | }
24 |
25 | /**
26 | * Creates new instance with given value.
27 | */
28 | public static function send(mixed $value): self
29 | {
30 | return new self($value);
31 | }
32 |
33 | /**
34 | * Sets argument which will be autimaticaly passed in every callback.
35 | */
36 | public function with(mixed $value): static
37 | {
38 | $this->args[] = $value;
39 |
40 | return $this;
41 | }
42 |
43 | /**
44 | * Calls function with curent value.
45 | *
46 | * @see https://www.youtube.com/watch?v=oqwzuiSy9y0
47 | */
48 | public function then(callable $action): static
49 | {
50 | $this->value = $action($this->value, ...$this->args);
51 |
52 | return $this;
53 | }
54 |
55 | /**
56 | * Returns curent value.
57 | */
58 | public function return(): mixed
59 | {
60 | return $this->value;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Lemon/Support/Properties/Properties.php:
--------------------------------------------------------------------------------
1 | getProperty($name);
14 | if ($property->getAttributes(Read::class)) {
15 | return $this->{$name};
16 | }
17 | }
18 |
19 | throw new \Exception('Undefined property '.$name);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Lemon/Support/Properties/Read.php:
--------------------------------------------------------------------------------
1 | $value) {
51 | $array[$key] = $callback($value, $key);
52 | }
53 |
54 | return $array;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Lemon/Support/Types/Just.php:
--------------------------------------------------------------------------------
1 | data;
17 | }
18 |
19 | public function expect(string $error): mixed
20 | {
21 | return $this->data;
22 | }
23 |
24 | public function then(callable $action): static
25 | {
26 | $this->data = $action($this->data);
27 |
28 | return $this;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Lemon/Support/Types/Maybe.php:
--------------------------------------------------------------------------------
1 | before as $action) {
21 | $action();
22 | }
23 |
24 | $result = $this->object->{$name}(...$arguments);
25 |
26 | foreach ($this->after as $action) {
27 | $action($result);
28 | }
29 |
30 | return $result;
31 | }
32 |
33 | public function __get($name)
34 | {
35 | return $this->object->{$name};
36 | }
37 |
38 | public function after(callable $action): static
39 | {
40 | $this->after[] = $action;
41 |
42 | return $this;
43 | }
44 |
45 | public function before(callable $action): static
46 | {
47 | $this->before[] = $action;
48 |
49 | return $this;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Lemon/Support/Types/Stack.php:
--------------------------------------------------------------------------------
1 | type, $value)) {
30 | throw new \InvalidArgumentException('Argument 1 of function '.static::class.'::push() must be type '.$this->type.' '.gettype($value).' given');
31 | }
32 | $this->storage[] = $value;
33 |
34 | return $this;
35 | }
36 |
37 | /**
38 | * Pops item from the stack.
39 | */
40 | public function pop(): mixed
41 | {
42 | return array_pop($this->storage);
43 | }
44 |
45 | /**
46 | * Returns top of the stack.
47 | */
48 | public function top(): mixed
49 | {
50 | return $this->storage[count($this->storage) - 1];
51 | }
52 |
53 | /**
54 | * Returns size of the stack.
55 | */
56 | public function size(): int
57 | {
58 | return count($this->storage);
59 | }
60 |
61 | /**
62 | * Returns stack as array.
63 | */
64 | public function storage(): array
65 | {
66 | return $this->storage;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Lemon/Support/Types/Str.php:
--------------------------------------------------------------------------------
1 | value\)\?/str_replace(\3, \4, \2).
10 | */
11 | class Str
12 | {
13 | public static function random(int $size): string
14 | {
15 | $chars = str_split(str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'));
16 |
17 | return array_reduce(array_rand($chars, $size), fn ($carry, $item) => $carry.$chars[$item]);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Lemon/Support/Types/Type.php:
--------------------------------------------------------------------------------
1 | message = $message;
12 | $this->line = $line ?? $this->line;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Exceptions/SyntaxException.php:
--------------------------------------------------------------------------------
1 | getMessage(), $original->getCode(), 1, $source, $original->getLine());
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compiler.php:
--------------------------------------------------------------------------------
1 | lexer = new Lexer($config->get('templating.juice.syntax'));
26 | $this->output = new OutputCompiler();
27 | $this->directives = new DirectiveCompiler();
28 | }
29 |
30 | /**
31 | * {@inheritdoc}
32 | */
33 | public function compile(string $template): string
34 | {
35 | $lex = $this->lexer->lex(
36 | str_replace("\r\n", "\n", $template)
37 | );
38 | $parser = new Parser($lex, $this->output, $this->directives);
39 |
40 | return $parser->parse();
41 | }
42 |
43 | /**
44 | * {@inheritdoc}
45 | */
46 | public function getExtension(): string
47 | {
48 | return 'juice';
49 | }
50 |
51 | /**
52 | * Adds directive compiler class.
53 | */
54 | public function addDirectiveCompiler(string $name, string $class): static
55 | {
56 | $this->directives->addDirectiveCompiler($name, $class);
57 |
58 | return $this;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/CaseDirective.php:
--------------------------------------------------------------------------------
1 | line);
17 | }
18 |
19 | if ('' === $token->content[1]) {
20 | throw new CompilerException('Directive case expects arguments', $token->line); // TODO
21 | }
22 |
23 | return 'content[1].': ?>';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/CsrfDirective.php:
--------------------------------------------------------------------------------
1 | content[1]) {
15 | throw new CompilerException('Directive csrf takes 0 arguments', $token->line);
16 | }
17 |
18 | return '';
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/Directive.php:
--------------------------------------------------------------------------------
1 | line);
17 | }
18 |
19 | if ('' !== $token->content[1]) {
20 | throw new CompilerException('Directive else takes 0 arguments', $token->line);
21 | }
22 |
23 | return '';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/ElseIfDirective.php:
--------------------------------------------------------------------------------
1 | line);
17 | }
18 |
19 | if ('' === $token->content[1]) {
20 | throw new CompilerException('Directive elseif takes arguments', $token->line);
21 | }
22 |
23 | return 'content[1].'): ?>';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/ErrorDirective.php:
--------------------------------------------------------------------------------
1 | ';
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/ForDirective.php:
--------------------------------------------------------------------------------
1 | content[1]) {
16 | throw new CompilerException('Directive for expects arguments', $token->line);
17 | }
18 |
19 | return 'content[1].'): ?>';
20 | }
21 |
22 | public function compileClosing(): string
23 | {
24 | return '';
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/ForeachDirective.php:
--------------------------------------------------------------------------------
1 | content[1]) {
16 | throw new CompilerException('Directive foreach expects arguments', $token->line);
17 | }
18 |
19 | return 'content[1].'): ?>';
20 | }
21 |
22 | public function compileClosing(): string
23 | {
24 | return '';
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/IfDirective.php:
--------------------------------------------------------------------------------
1 | content[1]) {
15 | throw new CompilerException('Directive if expects arguments', $token->line);
16 | }
17 |
18 | return 'content[1].'): ?>';
19 | }
20 |
21 | public function compileClosing(): string
22 | {
23 | return '';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/IfErrorDirective.php:
--------------------------------------------------------------------------------
1 | ';
14 | }
15 |
16 | public function compileClosing(): string
17 | {
18 | return '';
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/IncludeDirective.php:
--------------------------------------------------------------------------------
1 | content[1]); // TODO better manipulation
15 | if (2 != count($tokens)) {
16 | throw new CompilerException('Directive include takes exactly 1 argument', $token->line);
17 | }
18 | if (T_CONSTANT_ENCAPSED_STRING != $tokens[1][0]) {
19 | throw new CompilerException('Argument 1 of directive include has to be string', $token->line);
20 | }
21 |
22 | return 'make('.$tokens[1][1].')->compiled_path ?>';
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/Layout/BlockDirective.php:
--------------------------------------------------------------------------------
1 | content[1]); // TODO better manipulation
16 | if (2 != count($tokens)) {
17 | throw new CompilerException('Directive block takes exactly 1 argument', $token->line);
18 | }
19 | if (T_CONSTANT_ENCAPSED_STRING != $tokens[1][0]) {
20 | throw new CompilerException('Argument 1 of directive block has to be string', $token->line);
21 | }
22 |
23 | return 'startBlock(\''.substr($tokens[1][1], 1, -1).'\') ?>';
24 | }
25 |
26 | public function compileClosing()
27 | {
28 | return 'endBlock() ?>';
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/Layout/ExtendsDirective.php:
--------------------------------------------------------------------------------
1 | content[1]); // TODO better manipulation
16 | if (2 != count($tokens)) {
17 | throw new CompilerException('Directive extends takes exactly 1 argument', $token->line);
18 | }
19 | if (T_CONSTANT_ENCAPSED_STRING != $tokens[1][0]) {
20 | throw new CompilerException('Argument 1 of directive extends has to be string', $token->line);
21 | }
22 |
23 | $class = Layout::class;
24 |
25 | return 'make('.$tokens[1][1].')->compiled_path, $data) ?>';
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/Layout/Layout.php:
--------------------------------------------------------------------------------
1 | last) {
26 | $data = $this->data;
27 | extract($data);
28 | include $this->file;
29 | }
30 | }
31 |
32 | public function startBlock(string $name)
33 | {
34 | if ($this->last) {
35 | throw new TemplateException('Unable to create block, because one is already openned');
36 | }
37 | $this->blocks[$name] = null;
38 | $this->last = $name;
39 | ob_start();
40 | }
41 |
42 | public function endBlock()
43 | {
44 | if (is_null($this->last)) {
45 | throw new TemplateException('Unable to close block, because there is no opened block');
46 | }
47 | $this->blocks[$this->last] = ob_get_clean();
48 | $this->last = null;
49 | }
50 |
51 | public function yield(string $name)
52 | {
53 | if (!isset($this->blocks[$name])) {
54 | throw new TemplateException('Undefined block '.$name);
55 | }
56 | echo trim($this->blocks[$name]);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/Layout/YieldDirective.php:
--------------------------------------------------------------------------------
1 | content[1]); // TODO better manipulation
16 | if (2 != count($tokens)) {
17 | throw new CompilerException('Directive block takes exactly 1 argument', $token->line);
18 | }
19 | if (T_CONSTANT_ENCAPSED_STRING != $tokens[1][0]) {
20 | throw new CompilerException('Argument 1 of directive block has to be string', $token->line);
21 | }
22 |
23 | return 'yield(\''.substr($tokens[1][1], 1, -1).'\') ?>';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/SwitchDirective.php:
--------------------------------------------------------------------------------
1 | content[1]) {
15 | throw new CompilerException('Directive switch expects arguments', $token->line);
16 | }
17 |
18 | return 'content[1].'): ?>';
19 | }
20 |
21 | public function compileClosing(): string
22 | {
23 | return '';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/TranslationDirective.php:
--------------------------------------------------------------------------------
1 | content[1]) {
15 | throw new CompilerException('Directive text expects one argument');
16 | }
17 |
18 | return 'content[1], '\'"').'\') ?>';
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/UnlessDirective.php:
--------------------------------------------------------------------------------
1 | content[1]) {
15 | throw new CompilerException('Directive unless expects arguments', $token->line);
16 | }
17 |
18 | return 'content[1].')): ?>';
19 | }
20 |
21 | public function compileClosing(): string
22 | {
23 | return '';
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/Directives/WhileDirective.php:
--------------------------------------------------------------------------------
1 | content[1]) {
16 | throw new CompilerException('Directive while expects arguments', $token->line);
17 | }
18 |
19 | return 'content[1].'): ?>';
20 | }
21 |
22 | public function compileClosing(): string
23 | {
24 | return '';
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Compilers/OutputCompiler.php:
--------------------------------------------------------------------------------
1 | '.match ($context) {
21 | Parser::CONTEXT_HTML => 'escapeHtml',
22 | Parser::CONTEXT_ATTRIBUTE => 'escapeAttribute',
23 | Parser::CONTEXT_JS, Parser::CONTEXT_JS_ATTRIBUTE => 'escapeScript',
24 | default => throw new CompilerException('Unknown context')
25 | };
26 |
27 | return 'resolvePipes($content).') ?>';
28 | }
29 |
30 | /**
31 | * Compiles unescaped output tag.
32 | */
33 | public function compileUnescaped(string $content): string
34 | {
35 | return 'resolvePipes($content).' ?>';
36 | }
37 |
38 | /**
39 | * Resolves elixir-like pipes.
40 | */
41 | private function resolvePipes(string $content): string
42 | {
43 | // TODO tokenizer?
44 | $parts = explode('|>', $content);
45 | if (count($parts) < 2) {
46 | return $content;
47 | }
48 |
49 | $result = trim($parts[0]);
50 |
51 | foreach (array_slice($parts, 1) as $part) {
52 | $part = trim($part);
53 | $result = "\$_env->{$part}({$result})";
54 | }
55 |
56 | return $result;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/README.md:
--------------------------------------------------------------------------------
1 | # Juice
2 |
3 | Juice is Lemons template engine that compiles to raw php code. It does that by lexing into tokens via regular expressions and then parsing.
4 |
5 | Its heavily influenced by [nette/latte](https://github.com/nette/latte)
6 |
7 | Default syntax are:
8 | - `{{ $variable }}` - variable $variable will be safely echoed
9 | - `{! $variable !}` - variable $variable will be unsafely echoed
10 | - `{ tag args }` - this will replace into one of juices supported tag
11 | - `{# anything#}` - this wont be included in render - comment
12 |
13 | You can modify those syntax in config:
14 |
15 | - Each regex mustn't contain starting and ending deliminers
16 | - Tag regex must have one match for tag name and second optional match for tag arguments
17 | - End regex mustn't have tag logic, only regex that describes end tag. It has to have 1 match that is tag that is ended
18 | - Echo and unescaped echo tags must have 1 match for content
19 |
20 | Custom syntax are very risky so be aware.
21 |
22 | ## Plans
23 |
24 | - [x] lexer
25 | - [x] each tag compiler is separate class that takes token and returns php code
26 | - [x] context-based rendering like in Latte https://stackoverflow.com/questions/2725156/complete-list-of-html-tag-attributes-which-have-a-url-value, `on.+='|"`, `src|href|...=`
27 | - [x] syntax bundle of blade and twig (For blade `@([^\(]+)(?(?=\()\((.+?)\))` this is what my mind made in 23:15 CET i will regret this tommorow cause i have no idea how it works but it works lets goooooo)
28 | - [x] syntax highlight for vim (emacs?)
29 | - [x] ability of custom tags
30 | - [x] metaframework
31 | - [x] elixir-like piping?
32 | - [ ] safer syntax check
33 | - [ ] syntax for actual {}
34 | - [x] includes, extends,...
35 | - [x] line counting
36 | - [ ] rewrite lexer
37 | - [ ] migrate to stack
38 |
39 | ## Contributing
40 |
41 | You can add more directives.
42 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Juice/Token.php:
--------------------------------------------------------------------------------
1 | |string $content
15 | * @property int $line
16 | */
17 | class Token
18 | {
19 | use Properties;
20 |
21 | public const TAG = 0;
22 | public const TAG_END = 1;
23 | public const OUTPUT = 2;
24 | public const UNESCAPED = 3;
25 | public const TEXT = 4;
26 |
27 | public function __construct(
28 | #[Read]
29 | private int $kind,
30 | #[Read]
31 | private string|array $content,
32 | #[Read]
33 | private int $line,
34 | ) {
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/Template.php:
--------------------------------------------------------------------------------
1 | render();
24 | }
25 |
26 | public function with(...$data): static
27 | {
28 | $this->data = [...$this->data, ...$data];
29 |
30 | return $this;
31 | }
32 |
33 | public function data(): array
34 | {
35 | return $this->data;
36 | }
37 |
38 | public function render(): string
39 | {
40 | ob_start();
41 |
42 | try {
43 | (function ($data) {
44 | extract($data);
45 |
46 | require $this->compiled_path;
47 | })($this->data);
48 | } catch (\Throwable $e) {
49 | ob_get_clean();
50 |
51 | throw TemplateException::from($e, $this->raw_path);
52 | }
53 |
54 | return ob_get_clean();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Lemon/Templating/config.php:
--------------------------------------------------------------------------------
1 | 'storage.templates',
9 | 'location' => 'templates',
10 | 'juice' => [
11 | 'syntax' => new Syntax(),
12 | ],
13 | ];
14 |
--------------------------------------------------------------------------------
/src/Lemon/Terminal.php:
--------------------------------------------------------------------------------
1 | name, $this->parameters] = $this->resolveSignature($signature);
23 | }
24 |
25 | private function resolveSignature(string $signature): array
26 | {
27 | $signature = explode(' ', $signature);
28 | $name = $signature[0];
29 | $result = [];
30 | foreach (array_slice($signature, 1) as $argument) {
31 | if (preg_match('/^{([a-zA-Z0-9]+)}$/', $argument, $matches)) {
32 | $result[] = ['obligated', $matches[1]];
33 | } elseif (preg_match('/^{([a-zA-Z0-9]+)\?}$/', $argument, $matches)) {
34 | $result[] = ['optional', $matches[1]];
35 | } else {
36 | throw new CommandException('Signature of command '.$name.' contains invalid argument patern: '.$argument);
37 | }
38 | }
39 |
40 | return [$name, $result];
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Lemon/Terminal/Commands/Dispatcher.php:
--------------------------------------------------------------------------------
1 | */
10 | private array $commands = [];
11 |
12 | /**
13 | * Adds command.
14 | */
15 | public function add(Command $command): static
16 | {
17 | $this->commands[$command->name] = $command;
18 |
19 | return $this;
20 | }
21 |
22 | /**
23 | * Returns command mathing given arguments or error.
24 | */
25 | public function dispatch(array $arguments): array|string
26 | {
27 | if (empty($arguments)) {
28 | return 'No command provided';
29 | }
30 |
31 | $name = $arguments[0];
32 | $arguments = array_slice($arguments, 1);
33 |
34 | if (!isset($this->commands[$name])) {
35 | return 'Command '.$name.' was not found.'; // Todo hints 1000iq
36 | }
37 |
38 | $command = $this->commands[$name];
39 |
40 | $arguments = $this->parseArguments($arguments);
41 |
42 | $result = [];
43 | foreach ($command->parameters as $parameter) {
44 | if (isset($arguments[$parameter[1]])) {
45 | $result[$parameter[1]] = $arguments[$parameter[1]];
46 | } elseif ('optional' != $parameter[0]) {
47 | return 'Argument '.$parameter[1].' is missing.';
48 | }
49 | }
50 |
51 | return [$command->action, $result];
52 | }
53 |
54 | /**
55 | * Returns all commands.
56 | *
57 | * @return array<\Lemon\Terminal\Commands\Command>
58 | */
59 | public function commands(): array
60 | {
61 | return $this->commands;
62 | }
63 |
64 | private function parseArguments(array $arguments): array
65 | {
66 | $result = [];
67 | foreach ($arguments as $argument) {
68 | [$name, $value] = explode('=', $argument);
69 | $result[$name] = $value;
70 | }
71 |
72 | return $result;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Lemon/Terminal/Exceptions/CommandException.php:
--------------------------------------------------------------------------------
1 | components = new Components();
14 | }
15 |
16 | public function compile(string $content): string
17 | {
18 | return $this->components->parse($this->getDom($content));
19 | }
20 |
21 | private function getDom(string $content): \DOMNode
22 | {
23 | $dom = new \DOMDocument();
24 | $dom->loadHTML($content);
25 |
26 | return $dom->getElementsByTagName('body')[0];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Lemon/Terminal/IO/Html/README.md:
--------------------------------------------------------------------------------
1 | # Lemon Html Terminal
2 |
3 | Basic component that compiles html into terminal-ready text.
4 |
5 | Heavilly inspired by [nunomaduro/termwind](https://github.com/nunomaduro/termwind)
6 |
--------------------------------------------------------------------------------
/src/Lemon/Terminal/IO/Html/Styles.php:
--------------------------------------------------------------------------------
1 | ['%e30m', '', ''],
16 | 'text-red' => ['%e31m', '', ''],
17 | 'text-green' => ['%e32m', '', ''],
18 | 'text-yellow' => ['%e33m', '', ''],
19 | 'text-blue' => ['%e34m', '', ''],
20 | 'text-magenta' => ['%e35m', '', ''],
21 | 'text-cyan' => ['%e36m', '', ''],
22 | 'text-white' => ['%e37m', '', ''],
23 | 'bg-black' => ['%e40m', '', ''],
24 | 'bg-red' => ['%e41m', '', ''],
25 | 'bg-green' => ['%e42m', '', ''],
26 | 'bg-yellow' => ['%e43m', '', ''],
27 | 'bg-blue' => ['%e44m', '', ''],
28 | 'bg-magenta' => ['%e45m', '', ''],
29 | 'bg-cyan' => ['%e46m', '', ''],
30 | 'bg-white' => ['%e47m', '', ''],
31 | ];
32 |
33 | public function getStyle(\DOMNode $node): array
34 | {
35 | if (!isset($node->attributes['class'])) {
36 | return ['', '', ''];
37 | }
38 |
39 | $result = ['', '', ''];
40 | foreach (explode(' ', $node->attributes['class']->nodeValue) as $class) {
41 | if (!isset($this->styles[$class])) {
42 | throw new HtmlException('Class '.$class.' does not exist');
43 | }
44 | $style = $this->styles[$class];
45 |
46 | $style = str_replace('%e', "\033[", $style);
47 |
48 | foreach ($style as $index => $part) {
49 | $result[$index] .= $part;
50 | }
51 | }
52 |
53 | return $result;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Lemon/Terminal/IO/Output.php:
--------------------------------------------------------------------------------
1 | html = new HtmlOutput();
18 | }
19 |
20 | public function out(mixed $content): string
21 | {
22 | if ($content instanceof Template) {
23 | return $this->out($content->render());
24 | }
25 |
26 | if (is_string($content)) {
27 | if (strip_tags($content) != $content) {
28 | return $this->html->compile($content);
29 | }
30 | }
31 |
32 | if (is_scalar($content)) {
33 | return (string) $content;
34 | }
35 |
36 | $type = gettype($content);
37 |
38 | throw new IOException('Value of type '.('object' == $type ? get_class($content) : $type).' could not be outputed');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Lemon/Terminal/README.md:
--------------------------------------------------------------------------------
1 | # Lemon Terminal
2 |
3 | Command-line interface library supoorting commands and html output.
4 |
5 | Inspired by [nunomaduro/termwind](https://github.com/nunomaduro/termwind)
6 |
7 | ## Contributing
8 |
9 | You can add more components and styles.
10 |
--------------------------------------------------------------------------------
/src/Lemon/Testing/Mock.php:
--------------------------------------------------------------------------------
1 | mock = \Mockery::mock($class);
17 | }
18 |
19 | public function expect(callable ...$methods): MockInterface
20 | {
21 | foreach ($methods as $method => $action) {
22 | // @phpstan-ignore-next-line
23 | $this->mock->shouldReceive($method)
24 | ->andReturnUsing(\Closure::fromCallable($action)->bindTo($this->mock))
25 | ;
26 | }
27 |
28 | return $this->mock;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Lemon/Testing/SessionMock.php:
--------------------------------------------------------------------------------
1 | has($key)) {
25 | throw new SessionException('Session key '.$key.' does not exist');
26 | }
27 |
28 | return $this->data[$key];
29 | }
30 |
31 | public function has(string $key): bool
32 | {
33 | return isset($this->data[$key]);
34 | }
35 |
36 | public function set(string $key, mixed $value): static
37 | {
38 | $this->data[$key] = $value;
39 |
40 | return $this;
41 | }
42 |
43 | public function expireAt(int $seconds): static
44 | {
45 | return $this;
46 | }
47 |
48 | public function dontExpire(): static
49 | {
50 | return $this;
51 | }
52 |
53 | public function remove(string $key): static
54 | {
55 | unset($this->data[$key]);
56 |
57 | return $this;
58 | }
59 |
60 | public function clear(): void
61 | {
62 | $this->data = [];
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Lemon/Testing/TestCase.php:
--------------------------------------------------------------------------------
1 | application = $this->createApplication();
24 | }
25 |
26 | abstract public function createApplication(): Application;
27 |
28 | public function mock(string $class, string ...$aliases): Mock
29 | {
30 | $mock = new Mock($class);
31 | $mock_class = get_class($mock->mock);
32 | $this->application->add($mock_class, $mock->mock);
33 | foreach ([$class, ...$aliases] as $alias) {
34 | $this->application->alias($alias, $mock_class);
35 | }
36 |
37 | return $mock;
38 | }
39 |
40 | public function request(string $path, string $method = 'GET', array $headers = [], array $cookies = [], string $body = '', array $files = [], string $ip = ''): TestResponse
41 | {
42 | [$path, $query] = Request::trimQuery($path);
43 | $request = new Request($path, $query, $method, $headers, $body, $cookies, $files, $ip);
44 |
45 | $app = $this->application;
46 | $request->injectApplication($app);
47 | $app->add(Request::class, $request);
48 | $app->alias('request', Request::class);
49 |
50 | return new TestResponse(
51 | $app->get('routing')->dispatch($request),
52 | $this,
53 | $app->get(Factory::class)
54 | );
55 | }
56 |
57 | public function session(...$data): static
58 | {
59 | $this->application->add(SessionMock::class, new SessionMock($data));
60 | $this->application->alias(Session::class, SessionMock::class);
61 | $this->application->alias('session', SessionMock::class);
62 |
63 | return $this;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Lemon/Translating/Exceptions/TranslatorException.php:
--------------------------------------------------------------------------------
1 | locate($session->get('locale'));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Lemon/Translating/config.php:
--------------------------------------------------------------------------------
1 | 'translations',
7 | 'fallback' => 'en',
8 | ];
9 |
--------------------------------------------------------------------------------
/src/Lemon/Translating/translations/README.md:
--------------------------------------------------------------------------------
1 | # Translations
2 |
3 | This folder contains default Lemon translations, mostly for validation, see [todo repo] for more languages
4 |
--------------------------------------------------------------------------------
/src/Lemon/Translating/translations/en/validation.php:
--------------------------------------------------------------------------------
1 | 'Value %field must be numeric.',
7 | 'notNumeric' => 'Value %field must not be numeric.',
8 | 'email' => 'Value %field must be email.',
9 | 'url' => 'Value %field must be valid url',
10 | 'color' => 'Value %field must be valid hex color',
11 | 'max' => 'Value %field must be smaller or equal than %arg.',
12 | 'min' => 'Value %field must be higher or equal than %arg.',
13 | 'regex' => 'Value %field is not valid.',
14 | 'notRegex' => 'Value %field is not valid.',
15 | 'contains' => 'Value %field is not valid.',
16 | 'doesntContain' => 'Value %field is not valid.',
17 | 'missing' => 'Value %field is missing.',
18 | ];
19 |
--------------------------------------------------------------------------------
/src/Lemon/Translator.php:
--------------------------------------------------------------------------------
1 | {$name}(...$arguments);
22 | }
23 |
24 | /**
25 | * Initializes zests.
26 | */
27 | public static function init(Application $application): void
28 | {
29 | self::$application = $application;
30 | }
31 |
32 | public static function getAccessor(): object
33 | {
34 | $unit = static::unit();
35 |
36 | return self::$application->get($unit);
37 | }
38 |
39 | abstract public static function unit(): string;
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Config/config/cache.php:
--------------------------------------------------------------------------------
1 | 'storage.cache',
7 | ];
8 |
--------------------------------------------------------------------------------
/tests/Config/config/schnitzels.php:
--------------------------------------------------------------------------------
1 | 'chicken',
7 | 'tasty' => true,
8 | 'storage' => 'config.something',
9 | 'foo' => [
10 | 'bar' => 'neco',
11 | ],
12 | 'baz' => null,
13 | ];
14 |
--------------------------------------------------------------------------------
/tests/Config/config/something.txt:
--------------------------------------------------------------------------------
1 | dal bych klobnu
2 |
--------------------------------------------------------------------------------
/tests/Database/.env:
--------------------------------------------------------------------------------
1 | DB_DRIVER=sqlite
2 | DB_FILE=database
3 |
--------------------------------------------------------------------------------
/tests/Database/DatabaseTest.php:
--------------------------------------------------------------------------------
1 | add(Env::class);
31 | $lc->alias('env', Env::class);
32 |
33 | Zest::init($lc);
34 |
35 | $d = new Database(new Config($lc));
36 |
37 | $driver = $d->getConnection();
38 | $this->assertInstanceOf(Sqlite::class, $driver);
39 |
40 | $this->assertSame($driver, $d->getConnection());
41 | }
42 |
43 | public function testQuery()
44 | {
45 | $lc = new Application(__DIR__);
46 | $lc->add(Env::class);
47 | $lc->alias('env', Env::class);
48 |
49 | Zest::init($lc);
50 |
51 | $d = new Database(new Config($lc));
52 |
53 | $d->query('CREATE TABLE example (name varchar)');
54 | $foo = 'majkel';
55 | $d->query('INSERT INTO example VALUES (?)', $foo);
56 |
57 | $r = $d->query('SELECT * FROM example WHERE name=:name', name: $foo);
58 |
59 | $this->assertSame([
60 | [
61 | 'name' => 'majkel',
62 | 0 => 'majkel',
63 | ],
64 | ], $r->fetchAll());
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tests/Database/Drivers/.env:
--------------------------------------------------------------------------------
1 | DB_FILE=database
2 |
--------------------------------------------------------------------------------
/tests/Database/Drivers/SqliteTest.php:
--------------------------------------------------------------------------------
1 | expectNotToPerformAssertions(); // Just checking if we are able to connect
29 |
30 | $lc = new Application(__DIR__);
31 | $lc->add(Env::class);
32 | $lc->alias('env', Env::class);
33 |
34 | Zest::init($lc);
35 |
36 | $c = new Config($lc);
37 |
38 | new Sqlite($c);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Debug/ConsultantTest.php:
--------------------------------------------------------------------------------
1 | assertSame('parek', $c->bestMatch(['parek', 'rohlik'], 'parke'));
23 | $this->assertSame('dd', $c->bestMatch(['dd', 'drakoparek'], 'dp'));
24 | $this->assertNull($c->bestMatch([], 'parek'));
25 | }
26 |
27 | public function testFunction()
28 | {
29 | $c = new Consultant();
30 |
31 | $this->assertSame(['Did you mean explode?'], $c->giveAdvice(\Exception::class, 'Call to undefined function explod()'));
32 | $this->assertSame(['Function was propably not loaded. Try checking your loader'], $c->giveAdvice(\Exception::class, 'Call to undefined function AAAAAAAAAAAAAAAAAA()'));
33 | }
34 |
35 | public function testMethod()
36 | {
37 | $c = new Consultant();
38 |
39 | $this->assertSame(['Did you mean compile?'], $c->giveAdvice(\Exception::class, 'Call to undefined method '.Compiler::class.'::compilr()'));
40 | $this->assertSame([''], $c->giveAdvice(\Exception::class, 'Call to undefined method '.Compiler::class.'::qqqq()'));
41 | }
42 |
43 | public function testProperty()
44 | {
45 | $c = new Consultant();
46 |
47 | $this->assertSame(['Did you mean $directives?'], $c->giveAdvice(\Exception::class, 'Undefined property: '.Compiler::class.'::$direktizep'));
48 | $this->assertSame([''], $c->giveAdvice(\Exception::class, 'Undefined property: '.Compiler::class.'::$qaopsfjasdj'));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/Debug/StyleTest.php:
--------------------------------------------------------------------------------
1 | assertSame(<<<'HTML'
21 |
73 |
74 | HTML, $s->generate());
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/tests/Debug/foo.php:
--------------------------------------------------------------------------------
1 | 1
2 | 2
3 | 3
4 | 4
5 | 5
6 | 6
7 | 7
8 | 8
9 | 9
10 | 10
11 | 11
12 | 12
13 | 13
14 | 14
15 | 15
16 |
--------------------------------------------------------------------------------
/tests/Events/DispatcherTest.php:
--------------------------------------------------------------------------------
1 | on('foo', function () {});
22 | $events->on('foo', function () {});
23 | $events->on('bar', function () {});
24 | $events->on('baz', function () {});
25 | $this->assertSame(['foo', 'bar', 'baz'], array_keys($events->events()));
26 | }
27 |
28 | public function testFiring()
29 | {
30 | $lc = new Application(__DIR__);
31 | $lc->add(Logger::class);
32 | $events = new Dispatcher($lc);
33 | $events->on('foo', function (Logger $logger) {
34 | $logger->log('foo');
35 | });
36 | $events->on('foo', function (Logger $logger) {
37 | $logger->log('foo 2');
38 | });
39 |
40 | $events->fire('foo');
41 | $this->assertSame(['foo', 'foo 2'], $lc->get(Logger::class)->read());
42 |
43 | $events->fire('foo');
44 | $this->assertSame(['foo', 'foo 2', 'foo', 'foo 2'], $lc->get(Logger::class)->read());
45 | }
46 | }
47 |
48 | class Logger
49 | {
50 | private array $logs = [];
51 |
52 | public function log(string $message): static
53 | {
54 | $this->logs[] = $message;
55 |
56 | return $this;
57 | }
58 |
59 | public function read(): array
60 | {
61 | return $this->logs;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tests/Highlighter/HighlighterTest.php:
--------------------------------------------------------------------------------
1 | assertSame(<<<'HTML'
26 | foo
27 | echo 'cs';
29 | foreach ([] as $bar) {
30 | array_map($bar, fn() => 10 + 1);
31 | }
32 | HTML, $highlighter->highlight(<<<'HTML'
33 | foo
34 | 10 + 1);
38 | }
39 | HTML));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Http/CookieJarTest.php:
--------------------------------------------------------------------------------
1 | getJar(['foo' => 'bar']);
26 | $this->assertSame('bar', $jar->get('foo'));
27 | $this->assertNull($jar->get('parek'));
28 | }
29 |
30 | public function testSet()
31 | {
32 | $jar = $this->getJar();
33 | $jar->set('foo', 'bar');
34 | $jar->set('bar', 'baz', 3600);
35 |
36 | $this->assertSame([[['foo', 'bar'], ['expires' => 0, 'SameSite' => 'None']], [['bar', 'baz'], ['expires' => 3600, 'SameSite' => 'None']]], $jar->cookies());
37 | }
38 |
39 | public function getDelete()
40 | {
41 | $jar = $this->getJar(['foo' => 'bar']);
42 | $jar->delete('foo');
43 | $this->assertSame([[['foo', ''], ['expires' => -1, 'SameSite' => 'None']]], $jar->cookies());
44 | $jar->delete('bar');
45 | $this->assertSame([[['foo', ''], ['expires' => -1, 'SameSite' => 'None']]], $jar->cookies());
46 | }
47 |
48 | public function testHas()
49 | {
50 | $jar = $this->getJar(['foo' => 'bar']);
51 | $this->assertTrue($jar->has('foo'));
52 | $this->assertFalse($jar->has('parek'));
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tests/Http/Middlewares/TrimStringsTest.php:
--------------------------------------------------------------------------------
1 | 'application/json'], '{"foo":" 10 "}', [], [], '');
17 | $middleware->handle($r);
18 | $this->assertSame(['foo' => '10'], $r->data());
19 | $r = new Request('/', 'parek=%20%20rizek&kecup= horcice', 'GET', ['Content-Type' => 'application/json'], '{"foo":" 10 \n\t\r "}', [], [], '');
20 | $middleware->handle($r);
21 | $this->assertSame(['foo' => '10'], $r->data());
22 | $this->assertSame(['parek' => 'rizek', 'kecup' => 'horcice'], $r->query());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/Http/storage/templates/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/tests/Http/templates/errors/404.phtml:
--------------------------------------------------------------------------------
1 | 404 rip bozo
2 |
--------------------------------------------------------------------------------
/tests/Http/tmp/foo.php:
--------------------------------------------------------------------------------
1 | ok
2 |
--------------------------------------------------------------------------------
/tests/Kernel/LifecycleTest.php:
--------------------------------------------------------------------------------
1 | assertSame(__DIR__.DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.'klobasa', $lc->file('views.klobasa'));
21 | $this->assertSame(__DIR__.DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.'klobasa.juice', $lc->file('views.klobasa', ' . juice... '));
22 | $this->assertSame(__DIR__.DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.'klobasa.juice', $lc->file('.....views......klobasa...', ' . juice... '));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/Kernel/Resources/IFoo.php:
--------------------------------------------------------------------------------
1 | array, $item);
19 | }
20 |
21 | public function all()
22 | {
23 | return $this->array;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Kernel/Resources/Units/Baz.php:
--------------------------------------------------------------------------------
1 | array, $item);
16 | }
17 |
18 | public function all()
19 | {
20 | return $this->array;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Kernel/Resources/Units/User.php:
--------------------------------------------------------------------------------
1 | get(UserFactory::class)->make($value);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Kernel/Resources/Units/UserFactory.php:
--------------------------------------------------------------------------------
1 | add(Bar::class);
25 | $lc->add(Foo::class);
26 | Zest::init($lc);
27 |
28 | ZestsBar::add('foo');
29 |
30 | $this->assertSame(['foo'], $lc->get(Bar::class)->all());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/Logging/LoggerTest.php:
--------------------------------------------------------------------------------
1 | assertDirectoryExists(Filesystem::join(__DIR__, 'storage', 'logs'));
31 | $this->assertFileExists(Filesystem::join(__DIR__, 'storage', 'logs', '.gitignore'));
32 | }
33 |
34 | public function testInterpolation()
35 | {
36 | $log = new Logger(new Config(new Application(__DIR__)));
37 | $this->assertSame('foo idk ok 10', $log->interpolate('foo {bar} ok {baz}', ['bar' => 'idk', 'baz' => 10]));
38 | }
39 |
40 | public function testLogging()
41 | {
42 | $log = new Logger(new Config(new Application(__DIR__)));
43 | $log->log(LogLevel::INFO, '{what} se hrouti', ['what' => 'festival']);
44 | $log->log(LogLevel::ALERT, '{what} protekaji', ['what' => 'zachody']);
45 | $log->log(LogLevel::EMERGENCY, 'on proste nema svoje kafe');
46 | $now = (new \DateTime())->format('D M d h:i:s Y');
47 | $this->assertStringEqualsFile(Filesystem::join(__DIR__, 'storage', 'logs', 'lemon.log'), sprintf(<<<'LOG'
48 | [%s] INFO: festival se hrouti
49 | [%s] ALERT: zachody protekaji
50 | [%s] EMERGENCY: on proste nema svoje kafe
51 |
52 | LOG, $now, $now, $now));
53 | $this->expectException(InvalidArgumentException::class);
54 | $log->log('parek', 'hukot');
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/README.md:
--------------------------------------------------------------------------------
1 | # Lemon Tests
2 |
3 | This directory contains test suite for Lemon.
4 |
5 |
--------------------------------------------------------------------------------
/tests/Routing/routes/web.php:
--------------------------------------------------------------------------------
1 | get('/', function () {
7 | return 'hi';
8 | });
9 |
10 | $router->post('/foo', function () {
11 | return 'foo';
12 | });
13 |
--------------------------------------------------------------------------------
/tests/Support/Helpers.php:
--------------------------------------------------------------------------------
1 | assertSame('Foo', compose('ucfirst', 'strtolower')('FOO'));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/Support/MacrosTest.php:
--------------------------------------------------------------------------------
1 | macro('add', function () {
21 | // @phpstan-ignore-next-line
22 | return $this->getNumber() + 1;
23 | });
24 |
25 | $this->assertSame(3, $foo->add());
26 | }
27 | }
28 |
29 | class Foo
30 | {
31 | use Macros;
32 |
33 | public function __construct(
34 | private int $number
35 | ) {
36 | }
37 |
38 | public function getNumber(): int
39 | {
40 | return $this->number;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Support/PipeTest.php:
--------------------------------------------------------------------------------
1 | assertSame(
20 | 'Foo bar baz',
21 | Pipe::send('foo.bar.baz')
22 | ->then('ucfirst')
23 | ->then(fn ($value) => str_replace('.', ' ', $value))
24 | ->return()
25 | );
26 |
27 | $this->assertSame(
28 | 'foo bar',
29 | Pipe::send('foo')
30 | ->with('bar')
31 | ->then(fn ($value, $bar) => $value.' '.$bar)
32 | ->return()
33 | );
34 |
35 | $this->assertSame(
36 | 'Foo bar baz',
37 | Pipe::send('foo.bar.baz')
38 | ->{'>>>='}('ucfirst')
39 | ->{'>>>='}(fn ($value) => str_replace('.', ' ', $value))
40 | ->return()
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Support/Properties/PropertiesTest.php:
--------------------------------------------------------------------------------
1 | assertSame('parek', $f->bar);
35 | $this->assertNull($f->baz);
36 | $this->expectException(\Exception::class);
37 | $f->foo;
38 | $f->klobna;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Support/RegexTest.php:
--------------------------------------------------------------------------------
1 | assertSame(3, Regex::getLine("foo\nb\nar", 6));
20 | $this->assertSame(1, Regex::getLine("foo\nb\nar", 0));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Support/Types/ArrTest.php:
--------------------------------------------------------------------------------
1 | assertSame('foo', Arr::last(['klobna', 'parek', 'rizek', 'foo']));
20 | $this->assertSame('foo', Arr::last(['bar' => 'klobna', 'baz' => 'parek', 'nevim' => 'rizek', 'neco' => 'foo']));
21 | $this->assertNull(Arr::last([]));
22 | }
23 |
24 | public function testFirst()
25 | {
26 | $this->assertSame('klobna', Arr::first(['klobna', 'parek', 'rizek', 'foo']));
27 | $this->assertSame('klobna', Arr::first(['bar' => 'klobna', 'baz' => 'parek', 'nevim' => 'rizek', 'neco' => 'foo']));
28 | $this->assertNull(Arr::first([]));
29 | }
30 |
31 | public function testRange()
32 | {
33 | $this->assertSame([1, 2, 3, 4, 5], iterator_to_array(Arr::range(1, 5)));
34 | $this->assertSame([5, 4, 3, 2, 1], iterator_to_array(Arr::range(5, 1)));
35 | $this->assertSame([1], iterator_to_array(Arr::range(1, 1)));
36 | }
37 |
38 | public function testMap()
39 | {
40 | $this->assertSame([2, 4, 6], Arr::map(fn ($item) => $item * 2, [1, 2, 3]));
41 | $this->assertSame([[0, 1], [1, 2], [2, 3]], Arr::map(fn ($item, $key) => [$key, $item], [1, 2, 3]));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/Support/Types/MaybeTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Just::class, Maybe::just('foo'));
22 | $this->assertSame('foo', Maybe::just('foo')->unwrap());
23 | $this->assertSame('foo', Maybe::just('foo')->unwrap('cscs'));
24 | $this->assertSame('foo', Maybe::just('foo')->expect('bad'));
25 | $this->assertSame('Foo', Maybe::just('foo')->then('ucfirst')->unwrap());
26 | }
27 |
28 | public function testNothing()
29 | {
30 | $this->assertInstanceOf(Nothing::class, Maybe::nothing());
31 | $this->assertNull(Maybe::nothing()->unwrap());
32 | $this->assertSame('foo', Maybe::nothing()->unwrap('foo'));
33 | $this->assertNull(Maybe::nothing()->then('ucfirst')->unwrap());
34 |
35 | $this->expectExceptionMessage('bad');
36 | Maybe::nothing()->expect('bad');
37 | }
38 |
39 | public function testMaybe()
40 | {
41 | $this->assertSame('bar', $this->get(['foo' => 'bar'], 'foo')->unwrap());
42 | $this->assertSame('Bar', $this->get(['foo' => 'bar'], 'foo')->then('ucfirst')->unwrap());
43 | $this->assertSame('bar', $this->get(['foo' => 'bar'], 'foo')->expect('wrong'));
44 | $this->assertSame('bar', $this->get(['foo' => 'bar'], 'baz')->unwrap('bar'));
45 |
46 | $this->expectExceptionMessage('wrong');
47 | $this->get(['foo' => 'bar'], 'AAAA')->expect('wrong');
48 | }
49 |
50 | public function get(array $target, string $key): Maybe
51 | {
52 | if (!isset($target[$key])) {
53 | return Maybe::nothing();
54 | }
55 |
56 | return Maybe::just($target[$key]);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Support/Types/StackTest.php:
--------------------------------------------------------------------------------
1 | push('foo');
21 | $stack->push(1);
22 | $stack->push([]);
23 | $this->assertSame(['foo', 1, []], $stack->storage());
24 |
25 | $stack = Stack::withType('integer');
26 | $stack->push(1);
27 | $this->expectException(\InvalidArgumentException::class);
28 | $stack->push('foo');
29 | }
30 |
31 | public function testPop()
32 | {
33 | $stack = new Stack();
34 | $stack->push('foo');
35 | $stack->push('bar');
36 | $this->assertSame('bar', $stack->pop());
37 | $this->assertSame(['foo'], $stack->storage());
38 | }
39 |
40 | public function testTop()
41 | {
42 | $stack = new Stack();
43 | $stack->push('foo');
44 | $stack->push('bar');
45 | $this->assertSame('bar', $stack->top());
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/Support/Types/TypeTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(Type::is('mixed', 'foo'));
20 | $this->assertTrue(Type::is('string', 'foo'));
21 | $this->assertFalse(Type::is('string', 10));
22 | $this->assertTrue(Type::is('object', new Type()));
23 | $this->assertTrue(Type::is(Type::class, new Type()));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/CompilerTest.php:
--------------------------------------------------------------------------------
1 | assertSame(<<<'HTML'
24 |
25 |
26 | - escapeHtml($baz) ?>
27 |
28 |
29 | HTML, $compiler->compile(<<<'HTML'
30 |
31 | {foreach $foo as $baz }
32 | - {{ $baz}}
33 | {/foreach}
34 |
35 | HTML));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/CaseDirectiveTest.php:
--------------------------------------------------------------------------------
1 | assertSame('', $c->compileOpenning(new T(T::TAG, ['case', '10'], 1), ['if', 'switch']));
23 |
24 | $this->assertThrowable(function (DirectiveCompiler $c) {
25 | $c->compileOpenning(new T(T::TAG, ['case', ''], 1), ['switch']);
26 | }, CompilerException::class, $c);
27 |
28 | $this->assertThrowable(function (DirectiveCompiler $c) {
29 | $c->compileOpenning(new T(T::TAG, ['case', '10'], 1), []);
30 | }, CompilerException::class, $c);
31 |
32 | $this->assertThrowable(function (DirectiveCompiler $c) {
33 | $c->compileOpenning(new T(T::TAG, ['case', '10'], 1), ['switch', 'if']);
34 | }, CompilerException::class, $c);
35 | }
36 |
37 | public function testClosability()
38 | {
39 | $c = new DirectiveCompiler();
40 | $this->assertFalse($c->isClosable('case'));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/CsrfDirectiveTest.php:
--------------------------------------------------------------------------------
1 | assertSame('', $c->compileOpenning(new Token(Token::TAG, ['csrf', ''], 1), []));
23 | $this->expectException(CompilerException::class);
24 | $c->compileOpenning(new Token(Token::TAG, ['csrf', 'parek'], 1), []);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/ElseDirectiveTest.php:
--------------------------------------------------------------------------------
1 | assertSame('', $c->compileOpenning(new T(T::TAG, ['else', ''], 1), ['switch', 'if']));
23 |
24 | $this->assertThrowable(function (DirectiveCompiler $c) {
25 | $c->compileOpenning(new T(T::TAG, ['else', '$foo'], 1), ['if']);
26 | }, CompilerException::class, $c);
27 |
28 | $this->assertThrowable(function (DirectiveCompiler $c) {
29 | $c->compileOpenning(new T(T::TAG, ['else', ''], 1), []);
30 | }, CompilerException::class, $c);
31 |
32 | $this->assertThrowable(function (DirectiveCompiler $c) {
33 | $c->compileOpenning(new T(T::TAG, ['else', ''], 1), ['if', 'switch']);
34 | }, CompilerException::class, $c);
35 | }
36 |
37 | public function testClosability()
38 | {
39 | $c = new DirectiveCompiler();
40 | $this->assertFalse($c->isClosable('else'));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/ElseIfDirectiveTest.php:
--------------------------------------------------------------------------------
1 | assertSame('', $c->compileOpenning(new T(T::TAG, ['elseif', '10 == $foo'], 1), ['switch', 'if']));
23 |
24 | $this->assertThrowable(function (DirectiveCompiler $c) {
25 | $c->compileOpenning(new T(T::TAG, ['elseif', ''], 1), ['if']);
26 | }, CompilerException::class, $c);
27 |
28 | $this->assertThrowable(function (DirectiveCompiler $c) {
29 | $c->compileOpenning(new T(T::TAG, ['elseif', '10 == $foo'], 1), []);
30 | }, CompilerException::class, $c);
31 |
32 | $this->assertThrowable(function (DirectiveCompiler $c) {
33 | $c->compileOpenning(new T(T::TAG, ['elseif', '10 == $foo'], 1), ['if', 'switch']);
34 | }, CompilerException::class, $c);
35 | }
36 |
37 | public function testClosability()
38 | {
39 | $c = new DirectiveCompiler();
40 | $this->assertFalse($c->isClosable('elseif'));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/ErrorDirectiveTest.php:
--------------------------------------------------------------------------------
1 | assertSame('', $c->compileOpenning(new Token(Token::TAG, ['error', ''], 1), []));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/ForDirectiveTest.php:
--------------------------------------------------------------------------------
1 | assertSame('', $c->compileOpenning(new T(T::TAG, ['for', '$foo = 0; $foo < 10; $foo++'], 1), []));
23 | $this->assertSame('', $c->compileOpenning(new T(T::TAG, ['for', '$foo = 0; $foo < 10; $foo++'], 1), ['if']));
24 |
25 | $this->assertThrowable(function (DirectiveCompiler $c) {
26 | $c->compileOpenning(new T(T::TAG, ['for', ''], 1), []);
27 | }, CompilerException::class, $c);
28 | }
29 |
30 | public function testClosability()
31 | {
32 | $c = new DirectiveCompiler();
33 | $this->assertTrue($c->isClosable('for'));
34 | }
35 |
36 | public function testClose()
37 | {
38 | $c = new DirectiveCompiler();
39 | $this->assertSame('', $c->compileClosing('for'));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/ForeachDirectiveTest.php:
--------------------------------------------------------------------------------
1 | assertSame('', $c->compileOpenning(new T(T::TAG, ['foreach', '$foo as $bar'], 1), []));
23 | $this->assertSame('', $c->compileOpenning(new T(T::TAG, ['foreach', '$foo as $bar'], 1), ['if']));
24 |
25 | $this->assertThrowable(function (DirectiveCompiler $c) {
26 | $c->compileOpenning(new T(T::TAG, ['foreach', ''], 1), []);
27 | }, CompilerException::class, $c);
28 | }
29 |
30 | public function testClosability()
31 | {
32 | $c = new DirectiveCompiler();
33 | $this->assertTrue($c->isClosable('foreach'));
34 | }
35 |
36 | public function testClose()
37 | {
38 | $c = new DirectiveCompiler();
39 | $this->assertSame('', $c->compileClosing('foreach'));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/IfDirectiveTest.php:
--------------------------------------------------------------------------------
1 | assertSame('', $c->compileOpenning(new T(T::TAG, ['if', '1 == $foo'], 1), []));
23 | $this->assertSame('', $c->compileOpenning(new T(T::TAG, ['if', '1 == $foo'], 1), ['switch']));
24 |
25 | $this->assertThrowable(function (DirectiveCompiler $c) {
26 | $c->compileOpenning(new T(T::TAG, ['if', ''], 1), []);
27 | }, CompilerException::class, $c);
28 | }
29 |
30 | public function testClosability()
31 | {
32 | $c = new DirectiveCompiler();
33 | $this->assertTrue($c->isClosable('if'));
34 | }
35 |
36 | public function testClose()
37 | {
38 | $c = new DirectiveCompiler();
39 | $this->assertSame('', $c->compileClosing('if'));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/IfErrorDirectiveTest.php:
--------------------------------------------------------------------------------
1 | assertSame('', $c->compileOpenning(new Token(Token::TAG, ['ifError', ''], 1), []));
22 | }
23 |
24 | public function testClosability()
25 | {
26 | $c = new DirectiveCompiler();
27 | $this->assertTrue($c->isClosable('ifError'));
28 | }
29 |
30 | public function testClose()
31 | {
32 | $c = new DirectiveCompiler();
33 | $this->assertSame('', $c->compileClosing('ifError'));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/IncludeDirectiveTest.php:
--------------------------------------------------------------------------------
1 | assertSame('make("foo.bar")->compiled_path ?>', $compiler->directives->compileOpenning(new Token(Token::TAG, ['include', '"foo.bar"'], 1), []));
31 |
32 | $this->assertSame('make(\'foo.bar\')->compiled_path ?>', $compiler->directives->compileOpenning(new Token(Token::TAG, ['include', '\'foo.bar\''], 1), []));
33 |
34 | $this->assertThrowable(function (DirectiveCompiler $d) {
35 | $d->compileOpenning(new Token(Token::TAG, ['include', ''], 1), []);
36 | }, CompilerException::class, $compiler->directives);
37 |
38 | $this->assertThrowable(function (DirectiveCompiler $d) {
39 | $d->compileOpenning(new Token(Token::TAG, ['include', 'echo'], 1), []);
40 | }, CompilerException::class, $compiler->directives);
41 |
42 | $this->assertThrowable(function (DirectiveCompiler $d) {
43 | $d->compileOpenning(new Token(Token::TAG, ['include', '\'foo'], 1), []);
44 | }, CompilerException::class, $compiler->directives);
45 |
46 | $this->assertThrowable(function (DirectiveCompiler $d) {
47 | $d->compileOpenning(new Token(Token::TAG, ['include', '\'foo\'"bar"'], 1), []);
48 | }, CompilerException::class, $compiler->directives);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/Layout/BlockTest.php:
--------------------------------------------------------------------------------
1 | assertSame('startBlock(\'foo\') ?>', $d->compileOpenning(new Token(Token::TAG, ['block', '"foo"'], 1), []));
23 |
24 | $this->assertSame('startBlock(\'foo\') ?>', $d->compileOpenning(new Token(Token::TAG, ['block', '\'foo\''], 1), []));
25 |
26 | $this->assertThrowable(function (DirectiveCompiler $d) {
27 | $d->compileOpenning(new Token(Token::TAG, ['block', ''], 1), []);
28 | }, CompilerException::class, $d);
29 |
30 | $this->assertThrowable(function (DirectiveCompiler $d) {
31 | $d->compileOpenning(new Token(Token::TAG, ['block', 'echo'], 1), []);
32 | }, CompilerException::class, $d);
33 |
34 | $this->assertThrowable(function (DirectiveCompiler $d) {
35 | $d->compileOpenning(new Token(Token::TAG, ['block', '"parek" "rizek"'], 1), []);
36 | }, CompilerException::class, $d);
37 | }
38 |
39 | public function testCompileClosing()
40 | {
41 | $d = new DirectiveCompiler();
42 | $this->assertSame('endBlock() ?>', $d->compileClosing('block'));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/Layout/ExtendsTest.php:
--------------------------------------------------------------------------------
1 | assertSame(
31 | 'make("foo.bar")->compiled_path, $data) ?>',
32 | $compiler->directives->compileOpenning(new Token(Token::TAG, ['extends', '"foo.bar"'], 1), [])
33 | );
34 |
35 | $this->assertThrowable(function (DirectiveCompiler $d) {
36 | $d->compileOpenning(new Token(Token::TAG, ['extends', ''], 1), []);
37 | }, CompilerException::class, $compiler->directives);
38 |
39 | $this->assertThrowable(function (DirectiveCompiler $d) {
40 | $d->compileOpenning(new Token(Token::TAG, ['extends', 'echo'], 1), []);
41 | }, CompilerException::class, $compiler->directives);
42 |
43 | $this->assertThrowable(function (DirectiveCompiler $d) {
44 | $d->compileOpenning(new Token(Token::TAG, ['extends', '\'foo'], 1), []);
45 | }, CompilerException::class, $compiler->directives);
46 |
47 | $this->assertThrowable(function (DirectiveCompiler $d) {
48 | $d->compileOpenning(new Token(Token::TAG, ['extends', '\'foo\'"bar"'], 1), []);
49 | }, CompilerException::class, $compiler->directives);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/Layout/LayoutTest.php:
--------------------------------------------------------------------------------
1 | '10'];
22 | $path = __DIR__.DIRECTORY_SEPARATOR.'templates'.DIRECTORY_SEPARATOR.'foo.php';
23 | $result = new Template($path, $path, $data);
24 | $this->assertSame(<<<'HTML'
25 |
26 |
27 | 10
28 |
29 | bar
30 |
31 | HTML, $result->render());
32 |
33 | $this->assertThrowable(function () {
34 | $l = new FakeLayout('foo');
35 | $l->endBlock();
36 | }, TemplateException::class);
37 |
38 | $this->assertThrowable(function () {
39 | $l = new FakeLayout('foo');
40 | $l->startBlock('nevim');
41 | $l->startBlock('neco');
42 | }, TemplateException::class);
43 |
44 | ob_get_clean();
45 |
46 | $this->assertThrowable(function () {
47 | $l = new FakeLayout('foo');
48 | $l->yield('parek');
49 | }, TemplateException::class);
50 | }
51 | }
52 |
53 | class FakeLayout extends Layout
54 | {
55 | public function __destruct()
56 | {
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/Layout/YieldTest.php:
--------------------------------------------------------------------------------
1 | assertSame('yield(\'foo\') ?>', $d->compileOpenning(new Token(Token::TAG, ['yield', '"foo"'], 1), []));
23 |
24 | $this->assertSame('yield(\'foo\') ?>', $d->compileOpenning(new Token(Token::TAG, ['yield', '\'foo\''], 1), []));
25 |
26 | $this->assertThrowable(function (DirectiveCompiler $d) {
27 | $d->compileOpenning(new Token(Token::TAG, ['yield', ''], 1), []);
28 | }, CompilerException::class, $d);
29 |
30 | $this->assertThrowable(function (DirectiveCompiler $d) {
31 | $d->compileOpenning(new Token(Token::TAG, ['yield', 'echo'], 1), []);
32 | }, CompilerException::class, $d);
33 |
34 | $this->assertThrowable(function (DirectiveCompiler $d) {
35 | $d->compileOpenning(new Token(Token::TAG, ['yield', '"parek" "rizek"'], 1), []);
36 | }, CompilerException::class, $d);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/Layout/storage/templates/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/Layout/templates/bar.php:
--------------------------------------------------------------------------------
1 | yield('foo'); ?>
2 |
3 | yield('bar'); ?>
4 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/Layout/templates/foo.php:
--------------------------------------------------------------------------------
1 |
3 |
4 | startBlock('foo'); ?>
5 |
6 | endBlock(); ?>
7 |
8 | startBlock('bar'); ?>
9 | bar
10 | endBlock(); ?>
11 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/Layout/templates/foo/bar.juice:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lemon-Framework/Lemon/b19cdd4646b20972dca139d88f05b4361a104d12/tests/Templating/Juice/Compilers/Directives/Layout/templates/foo/bar.juice
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/SwitchDirectiveTest.php:
--------------------------------------------------------------------------------
1 | assertSame('', $c->compileOpenning(new T(T::TAG, ['switch', '$foo'], 1), []));
23 | $this->assertSame('', $c->compileOpenning(new T(T::TAG, ['switch', '$foo'], 1), ['if']));
24 |
25 | $this->assertThrowable(function (DirectiveCompiler $c) {
26 | $c->compileOpenning(new T(T::TAG, ['switch', ''], 1), []);
27 | }, CompilerException::class, $c);
28 | }
29 |
30 | public function testClosability()
31 | {
32 | $c = new DirectiveCompiler();
33 | $this->assertTrue($c->isClosable('switch'));
34 | }
35 |
36 | public function testClose()
37 | {
38 | $c = new DirectiveCompiler();
39 | $this->assertSame('', $c->compileClosing('switch'));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/TranslationDirectiveTest.php:
--------------------------------------------------------------------------------
1 | assertSame('', $c->compileOpenning(new Token(Token::TAG, ['text', 'klobas'], 1), []));
23 | $this->assertSame('', $c->compileOpenning(new Token(Token::TAG, ['text', '"klobas"'], 1), []));
24 | $this->assertSame('', $c->compileOpenning(new Token(Token::TAG, ['text', "'klobas'"], 1), []));
25 | $this->expectException(CompilerException::class);
26 | $c->compileOpenning(new Token(Token::TAG, ['text', ''], 1), []);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/UnlessDirectiveTest.php:
--------------------------------------------------------------------------------
1 | assertSame('', $c->compileOpenning(new T(T::TAG, ['unless', '1 == $foo'], 1), []));
23 | $this->assertSame('', $c->compileOpenning(new T(T::TAG, ['unless', '1 == $foo'], 1), ['switch']));
24 |
25 | $this->assertThrowable(function (DirectiveCompiler $c) {
26 | $c->compileOpenning(new T(T::TAG, ['unless', ''], 1), []);
27 | }, CompilerException::class, $c);
28 | }
29 |
30 | public function testClosability()
31 | {
32 | $c = new DirectiveCompiler();
33 | $this->assertTrue($c->isClosable('unless'));
34 | }
35 |
36 | public function testClose()
37 | {
38 | $c = new DirectiveCompiler();
39 | $this->assertSame('', $c->compileClosing('unless'));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/WhileDirectiveTest.php:
--------------------------------------------------------------------------------
1 | assertSame('', $c->compileOpenning(new T(T::TAG, ['while', '$foo'], 1), []));
23 | $this->assertSame('', $c->compileOpenning(new T(T::TAG, ['while', '$foo'], 1), ['switch']));
24 |
25 | $this->assertThrowable(function (DirectiveCompiler $c) {
26 | $c->compileOpenning(new T(T::TAG, ['while', ''], 1), []);
27 | }, CompilerException::class, $c);
28 | }
29 |
30 | public function testClosability()
31 | {
32 | $c = new DirectiveCompiler();
33 | $this->assertTrue($c->isClosable('while'));
34 | }
35 |
36 | public function testClose()
37 | {
38 | $c = new DirectiveCompiler();
39 | $this->assertSame('', $c->compileClosing('while'));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/storage/templates/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/tests/Templating/Juice/Compilers/Directives/templates/foo/bar.juice:
--------------------------------------------------------------------------------
1 | good morning
2 |
--------------------------------------------------------------------------------
/tests/Templating/Juice/EnviromentTest.php:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lemon-Framework/Lemon/b19cdd4646b20972dca139d88f05b4361a104d12/tests/Templating/Juice/EnviromentTest.php
--------------------------------------------------------------------------------
/tests/Templating/TemplateTest.php:
--------------------------------------------------------------------------------
1 | 'bar']);
21 | $this->assertSame('bar', $template->render());
22 |
23 | $template = new Template(__DIR__.DIRECTORY_SEPARATOR.'bar.juice', __DIR__.DIRECTORY_SEPARATOR.'bar.php', []);
24 | $this->expectException(TemplateException::class);
25 | $template->render();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/Templating/bar.juice:
--------------------------------------------------------------------------------
1 | {{ $bar }}
2 |
--------------------------------------------------------------------------------
/tests/Templating/bar.php:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/Templating/foo.juice:
--------------------------------------------------------------------------------
1 | {{ $foo }}
2 |
--------------------------------------------------------------------------------
/tests/Templating/foo.php:
--------------------------------------------------------------------------------
1 | assertSame([['obligated', 'bar'], ['obligated', 'baz']], $command->parameters);
24 |
25 | $this->assertNotEmpty($command->action);
26 |
27 | $command = new Command('foo {bar} {baz?}', function ($bar, $baz = 'foo') {
28 | echo $bar.' '.$baz;
29 | });
30 |
31 | $this->assertSame([['obligated', 'bar'], ['optional', 'baz']], $command->parameters);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Terminal/Commands/DispatcherTest.php:
--------------------------------------------------------------------------------
1 | add(new Command('foo {bar}', $fn));
25 |
26 | $this->assertSame([$fn, ['bar' => 'baz']], $d->dispatch(['foo', 'bar=baz']));
27 |
28 | $this->assertSame('Command parek was not found.', $d->dispatch(['parek']));
29 |
30 | $this->assertSame('No command provided', $d->dispatch([]));
31 |
32 | $d->add(new Command('baz {bar} {foo?} {idk}', $fn));
33 | $this->assertSame([$fn, ['bar' => 'foo', 'idk' => '10']], $d->dispatch(['baz', 'idk=10', 'bar=foo']));
34 |
35 | $d->add(new Command('bar {bar} {foo?} {idk}', $fn));
36 | $this->assertSame('Argument bar is missing.', $d->dispatch(['bar']));
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/tests/Terminal/IO/Html/HtmlOutputTest.php:
--------------------------------------------------------------------------------
1 | assertSame("\033[33mHodne dobre\033[33m\033[0m\033[0m", $o->compile(' Hodne dobre
'));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Terminal/IO/Html/StylesTest.php:
--------------------------------------------------------------------------------
1 | loadHTML('');
22 | $node = $dom->getElementsByTagName('div')[0];
23 | $this->assertSame([
24 | "\033[37m\033[43m",
25 | '',
26 | '',
27 | ], $s->getStyle($node));
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Terminal/IO/OutputTest.php:
--------------------------------------------------------------------------------
1 | assertSame('foo', $output->out('foo'));
22 | $this->assertSame("foo\033[0m\033[0m", $output->out('foo
'));
23 | $this->assertSame('10', $output->out(10));
24 | $path = __DIR__.DIRECTORY_SEPARATOR.'templates'.DIRECTORY_SEPARATOR.'foo.phtml';
25 | $this->assertSame("\033[33mnevim\033[33m\033[0m\033[0m", $output->out(new Template($path, $path, ['foo' => 'nevim'])));
26 |
27 | $this->expectException(\Exception::class);
28 | $output->out($output);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/Terminal/IO/templates/foo.phtml:
--------------------------------------------------------------------------------
1 | = $foo ?>
2 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | assertTrue($thrown, 'Failed asserting that action throws '.$expected.'.');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/Testing/TestCase.php:
--------------------------------------------------------------------------------
1 | allows()->getRawPath('foo.bar')->andReturns('foo/bar.juice');
23 | $app->add(get_class($templates), $templates);
24 | $app->alias(Factory::class, get_class($templates));
25 |
26 | $routing = \Mockery::mock(Router::class);
27 | $routing->expects()->dispatch(Request::class)->andReturnUsing(function (Request $request) {
28 | if ('/' === $request->path) {
29 | return (new HtmlResponse(headers: ['Location' => 'foo']))->cookie('foo', 'bar');
30 | }
31 |
32 | return new TemplateResponse(new Template('foo/bar.juice', 'foo/bar.php', ['foo' => 'bar']));
33 | });
34 | $app->add(get_class($routing), $routing);
35 | $app->alias(Router::class, get_class($routing));
36 | $app->alias('routing', get_class($routing));
37 |
38 | return $app;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/Testing/TestCaseTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(TestResponse::class, $this->request('/'));
19 | }
20 |
21 | public function testMock()
22 | {
23 | $this->mock(Foo::class, 'foo')
24 | ->expect(bar: fn () => 'cs')
25 | ;
26 |
27 | $this->assertSame('cs', $this->application->get(Foo::class)->bar());
28 | $this->assertSame('cs', $this->application->get('foo')->bar());
29 | }
30 | }
31 |
32 | interface Foo
33 | {
34 | public function bar(): string;
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Testing/TestResponseTest.php:
--------------------------------------------------------------------------------
1 | request('/')->assertStatus(200);
19 | $this->request('/')->assertOK();
20 | }
21 |
22 | public function testBody()
23 | {
24 | $this->request('/')->assertBody('');
25 | }
26 |
27 | public function testTemplate()
28 | {
29 | $this->request('foo')->assertTemplate('foo.bar');
30 | $this->expectException(AssertionFailedError::class);
31 | $this->request('/')->assertTemplate('foo.bar');
32 | }
33 |
34 | public function testTemplateData()
35 | {
36 | $this->request('foo')->assertTemplate('foo.bar', foo: 'bar');
37 | $this->request('foo')->assertTemplate('foo.bar', bar: null);
38 | $this->expectException(AssertionFailedError::class);
39 | $this->request('foo')->assertTemplate('foo.bar', foo: 'AAAAAAa');
40 | }
41 |
42 | public function testHeader()
43 | {
44 | $this->request('/')->assertHeader('Location', 'foo');
45 | $this->request('/')->assertLocation('foo');
46 | }
47 |
48 | public function testCookies()
49 | {
50 | $this->request('/')->assertCookie('foo', 'bar');
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/tests/Translating/translations/cs/base.php:
--------------------------------------------------------------------------------
1 | 'Vitejte v citronove ramopraci',
7 | ];
8 |
--------------------------------------------------------------------------------
/tests/Translating/translations/cs/foo.php:
--------------------------------------------------------------------------------
1 | 'Pokud tohle ctes tak je mi te za a lito a za b, pokud muzes, hod tam prosimte pavla a ne babise diky',
7 | ];
8 |
--------------------------------------------------------------------------------
/tests/Translating/translations/en/base.php:
--------------------------------------------------------------------------------
1 | 'Welcome to the Lemon Framework',
7 | ];
8 |
--------------------------------------------------------------------------------
/tests/Translating/translations/en/foo.php:
--------------------------------------------------------------------------------
1 | 'we live i a society',
7 | ];
8 |
--------------------------------------------------------------------------------