├── .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 | [](https://packagist.org/packages/romanpitak/nginx-config-processor)
4 | [](https://packagist.org/packages/romanpitak/nginx-config-processor)
5 | [](https://packagist.org/packages/romanpitak/nginx-config-processor)
6 | [](https://travis-ci.org/romanpitak/Nginx-Config-Processor)
7 | [](https://codeclimate.com/github/romanpitak/Nginx-Config-Processor)
8 | [](https://codeclimate.com/github/romanpitak/Nginx-Config-Processor/coverage)
9 | [](https://www.codacy.com/public/roman/Nginx-Config-Processor)
10 | [](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 |
--------------------------------------------------------------------------------