├── .gitignore ├── .travis.yml ├── tests ├── scope_create_output.conf ├── test_input.conf ├── bootstrap.php ├── FileTest.php ├── CommentTest.php ├── EmptyLineTest.php ├── TextTest.php └── ScopeTest.php ├── Makefile ├── src ├── Exception.php ├── Printable.php ├── EmptyLine.php ├── File.php ├── Comment.php ├── Scope.php ├── Text.php └── Directive.php ├── phpunit.xml ├── LICENSE ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | build 3 | composer.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.5 4 | - 5.6 5 | - 7.0 6 | script: make test 7 | after_script: 8 | - vendor/bin/test-reporter 9 | - vendor/bin/codacycoverage clover 10 | -------------------------------------------------------------------------------- /tests/scope_create_output.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | server_name example.net; 4 | root C:/www/example_net; 5 | location ^~ /var/ { # Deny access for location /var/ 6 | deny all; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/test_input.conf: -------------------------------------------------------------------------------- 1 | 2 | # Multi line 3 | # comment 4 | server { 5 | listen 8080; 6 | server_name example.net; 7 | root C:/www/example_net; 8 | location ^~ /var/ { # Deny access for location /var/ 9 | deny all; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: test clean coverage 3 | 4 | vendor: composer.json 5 | composer install --dev 6 | 7 | build: 8 | mkdir --parents -- $@ 9 | 10 | test: vendor build 11 | phpunit 12 | 13 | coverage: vendor build 14 | phpunit --coverage-html build/html/coverage 15 | 16 | clean: 17 | rm --recursive --force -- vendor 18 | rm --recursive --force -- build 19 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | print 'PHP v'.phpversion()."\n"; 12 | 13 | require __DIR__ . '/../vendor/autoload.php'; 14 | -------------------------------------------------------------------------------- /src/Exception.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | * 11 | */ 12 | 13 | namespace RomanPitak\Nginx\Config; 14 | 15 | class Exception extends \Exception 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Printable.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | * 11 | */ 12 | 13 | namespace RomanPitak\Nginx\Config; 14 | 15 | abstract class Printable 16 | { 17 | abstract public function prettyPrint($indentLevel, $spacesPerIndent = 4); 18 | 19 | public function __toString() 20 | { 21 | return $this->prettyPrint(0); 22 | } 23 | } -------------------------------------------------------------------------------- /src/EmptyLine.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | * 11 | */ 12 | 13 | namespace RomanPitak\Nginx\Config; 14 | 15 | class EmptyLine extends Printable 16 | { 17 | public static function fromString(Text $configString) 18 | { 19 | $configString->gotoNextEol(); 20 | return new self; 21 | } 22 | 23 | public function prettyPrint($indentLevel, $spacesPerIndent = 4) 24 | { 25 | return "\n"; 26 | } 27 | } -------------------------------------------------------------------------------- /tests/FileTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace RomanPitak\Nginx\Config; 12 | 13 | class FileTest extends \PHPUnit_Framework_TestCase 14 | { 15 | 16 | /** 17 | * Fail on non existing file 18 | * 19 | * @expectedException \RomanPitak\Nginx\Config\Exception 20 | */ 21 | public function testCannotRead() 22 | { 23 | new File('this_file_does_not_exist.txt'); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/CommentTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace RomanPitak\Nginx\Config; 12 | 13 | class CommentTest extends \PHPUnit_Framework_TestCase 14 | { 15 | 16 | public function testGetText() 17 | { 18 | $comment = new Comment('c'); 19 | $this->assertEquals("c", $comment->getText()); 20 | } 21 | 22 | public function testToString() 23 | { 24 | $comment = new Comment('c'); 25 | $this->assertEquals("# c\n", (string) $comment); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /tests/EmptyLineTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace RomanPitak\Nginx\Config; 12 | 13 | class EmptyLineTest extends \PHPUnit_Framework_TestCase 14 | { 15 | 16 | public function testCanBeConstructed() 17 | { 18 | $emptyLine = new EmptyLine(); 19 | $this->assertInstanceOf('\\RomanPitak\\Nginx\\Config\\EmptyLine', $emptyLine); 20 | return $emptyLine; 21 | } 22 | 23 | /** 24 | * @depends testCanBeConstructed 25 | * 26 | * @param EmptyLine $emptyLine 27 | */ 28 | public function testPrettyPrint(EmptyLine $emptyLine) 29 | { 30 | $this->assertEquals("\n", $emptyLine->prettyPrint(0)); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/File.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | * 11 | */ 12 | 13 | namespace RomanPitak\Nginx\Config; 14 | 15 | class File extends Text 16 | { 17 | /** @var string $inFilePath */ 18 | private $inFilePath; 19 | 20 | /** 21 | * @param $filePath string Name of the conf file (or full path). 22 | * @throws Exception 23 | */ 24 | public function __construct($filePath) 25 | { 26 | $this->inFilePath = $filePath; 27 | 28 | $contents = @file_get_contents($this->inFilePath); 29 | 30 | if (false === $contents) { 31 | throw new Exception('Cannot read file "' . $this->inFilePath . '".'); 32 | } 33 | 34 | parent::__construct($contents); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2016 [Roman Piták](http://pitak.net) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/TextTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace RomanPitak\Nginx\Config; 12 | 13 | class TextTest extends \PHPUnit_Framework_TestCase 14 | { 15 | 16 | /** 17 | * @expectedException \RomanPitak\Nginx\Config\Exception 18 | */ 19 | public function testGetCharPosition() 20 | { 21 | $text = new Text(''); 22 | $text->getChar(1.5); 23 | } 24 | 25 | /** 26 | * @expectedException \RomanPitak\Nginx\Config\Exception 27 | */ 28 | public function testGetCharEof() 29 | { 30 | $text = new Text(''); 31 | $text->getChar(1); 32 | } 33 | 34 | public function testGetLastEol() 35 | { 36 | $text = new Text(''); 37 | $this->assertEquals(0, $text->getLastEol()); 38 | } 39 | 40 | public function testGetNextEol() 41 | { 42 | $text = new Text("\n"); 43 | $this->assertEquals(0, $text->getNextEol()); 44 | $text = new Text("roman"); 45 | $this->assertEquals(4, $text->getNextEol()); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "romanpitak/nginx-config-processor", 3 | "type": "library", 4 | "description": "Nginx configuration files processor.", 5 | "keywords": [ 6 | "nginx", 7 | "conf", 8 | "conf file", 9 | "config", 10 | "config file", 11 | "configuration", 12 | "configuration file", 13 | "processor", 14 | "parser", 15 | "create", 16 | "creator", 17 | "manager", 18 | "php" 19 | ], 20 | "homepage": "https://github.com/romanpitak/Nginx-Config-Processor", 21 | "license": "MIT", 22 | "authors": [ 23 | { 24 | "name": "Roman Piták", 25 | "email": "roman@pitak.net", 26 | "homepage": "http://pitak.net", 27 | "role": "Developer" 28 | } 29 | ], 30 | "support": { 31 | "email": "roman@pitak.net", 32 | "issues": "https://github.com/romanpitak/Nginx-Config-Processor/issues", 33 | "source": "https://github.com/romanpitak/Nginx-Config-Processor" 34 | }, 35 | "repositories": [ 36 | { 37 | "type": "git", 38 | "url": "https://github.com/romanpitak/Nginx-Config-Processor.git" 39 | } 40 | ], 41 | "bugs": { 42 | "url": "https://github.com/romanpitak/Nginx-Config-Processor/issues" 43 | }, 44 | "require": { 45 | "php": ">=5.3.3" 46 | }, 47 | "require-dev": { 48 | "codeclimate/php-test-reporter": "dev-master", 49 | "codacy/coverage": "dev-master" 50 | }, 51 | "autoload": { 52 | "psr-4": { 53 | "RomanPitak\\Nginx\\Config\\": "src/" 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/ScopeTest.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace RomanPitak\Nginx\Config; 12 | 13 | class ScopeTest extends \PHPUnit_Framework_TestCase 14 | { 15 | 16 | public function testFromFile() 17 | { 18 | Scope::fromFile('tests/test_input.conf')->saveToFile('build/out.conf'); 19 | $this->assertEquals(@file_get_contents('tests/test_input.conf'), @file_get_contents('build/out.conf')); 20 | } 21 | 22 | /** 23 | * @expectedException \RomanPitak\Nginx\Config\Exception 24 | */ 25 | public function testSaveToFile() 26 | { 27 | $scope = new Scope(); 28 | $scope->saveToFile('this/path/does/not/exist.conf'); 29 | } 30 | 31 | public function testCreate() 32 | { 33 | $config_string = (string) Scope::create() 34 | ->addDirective(Directive::create('server') 35 | ->setChildScope(Scope::create() 36 | ->addDirective(Directive::create('listen', 8080)) 37 | ->addDirective(Directive::create('server_name', 'example.net')) 38 | ->addDirective(Directive::create('root', 'C:/www/example_net')) 39 | ->addDirective(Directive::create('location', '^~ /var/', Scope::create() 40 | ->addDirective(Directive::create('deny', 'all')) 41 | )->setCommentText('Deny access for location /var/') 42 | ) 43 | ) 44 | )->__toString(); 45 | $this->assertEquals($config_string, @file_get_contents('tests/scope_create_output.conf')); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/Comment.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | * 11 | */ 12 | 13 | namespace RomanPitak\Nginx\Config; 14 | 15 | class Comment extends Printable 16 | { 17 | /** @var string $text */ 18 | private $text = null; 19 | 20 | public function __construct($text = null) 21 | { 22 | $this->text = $text; 23 | } 24 | 25 | public static function fromString(Text $configString) 26 | { 27 | $text = ''; 28 | while ((false === $configString->eof()) && (false === $configString->eol())) { 29 | $text .= $configString->getChar(); 30 | $configString->inc(); 31 | } 32 | return new Comment(ltrim($text, "# ")); 33 | } 34 | 35 | /** 36 | * Get comment text 37 | * 38 | * @return string|null 39 | */ 40 | public function getText() 41 | { 42 | return $this->text; 43 | } 44 | 45 | /** 46 | * Is this an empty (no text) comment? 47 | * 48 | * @return bool 49 | */ 50 | public function isEmpty() 51 | { 52 | return ((is_null($this->text)) || ('' === $this->text)); 53 | } 54 | 55 | /** 56 | * Is this comment multi-line? 57 | * 58 | * @return bool 59 | */ 60 | public function isMultiline() 61 | { 62 | return (false !== strpos(rtrim($this->text), "\n")); 63 | } 64 | 65 | /** 66 | * Set the comment text 67 | * 68 | * If you set the comment text to null or empty string, 69 | * the comment will not print. 70 | * 71 | * @param string|null $text 72 | */ 73 | public function setText($text) 74 | { 75 | $this->text = $text; 76 | } 77 | 78 | public function prettyPrint($indentLevel, $spacesPerIndent = 4) 79 | { 80 | if (true === $this->isEmpty()) { 81 | return ''; 82 | } 83 | 84 | $indent = str_repeat(str_repeat(' ', $spacesPerIndent), $indentLevel); 85 | $text = $indent . "# " . rtrim($this->text); 86 | 87 | if (true === $this->isMultiline()) { 88 | $text = preg_replace("#\r{0,1}\n#", PHP_EOL . $indent . "# ", $text); 89 | } 90 | 91 | return $text . "\n"; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nginx Configuration Processor 2 | 3 | [![Latest Stable Version](https://img.shields.io/packagist/v/romanpitak/nginx-config-processor.svg)](https://packagist.org/packages/romanpitak/nginx-config-processor) 4 | [![Total Downloads](https://img.shields.io/packagist/dt/romanpitak/nginx-config-processor.svg)](https://packagist.org/packages/romanpitak/nginx-config-processor) 5 | [![License](https://img.shields.io/packagist/l/romanpitak/nginx-config-processor.svg)](https://packagist.org/packages/romanpitak/nginx-config-processor) 6 | [![Build Status](https://travis-ci.org/romanpitak/Nginx-Config-Processor.svg?branch=master)](https://travis-ci.org/romanpitak/Nginx-Config-Processor) 7 | [![Code Climate](https://codeclimate.com/github/romanpitak/Nginx-Config-Processor/badges/gpa.svg)](https://codeclimate.com/github/romanpitak/Nginx-Config-Processor) 8 | [![Test Coverage](https://codeclimate.com/github/romanpitak/Nginx-Config-Processor/badges/coverage.svg)](https://codeclimate.com/github/romanpitak/Nginx-Config-Processor/coverage) 9 | [![Codacy Badge](https://www.codacy.com/project/badge/bf83cd710c374869a96cd5d5e44e0329)](https://www.codacy.com/public/roman/Nginx-Config-Processor) 10 | [![Codacy Badge](https://api.codacy.com/project/badge/coverage/bf83cd710c374869a96cd5d5e44e0329)](https://www.codacy.com/app/roman/Nginx-Config-Processor) 11 | 12 | (c) 2014-2016 [Roman Piták](http://pitak.net) 13 | 14 | PHP Nginx configuration files processor (parser, creator). 15 | 16 | ## Installation 17 | 18 | The best way to install is to use the [Composer](https://getcomposer.org/) dependency manager. 19 | 20 | ``` 21 | php composer.phar require romanpitak/nginx-config-processor 22 | ``` 23 | 24 | ## Features 25 | 26 | ### Pretty Print 27 | 28 | ```php 29 | saveToFile('out.conf'); 30 | ``` 31 | 32 | ### Config Create 33 | 34 | ```php 35 | addDirective(Directive::create('server') 38 | ->setChildScope(Scope::create() 39 | ->addDirective(Directive::create('listen', 8080)) 40 | ->addDirective(Directive::create('server_name', 'example.net')) 41 | ->addDirective(Directive::create('root', 'C:/www/example_net')) 42 | ->addDirective(Directive::create('location', '^~ /var/', Scope::create() 43 | ->addDirective(Directive::create('deny', 'all')) 44 | )->setCommentText('Deny access for location /var/') 45 | ) 46 | ) 47 | ) 48 | ->saveToFile('example.net'); 49 | ``` 50 | 51 | File _example.net_: 52 | 53 | ```nginx 54 | server { 55 | listen 8080; 56 | server_name example.net; 57 | root C:/www/example_net; 58 | location ^~ /var/ { # Deny access for location /var/ 59 | deny all; 60 | } 61 | } 62 | ``` 63 | 64 | ### Comments handling 65 | 66 | #### Simple comments 67 | 68 | ```php 69 | setCommentText('Directive with a comment'); 100 | ``` 101 | 102 | output: 103 | 104 | ```nginx 105 | deny all; # Directive with a comment 106 | ``` 107 | 108 | #### Directive with a multi-line comment 109 | 110 | ```php 111 | setCommentText('Directive 112 | with a multi line comment'); 113 | ``` 114 | 115 | output: 116 | 117 | ```nginx 118 | # Directive 119 | # with a multi line comment 120 | deny all; 121 | ``` 122 | -------------------------------------------------------------------------------- /src/Scope.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | * 11 | */ 12 | 13 | namespace RomanPitak\Nginx\Config; 14 | 15 | class Scope extends Printable 16 | { 17 | /** @var Directive $parentDirective */ 18 | private $parentDirective = null; 19 | 20 | /** @var Directive[] $directives */ 21 | private $directives = array(); 22 | 23 | /** @var Printable[] $printables */ 24 | private $printables = array(); 25 | 26 | /** 27 | * Write this Scope into a file. 28 | * 29 | * @param $filePath 30 | * @throws Exception 31 | */ 32 | public function saveToFile($filePath) 33 | { 34 | $handle = @fopen($filePath, 'w'); 35 | if (false === $handle) { 36 | throw new Exception('Cannot open file "' . $filePath . '" for writing.'); 37 | } 38 | 39 | $bytesWritten = @fwrite($handle, (string)$this); 40 | if (false === $bytesWritten) { 41 | fclose($handle); 42 | throw new Exception('Cannot write into file "' . $filePath . '".'); 43 | } 44 | 45 | $closed = @fclose($handle); 46 | if (false === $closed) { 47 | throw new Exception('Cannot close file handle for "' . $filePath . '".'); 48 | } 49 | } 50 | 51 | /* 52 | * ========== Factories ========== 53 | */ 54 | 55 | /** 56 | * Provides fluid interface. 57 | * 58 | * @return Scope 59 | */ 60 | public static function create() 61 | { 62 | return new self(); 63 | } 64 | 65 | /** 66 | * Create new Scope from the configuration string. 67 | * 68 | * @param \RomanPitak\Nginx\Config\Text $configString 69 | * @return Scope 70 | * @throws Exception 71 | */ 72 | public static function fromString(Text $configString) 73 | { 74 | $scope = new Scope(); 75 | while (false === $configString->eof()) { 76 | 77 | if (true === $configString->isEmptyLine()) { 78 | $scope->addPrintable(EmptyLine::fromString($configString)); 79 | } 80 | 81 | $char = $configString->getChar(); 82 | 83 | if ('#' === $char) { 84 | $scope->addPrintable(Comment::fromString($configString)); 85 | continue; 86 | } 87 | 88 | if (('a' <= $char) && ('z' >= $char)) { 89 | $scope->addDirective(Directive::fromString($configString)); 90 | continue; 91 | } 92 | 93 | if ('}' === $configString->getChar()) { 94 | break; 95 | } 96 | 97 | $configString->inc(); 98 | } 99 | return $scope; 100 | } 101 | 102 | /** 103 | * Create new Scope from a file. 104 | * 105 | * @param $filePath 106 | * @return Scope 107 | */ 108 | public static function fromFile($filePath) 109 | { 110 | return self::fromString(new File($filePath)); 111 | } 112 | 113 | /* 114 | * ========== Getters ========== 115 | */ 116 | 117 | /** 118 | * Get parent Directive. 119 | * 120 | * @return Directive|null 121 | */ 122 | public function getParentDirective() 123 | { 124 | return $this->parentDirective; 125 | } 126 | 127 | /* 128 | * ========== Setters ========== 129 | */ 130 | 131 | /** 132 | * Add a Directive to the list of this Scopes directives 133 | * 134 | * Adds the Directive and sets the Directives parent Scope to $this. 135 | * 136 | * @param Directive $directive 137 | * @return $this 138 | */ 139 | public function addDirective(Directive $directive) 140 | { 141 | if ($directive->getParentScope() !== $this) { 142 | $directive->setParentScope($this); 143 | } 144 | 145 | $this->directives[] = $directive; 146 | $this->addPrintable($directive); 147 | 148 | return $this; 149 | } 150 | 151 | /** 152 | * Add printable element. 153 | * 154 | * @param Printable $printable 155 | */ 156 | private function addPrintable(Printable $printable) 157 | { 158 | $this->printables[] = $printable; 159 | } 160 | 161 | /** 162 | * Set parent directive for this Scope. 163 | * 164 | * Sets parent directive for this Scope and also 165 | * sets the $parentDirective->setChildScope($this) 166 | * 167 | * @param Directive $parentDirective 168 | * @return $this 169 | */ 170 | public function setParentDirective(Directive $parentDirective) 171 | { 172 | $this->parentDirective = $parentDirective; 173 | 174 | if ($parentDirective->getChildScope() !== $this) { 175 | $parentDirective->setChildScope($this); 176 | } 177 | 178 | return $this; 179 | } 180 | 181 | /* 182 | * ========== Printable ========== 183 | */ 184 | 185 | /** 186 | * Pretty print with indentation. 187 | * 188 | * @param $indentLevel 189 | * @param int $spacesPerIndent 190 | * @return string 191 | */ 192 | public function prettyPrint($indentLevel, $spacesPerIndent = 4) 193 | { 194 | $resultString = ""; 195 | foreach ($this->printables as $printable) { 196 | $resultString .= $printable->prettyPrint($indentLevel + 1, $spacesPerIndent); 197 | } 198 | 199 | return $resultString; 200 | } 201 | 202 | public function __toString() 203 | { 204 | return $this->prettyPrint(-1); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/Text.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | * 11 | */ 12 | 13 | namespace RomanPitak\Nginx\Config; 14 | 15 | class Text 16 | { 17 | const CURRENT_POSITION = -1; 18 | 19 | /** @var string $data */ 20 | private $data; 21 | 22 | /** @var int $position */ 23 | private $position; 24 | 25 | /** 26 | * @param string $data 27 | */ 28 | public function __construct($data) 29 | { 30 | $this->position = 0; 31 | $this->data = $data; 32 | } 33 | 34 | /* 35 | * ========== Getters ========== 36 | */ 37 | 38 | /** 39 | * Returns one character of the string. 40 | * 41 | * Does not move the string pointer. Use inc() to move the pointer after getChar(). 42 | * 43 | * @param int $position If not specified, current character is returned. 44 | * @return string The current character (under the pointer). 45 | * @throws Exception When out of range 46 | */ 47 | public function getChar($position = self::CURRENT_POSITION) 48 | { 49 | if (self::CURRENT_POSITION === $position) { 50 | $position = $this->position; 51 | } 52 | 53 | if (!is_int($position)) { 54 | throw new Exception('Position is not int. ' . gettype($position)); 55 | } 56 | 57 | if ($this->eof()) { 58 | throw new Exception('Index out of range. Position: ' . $position . '.'); 59 | } 60 | 61 | return $this->data[$position]; 62 | } 63 | 64 | /** 65 | * Get the text from $position to the next end of line. 66 | * 67 | * Does not move the string pointer. 68 | * 69 | * @param int $position 70 | * @return string 71 | */ 72 | public function getRestOfTheLine($position = self::CURRENT_POSITION) 73 | { 74 | if (self::CURRENT_POSITION === $position) { 75 | $position = $this->position; 76 | } 77 | $text = ''; 78 | while ((false === $this->eof($position)) && (false === $this->eol($position))) { 79 | $text .= $this->getChar($position); 80 | $position++; 81 | } 82 | return $text; 83 | } 84 | 85 | /** 86 | * Is this the end of line? 87 | * 88 | * @param int $position 89 | * @return bool 90 | * @throws Exception 91 | */ 92 | public function eol($position = self::CURRENT_POSITION) 93 | { 94 | return (("\r" === $this->getChar($position)) || ("\n" === $this->getChar($position))); 95 | } 96 | 97 | /** 98 | * Is this line empty? 99 | * 100 | * @param int $position 101 | * @return bool 102 | */ 103 | public function isEmptyLine($position = self::CURRENT_POSITION) 104 | { 105 | $line = $this->getCurrentLine($position); 106 | return (0 === strlen(trim($line))); 107 | } 108 | 109 | /** 110 | * Get the current line. 111 | * 112 | * @param int $position 113 | * @return string 114 | */ 115 | public function getCurrentLine($position = self::CURRENT_POSITION) 116 | { 117 | if (self::CURRENT_POSITION === $position) { 118 | $position = $this->position; 119 | } 120 | 121 | $offset = $this->getLastEol($position); 122 | $length = $this->getNextEol($position) - $offset; 123 | return substr($this->data, $offset, $length); 124 | } 125 | 126 | /** 127 | * Get the position of the last (previous) EOL. 128 | * 129 | * @param int $position 130 | * @return int 131 | */ 132 | public function getLastEol($position = self::CURRENT_POSITION) 133 | { 134 | if (self::CURRENT_POSITION === $position) { 135 | $position = $this->position; 136 | } 137 | 138 | return strrpos(substr($this->data, 0, $position), "\n", 0); 139 | } 140 | 141 | /** 142 | * Get the position of the next EOL. 143 | * 144 | * @param int $position 145 | * @return int 146 | */ 147 | public function getNextEol($position = self::CURRENT_POSITION) 148 | { 149 | if (self::CURRENT_POSITION === $position) { 150 | $position = $this->position; 151 | } 152 | 153 | $eolPosition = strpos($this->data, "\n", $position); 154 | if (false === $eolPosition) { 155 | $eolPosition = strlen($this->data) - 1; 156 | } 157 | 158 | return $eolPosition; 159 | } 160 | 161 | /** 162 | * Is this the end of file (string) or beyond? 163 | * 164 | * @param int $position 165 | * @return bool 166 | */ 167 | public function eof($position = self::CURRENT_POSITION) 168 | { 169 | if (self::CURRENT_POSITION === $position) { 170 | $position = $this->position; 171 | } 172 | return (!isset($this->data[$position])); 173 | } 174 | 175 | /* 176 | * ========== Manipulators ========== 177 | */ 178 | 179 | /** 180 | * Move string pointer. 181 | * 182 | * @param int $inc 183 | */ 184 | public function inc($inc = 1) 185 | { 186 | $this->position += $inc; 187 | } 188 | 189 | /** 190 | * Move pointer (position) to the next EOL. 191 | * 192 | * @param int $position 193 | */ 194 | public function gotoNextEol($position = self::CURRENT_POSITION) 195 | { 196 | if (self::CURRENT_POSITION === $position) { 197 | $position = $this->position; 198 | } 199 | $this->position = $this->getNextEol($position); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/Directive.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | * 11 | */ 12 | 13 | namespace RomanPitak\Nginx\Config; 14 | 15 | class Directive extends Printable 16 | { 17 | /** @var string $name */ 18 | private $name; 19 | 20 | /** @var string $value */ 21 | private $value; 22 | 23 | /** @var Scope $childScope */ 24 | private $childScope = null; 25 | 26 | /** @var Scope $parentScope */ 27 | private $parentScope = null; 28 | 29 | /** @var Comment $comment */ 30 | private $comment = null; 31 | 32 | /** 33 | * @param string $name 34 | * @param string $value 35 | * @param Scope $childScope 36 | * @param Scope $parentScope 37 | * @param Comment $comment 38 | */ 39 | public function __construct( 40 | $name, 41 | $value = null, 42 | Scope $childScope = null, 43 | Scope $parentScope = null, 44 | Comment $comment = null 45 | ) { 46 | $this->name = $name; 47 | $this->value = $value; 48 | if (!is_null($childScope)) { 49 | $this->setChildScope($childScope); 50 | } 51 | if (!is_null($parentScope)) { 52 | $this->setParentScope($parentScope); 53 | } 54 | if (!is_null($comment)) { 55 | $this->setComment($comment); 56 | } 57 | } 58 | 59 | /* 60 | * ========== Factories ========== 61 | */ 62 | 63 | /** 64 | * Provides fluid interface. 65 | * 66 | * @param $name 67 | * @param null $value 68 | * @param Scope $childScope 69 | * @param Scope $parentScope 70 | * @param Comment $comment 71 | * @return Directive 72 | */ 73 | public static function create( 74 | $name, 75 | $value = null, 76 | Scope $childScope = null, 77 | Scope $parentScope = null, 78 | Comment $comment = null 79 | ) { 80 | return new self($name, $value, $childScope, $parentScope, $comment); 81 | } 82 | 83 | /** 84 | * @param \RomanPitak\Nginx\Config\Text $configString 85 | * @return self 86 | * @throws Exception 87 | */ 88 | public static function fromString(Text $configString) 89 | { 90 | $text = ''; 91 | while (false === $configString->eof()) { 92 | $char = $configString->getChar(); 93 | if ('{' === $char) { 94 | return self::newDirectiveWithScope($text, $configString); 95 | } 96 | if (';' === $char) { 97 | return self::newDirectiveWithoutScope($text, $configString); 98 | } 99 | $text .= $char; 100 | $configString->inc(); 101 | } 102 | throw new Exception('Could not create directive.'); 103 | } 104 | 105 | private static function newDirectiveWithScope( 106 | $nameString, 107 | Text $scopeString 108 | ) { 109 | $scopeString->inc(); 110 | list($name, $value) = self::processText($nameString); 111 | $directive = new Directive($name, $value); 112 | 113 | $comment = self::checkRestOfTheLineForComment($scopeString); 114 | if (false !== $comment) { 115 | $directive->setComment($comment); 116 | } 117 | 118 | $childScope = Scope::fromString($scopeString); 119 | $childScope->setParentDirective($directive); 120 | $directive->setChildScope($childScope); 121 | 122 | $scopeString->inc(); 123 | 124 | $comment = self::checkRestOfTheLineForComment($scopeString); 125 | if (false !== $comment) { 126 | $directive->setComment($comment); 127 | } 128 | 129 | return $directive; 130 | } 131 | 132 | private static function newDirectiveWithoutScope( 133 | $nameString, 134 | Text $configString 135 | ) { 136 | $configString->inc(); 137 | list($name, $value) = self::processText($nameString); 138 | $directive = new Directive($name, $value); 139 | 140 | $comment = self::checkRestOfTheLineForComment($configString); 141 | if (false !== $comment) { 142 | $directive->setComment($comment); 143 | } 144 | 145 | return $directive; 146 | } 147 | 148 | private static function checkRestOfTheLineForComment(Text $configString) 149 | { 150 | $restOfTheLine = $configString->getRestOfTheLine(); 151 | if (1 !== preg_match('/^\s*#/', $restOfTheLine)) { 152 | return false; 153 | } 154 | 155 | $commentPosition = strpos($restOfTheLine, '#'); 156 | $configString->inc($commentPosition); 157 | return Comment::fromString($configString); 158 | } 159 | 160 | private static function processText($text) 161 | { 162 | $result = self::checkKeyValue($text); 163 | if (is_array($result)) { 164 | return $result; 165 | } 166 | $result = self::checkKey($text); 167 | if (is_array($result)) { 168 | return $result; 169 | } 170 | throw new Exception('Text "' . $text . '" did not match pattern.'); 171 | } 172 | 173 | private static function checkKeyValue($text) { 174 | if (1 === preg_match('#^([a-z][a-z0-9._/+-]*)\s+([^;{]+)$#', $text, $matches)) { 175 | return array($matches[1], rtrim($matches[2])); 176 | } 177 | return false; 178 | } 179 | 180 | private static function checkKey($text) { 181 | if (1 === preg_match('#^([a-z][a-z0-9._/+-]*)\s*$#', $text, $matches)) { 182 | return array($matches[1], null); 183 | } 184 | return false; 185 | } 186 | 187 | /* 188 | * ========== Getters ========== 189 | */ 190 | 191 | /** 192 | * Get parent Scope 193 | * 194 | * @return Scope|null 195 | */ 196 | public function getParentScope() 197 | { 198 | return $this->parentScope; 199 | } 200 | 201 | /** 202 | * Get child Scope. 203 | * 204 | * @return Scope|null 205 | */ 206 | public function getChildScope() 207 | { 208 | return $this->childScope; 209 | } 210 | 211 | /** 212 | * Get the associated Comment for this Directive. 213 | * 214 | * @return Comment 215 | */ 216 | public function getComment() 217 | { 218 | if (is_null($this->comment)) { 219 | $this->comment = new Comment(); 220 | } 221 | return $this->comment; 222 | } 223 | 224 | /** 225 | * Does this Directive have a Comment associated with it? 226 | * 227 | * @return bool 228 | */ 229 | public function hasComment() 230 | { 231 | return (!$this->getComment()->isEmpty()); 232 | } 233 | 234 | /* 235 | * ========== Setters ========== 236 | */ 237 | 238 | /** 239 | * Sets the parent Scope for this Directive. 240 | * 241 | * @param Scope $parentScope 242 | * @return $this 243 | */ 244 | public function setParentScope(Scope $parentScope) 245 | { 246 | $this->parentScope = $parentScope; 247 | return $this; 248 | } 249 | 250 | /** 251 | * Sets the child Scope for this Directive. 252 | * 253 | * Sets the child Scope for this Directive and also 254 | * sets the $childScope->setParentDirective($this). 255 | * 256 | * @param Scope $childScope 257 | * @return $this 258 | */ 259 | public function setChildScope(Scope $childScope) 260 | { 261 | $this->childScope = $childScope; 262 | 263 | if ($childScope->getParentDirective() !== $this) { 264 | $childScope->setParentDirective($this); 265 | } 266 | 267 | return $this; 268 | } 269 | 270 | /** 271 | * Set the associated Comment object for this Directive. 272 | * 273 | * This will overwrite the existing comment. 274 | * 275 | * @param Comment $comment 276 | * @return $this 277 | */ 278 | public function setComment(Comment $comment) 279 | { 280 | $this->comment = $comment; 281 | return $this; 282 | } 283 | 284 | /** 285 | * Set the comment text for this Directive. 286 | * 287 | * This will overwrite the existing comment. 288 | * 289 | * @param $text 290 | * @return $this 291 | */ 292 | public function setCommentText($text) 293 | { 294 | $this->getComment()->setText($text); 295 | return $this; 296 | } 297 | 298 | /* 299 | * ========== Printing ========== 300 | */ 301 | 302 | /** 303 | * Pretty print with indentation. 304 | * 305 | * @param $indentLevel 306 | * @param int $spacesPerIndent 307 | * @return string 308 | */ 309 | public function prettyPrint($indentLevel, $spacesPerIndent = 4) 310 | { 311 | $indent = str_repeat(str_repeat(' ', $spacesPerIndent), $indentLevel); 312 | 313 | $resultString = $indent . $this->name; 314 | if (!is_null($this->value)) { 315 | $resultString .= " " . $this->value; 316 | } 317 | 318 | if (is_null($this->getChildScope())) { 319 | $resultString .= ";"; 320 | } else { 321 | $resultString .= " {"; 322 | } 323 | 324 | if (false === $this->hasComment()) { 325 | $resultString .= "\n"; 326 | } else { 327 | if (false === $this->getComment()->isMultiline()) { 328 | $resultString .= " " . $this->comment->prettyPrint(0, 0); 329 | } else { 330 | $comment = $this->getComment()->prettyPrint($indentLevel, $spacesPerIndent); 331 | $resultString = $comment . $resultString; 332 | } 333 | } 334 | 335 | if (!is_null($this->getChildScope())) { 336 | $resultString .= "" . $this->childScope->prettyPrint($indentLevel, $spacesPerIndent) . $indent . "}\n"; 337 | } 338 | 339 | return $resultString; 340 | } 341 | } 342 | --------------------------------------------------------------------------------