├── .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 |
- :
29 |
30 |
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 | <?php echo Response::STATUS_CODES[$code] ?> 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 |
name ?>
-
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 | 29 | HTML, $compiler->compile(<<<'HTML' 30 | 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 |
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 | --------------------------------------------------------------------------------