├── .gitignore ├── .php_cs ├── .phpspec ├── class.tpl └── specification.tpl ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── UPGRADE-2.0.md ├── UPGRADE-3.0.md ├── VERSIONING.md ├── bin └── tester.sh ├── composer.json ├── couscous.yml ├── doc ├── 01-tutorial.md ├── 02-use-cases.md ├── 03-reference.md ├── 04-vocabulary.md ├── 05-extending.md └── 06-exceptions.md ├── phpspec.yml.dist ├── phpunit.xml.dist ├── src └── Gnugat │ └── Redaktilo │ ├── Command │ ├── Command.php │ ├── CommandInvoker.php │ ├── LineInsertAboveCommand.php │ ├── LineInsertBelowCommand.php │ ├── LineRemoveCommand.php │ ├── LineReplaceAllCommand.php │ ├── LineReplaceCommand.php │ └── Sanitizer │ │ ├── InputSanitizer.php │ │ ├── LocationSanitizer.php │ │ └── TextSanitizer.php │ ├── Editor.php │ ├── EditorFactory.php │ ├── Exception │ ├── CommandNotFoundException.php │ ├── DifferentLineBreaksFoundException.php │ ├── Exception.php │ ├── FileNotFoundException.php │ ├── IOException.php │ ├── InvalidArgumentException.php │ ├── InvalidLineNumberException.php │ ├── NoFilenameGivenException.php │ ├── NotSupportedException.php │ └── PatternNotFoundException.php │ ├── File.php │ ├── Search │ ├── LineNumberSearchStrategy.php │ ├── LineRegexSearchStrategy.php │ ├── LineSearchStrategy.php │ ├── Php │ │ ├── Token.php │ │ └── TokenBuilder.php │ ├── PhpSearchStrategy.php │ ├── SameSearchStrategy.php │ ├── SearchEngine.php │ └── SearchStrategy.php │ ├── Service │ ├── ContentFactory.php │ ├── EditorBuilder.php │ ├── Filesystem.php │ └── TextToPhpConverter.php │ ├── Text.php │ └── Util │ └── StringUtil.php └── tests ├── example ├── BundleRegistrationTest.php ├── BundleRoutingTest.php └── DocumentationReformattingTest.php ├── fixtures ├── expectations │ ├── AppKernel.php │ ├── composer.json │ ├── doctrine.rst │ ├── life-of-brian-insert.txt │ ├── life-of-brian-remove.txt │ ├── life-of-brian-replace-all.txt │ ├── life-of-brian-replace.txt │ ├── routing.yml │ └── to-indent.py └── sources │ ├── AppKernel.php │ ├── composer.json │ ├── copy-me.txt │ ├── doctrine.rst │ ├── life-of-brian.txt │ ├── php-sample.php │ ├── routing.yml │ └── to-indent.py └── spec └── Gnugat └── Redaktilo ├── Command ├── CommandInvokerSpec.php ├── LineInsertAboveCommandSpec.php ├── LineInsertBelowCommandSpec.php ├── LineRemoveCommandSpec.php ├── LineReplaceAllCommandSpec.php ├── LineReplaceCommandSpec.php └── Sanitizer │ ├── LocationSanitizerSpec.php │ └── TextSanitizerSpec.php ├── EditorFactorySpec.php ├── EditorSpec.php ├── FileSpec.php ├── Search ├── LineRegexSearchStrategySpec.php ├── SameSearchStrategySpec.php └── SearchEngineSpec.php ├── Service ├── ContentFactorySpec.php ├── EditorBuilderSpec.php ├── FilesystemSpec.php └── TextToPhpConverterSpec.php └── TextSpec.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary files 2 | /tests/fixtures/copies/ 3 | .php_cs.cache 4 | .phpunit.result.cache 5 | 6 | # Dependencies 7 | 8 | /vendor/ 9 | /composer.lock 10 | 11 | # Couscous 12 | /.couscous/ 13 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | exclude('bin') 5 | ->exclude('vendor') 6 | ->exclude('tests/fixtures') 7 | ->in(__DIR__) 8 | ; 9 | 10 | return PhpCsFixer\Config::create() 11 | ->setRules([ 12 | '@Symfony' => true, 13 | 'visibility_required' => false, 14 | 'array_syntax' => [ 15 | 'syntax' => 'short', 16 | ], 17 | ]) 18 | ->setUsingCache(true) 19 | ->setFinder($finder) 20 | ; 21 | -------------------------------------------------------------------------------- /.phpspec/class.tpl: -------------------------------------------------------------------------------- 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 | namespace %namespace%; 13 | 14 | class %name% 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /.phpspec/specification.tpl: -------------------------------------------------------------------------------- 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 | namespace %namespace%; 13 | 14 | use PhpSpec\ObjectBehavior; 15 | 16 | class %name% extends ObjectBehavior 17 | { 18 | function it_() 19 | { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes between versions 2 | 3 | ## 1.7.4: PHP 8 4 | 5 | * added support for PHP 8 6 | 7 | ## 2.0.0: Symfony 5 / PHP 7.2 8 | 9 | * bumped requirement to Symfony 5 10 | * bumped requirement to PHP 7.2 11 | 12 | ## 1.7.4: PHP 8 13 | 14 | * added support for PHP 8 15 | 16 | ## 1.7.3: Symfony 4 17 | 18 | * added support for Symfony 4 19 | 20 | ## 1.7.2: No Final 21 | 22 | * removed final 23 | 24 | ## 1.7.1: Updated dependencies 25 | 26 | * added support to Symfony 3 27 | * added support to PHP 7 28 | * updated to phpspec 2.4 29 | * updated to PHPUnit 4.5 30 | * added support to PHPUnit 5 31 | 32 | ## 1.7.0: Brace yourselves, v2 is coming 33 | 34 | * added documentation to upgrade to 2.0 35 | * added Text#map 36 | * added deprecation messages 37 | 38 | ## 1.6.0: Text construction 39 | 40 | * added an optional filename argument to Editor#save 41 | * removed TextFactory 42 | * removed Text#__construct 43 | * removed LineBreak 44 | * added Text::fromArray 45 | * added Text::fromString 46 | * added StringUtil::detectLineBreak 47 | 48 | ## 1.5.0: Current line number Incrementation 49 | 50 | * added Text#decrementCurrentLineNumber 51 | * added Text#incrementCurrentLineNumber 52 | 53 | ## 1.4.0: Exception 54 | 55 | * added PatternNotFoundException 56 | * added NotSupportedException 57 | * added InvalidLineNumberException 58 | * added InvalidArgumentException 59 | * added IOException 60 | * added FileNotFoundException 61 | * added DifferentLineBreaksFoundException 62 | * added CommandNotFoundException 63 | * added Exception 64 | 65 | ## 1.3.0: Chicken Run 66 | 67 | * added ContentFactory 68 | * added LineReplaceAllCommand 69 | * added Editor#run 70 | 71 | ## 1.2.1: Fixed Backward Compatibility Break 72 | 73 | * fixed BC break by making command constructor arguments optional 74 | 75 | ## 1.2.0: InputSanitizers 76 | 77 | * added mix of line break management 78 | * fixed text first line getter setter 79 | * fixed LineRemoveCommand using array_splice 80 | * added LocationSanitizer 81 | * added TextSanitizer 82 | * added InputSanitizer interface 83 | 84 | ## 1.1.6: Applying fix for line removal 85 | 86 | * fixed line number after line removal 87 | 88 | ## 1.1.5: Fixed remove command 89 | 90 | * fixed line numbers after line removal 91 | 92 | ## 1.1.4: Fixed Text InvalidLineNumberException 93 | 94 | * added tests for search relative to the first line 95 | * added tests for line search with the immediate line above 96 | * fixed Text to actually throw InvalidLineNumberException 97 | 98 | ## 1.1.3: Fixed line search above 99 | 100 | * fixed LineSearchStrategy#findAbove 101 | 102 | ## 1.1.2: Fixed locations 103 | 104 | * fixed passing of 0 as location 105 | 106 | ## 1.1.1: Fixed exceptions 107 | 108 | * fixed order of arguments in PatternNotFoundException 109 | * fixed message in NotSupportedException and PatternNotFoundException 110 | 111 | ## 1.1.0: Convenience 112 | 113 | * deprecated string support from LineReplaceCommand 114 | * deprecated has from Editor 115 | * added callback support to LineReplaceCommand 116 | * added hasAbove and hasBelow to Editor 117 | * added loggable exceptions 118 | * added line getter and setter to Text 119 | * added priority to Search Strategies 120 | 121 | ## 1.0.0: Documentation 122 | 123 | * updated documentation 124 | 125 | ## 1.0.0-rc3: Length 126 | 127 | * added Text length 128 | 129 | ## 1.0.0-rc2: Composer improvements 130 | 131 | * lowered requirements 132 | * fixed PSR4 133 | * added keywords 134 | * added @pyrech to authors 135 | 136 | ## 1.0.0-rc1: Safe checks 137 | 138 | * added totalLineNumber to Text 139 | * added safe checks 140 | 141 | ## 1.0.0-beta2: Fixed last BC breaks 142 | 143 | * renamed under with below 144 | * removed File prefix from Editor's open/save 145 | 146 | ## 1.0.0-beta1: Stable API 147 | 148 | * moved FileFactory into Filesystem 149 | * removed newText from Editor 150 | 151 | ## 1.0.0-alpha7: New src layout 152 | 153 | * Updated EditorBuilder with extensible only services 154 | * Removed api tag from services 155 | * Moved EditorBuilder into Service 156 | * Moved Filesystem into Service 157 | * Moved TextFactory into Service 158 | * Moved FileFactory into Service 159 | * Moved TextToPhpConverter into Service 160 | * Renamed PhpContentConverter to TextToPhpConverter 161 | * Removed ContentConverter interface 162 | 163 | ## 1.0.0-alpha6: Text and line content 164 | 165 | * renamed LineContentConverter to LineBreak 166 | * used Text instead of File 167 | * removed constructor API 168 | * removed File read and write 169 | * injected lines instead of content in File 170 | * used FileFactory in Filesystem 171 | * created FileFactory 172 | * made File a Text 173 | * suffixed Editor open/save with "File" 174 | * added newText in Editor 175 | * created TextFactory 176 | * created Text 177 | 178 | ## 1.0.0-alpha5: BC break renaming 179 | 180 | * renamed `SearchStrategy` `previous`/`next` to `above`/`under` 181 | * renamed `Editor` `addBefore`/`addAfter` to `insertAbove`/`insertUnder` 182 | * renamed `Editor` `changeTo` to `rename` 183 | * renamed `Editor` `jumpUpTo`/`jumpDownTo` to `jumpAbove`/`jumpUnder` 184 | * improved the vocabulary with `Actions`, `Directions` and `Location` 185 | 186 | ## 1.0.0-alpha4: Fix current line 187 | 188 | * fixed current line update's responsability in commands 189 | 190 | ## 1.0.0-alpha3: Deprecations 191 | 192 | * removed `changeTo` from `File` 193 | 194 | ## 1.0.0-alpha2: Quality 195 | 196 | * fixed Insight analysis 15 197 | 198 | ## 1.0.0-alpha1: Commands, boolean finds and locations 199 | 200 | * removed `SubstringSearchStrategy` 201 | * removed `replaceWith` from `Editor` 202 | * added `LineInsertAboveCommand` 203 | * added `LineInsertUnderCommand` 204 | * removed `LineInsertCommand` 205 | * moved line management from `Editor` to commands 206 | * renamed `UnsupportedCommandException` into `CommandNotFoundException` 207 | * added before/after to the `Editor` jumpTo methods 208 | * removed `FactoryMethod` 209 | * removed `SearchStrategy`'s `has` method 210 | * added before/after to the `SearchStrategy` find methods 211 | * added abstract class `LineSearchStrategy` 212 | * renamed `LineSearchStrategy` to `SameSearchStrategy` 213 | * moved search exception throwing from `Search` to the `Editor` 214 | * added location for `Editor`'s manipulation methods 215 | * moved `NotSupportedException` from `Engine` to `Search` 216 | * replaced `ReplaceEngine` with `CommandInvoker` 217 | * removed `ReplaceStrategy` 218 | * added `InsertCommand` 219 | * added `RemoveCommand` 220 | * added `ReplaceCommand` 221 | 222 | ## 0.9.0: PhpSearchStrategy 223 | 224 | * added `PhpSearchStrategy` 225 | * moved `SearchEngine` from the `Engine` namespace to the `Search` namespace 226 | * moved `ReplaceEngine` from the `Engine` namespace to the `Replace` namespace 227 | 228 | ## 0.8.0: SearchStrategy and EditorFactory 229 | 230 | * removed `StaticContainer` in favor of `EditorBuilder` and `EditorFactory` 231 | * added `SubstringSearchStrategy` 232 | * fixed `tester.sh` exit status 233 | * added `LineRegexSearchStrategy` 234 | 235 | ## 0.7.2: Fixed routing test 236 | 237 | * fixed the routing test by removing the dependency on sf2's DI component 238 | 239 | ## 0.7.1: Fixed line break 240 | 241 | * fixed line break in line content converter's back method 242 | 243 | ## 0.7.0: ContentConverter 244 | 245 | * replaced introduction with tutorial 246 | * replaced architecture details with reference 247 | * added vocabulary (cursor and line) 248 | * added static DIC documentation 249 | * added factory methods documentation 250 | * added `BundleRouting` example 251 | * fixed `Editor::addAfter` by moving down the cursor 252 | * added `Line` factory method for empty ones 253 | * added `Filesystem` factory method to force creation 254 | * moved factory methods into `Gnugat\Redaktilo` 255 | * removed `Filesystem`'s `detectLineBreak` method 256 | * removed `File`'s `readlines` and `writelines` methods 257 | * injected `LineContentConverter` into: 258 | + `LineReplaceStrategy` 259 | + `LineSearchStrategy` 260 | + `LineNumberSearchStrategy` 261 | * added `LineContentConverter` 262 | * added `ContentConverter` 263 | 264 | ## 0.6.1: Fixed DIC 265 | 266 | * fixed private methods into public static ones 267 | 268 | ## 0.6.0: ReplaceEngine 269 | 270 | * moved Engines into thei own directory 271 | * added `ReplaceEngine` to comply to the open/closed principle 272 | * removed `File`'s `hasLine` 273 | * added a Dependency Injection Container 274 | * added a use case for line presence checking 275 | * added a use case for "documentation reformatting" 276 | * replaced Behat by PHPUnit for automated use cases 277 | 278 | ## 0.5.0: SearchEngine 279 | 280 | * added `SearchEngine` to comply to the open/closed principle 281 | 282 | ## 0.4.0: Line manipulations 283 | 284 | * added line replacement 285 | * added line removal 286 | * added checking of line presence 287 | 288 | ## 0.3.0: File coming out 289 | 290 | * added file existence check 291 | * added line break detection 292 | * added `bin/tester.sh` script 293 | * moved stateness from `Editor` to `File` 294 | * moved classes at the root 295 | * removed interfaces 296 | * compiled documentation 297 | * improved tests 298 | 299 | ## 0.2.0: Jump to 300 | 301 | * added jump to methods to Editor 302 | * removed autosave 303 | * added manual save 304 | * added usage documentation 305 | * added use cases documentation 306 | * removed installer 307 | 308 | ## 0.1.2: Fix open 309 | 310 | * fixed `openFile` to `open` 311 | 312 | ## 0.1.1: Continuous Integration 313 | 314 | * fixed Insight analysis 1 315 | * added travis configuration 316 | * added badges on README 317 | 318 | ## 0.1.0: Initial release 319 | 320 | * created file opening 321 | * created insertion of line after a given one 322 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Everybody should be able to help. Here's how you can make this project more 4 | awesome: 5 | 6 | 1. [Fork it](https://github.com/gnugat/redaktilo/fork_select) 7 | 2. improve it 8 | 3. submit a [pull request](https://help.github.com/articles/creating-a-pull-request) 9 | 10 | Your work will then be reviewed as soon as possible (suggestions about some 11 | changes, improvements or alternatives may be given). 12 | 13 | Here's some tips to make you the best contributor ever: 14 | 15 | * [Green tests](#green-tests) 16 | * [Standard code](#standard-code) 17 | * [Specifications](#specifications) 18 | * [Use cases](#use-cases) 19 | * [Keeping your fork up-to-date](#keeping-your-fork-up-to-date) 20 | 21 | ## Green tests 22 | 23 | Run the tests using the following script: 24 | 25 | ```console 26 | $ bin/tester.sh 27 | ``` 28 | 29 | ## Standard code 30 | 31 | Use [PHP CS fixer](http://cs.sensiolabs.org/) to make your code compliant with 32 | Redaktilo's coding standards: 33 | 34 | ```console 35 | $ php-cs-fixer fix --config=sf23 . 36 | ``` 37 | 38 | ## Specifications 39 | 40 | Redaktilo drives its development using [phpspec](http://www.phpspec.net/). 41 | 42 | First boostrap the code for the Specification: 43 | 44 | ```console 45 | $ phpspec describe 'Gnugat\Redaktilo\MyNewClass' 46 | ``` 47 | 48 | Then write the code for the Specification: 49 | 50 | ```console 51 | $ $EDITOR spec/Gnugat/Redaktilo/MyNewClass.php 52 | ``` 53 | 54 | Next, bootstrap the code for the corresponding clas: 55 | 56 | ```console 57 | $ phpspec run 58 | ``` 59 | 60 | Follow that by writing the code of the corresponding class: 61 | 62 | ```console 63 | $ $EDITOR src/Gnugat/Redaktilo/MyNewClass.php 64 | ``` 65 | 66 | Finally, execute the specifications: 67 | 68 | ``` 69 | $ phpspec run 70 | ``` 71 | 72 | They should be green! 73 | 74 | ## Use cases 75 | 76 | Redaktilo has been created to fulfill actual needs. To keep sure of it, use 77 | cases are created and are automated: they become part of the test suite. 78 | 79 | Have a look at `tests/examples`, you might add your own. 80 | 81 | ## Keeping your fork up-to-date 82 | 83 | To keep your fork up-to-date, you should track the upstream (original) one 84 | using the following command: 85 | 86 | ```console 87 | $ git remote add upstream https://github.com/gnugat/redaktilo.git 88 | ``` 89 | 90 | Then get the upstream changes: 91 | 92 | ```console 93 | git checkout main 94 | git pull --rebase origin main 95 | git pull --rebase upstream main 96 | git checkout 97 | git rebase main 98 | ``` 99 | 100 | Finally, publish your changes: 101 | 102 | ```console 103 | $ git push -f origin 104 | ``` 105 | 106 | Your pull request will be automatically updated. 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2015 Loïc Faugeron 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redaktilo 2 | 3 | Redaktilo allows you to find, insert, replace and remove lines using an 4 | editor-like object. 5 | 6 | *Because your code too needs an editor to manipulate files*. 7 | 8 | ## Getting started 9 | 10 | Use [Composer](http://getcomposer.org/) to install Redaktilo in your projects: 11 | 12 | ```console 13 | $ composer require "gnugat/redaktilo:^2.0" 14 | ``` 15 | 16 | Redaktilo provides an `Editor` class which can be instanciated using 17 | `EditorFactory`: 18 | 19 | ```php 20 | editor = $editor; 51 | $this->appKernelFilename = $appKernelFilename; 52 | } 53 | 54 | public function addBundle($bundle) 55 | { 56 | $appKernel = $this->editor->open($this->appKernelFilename); 57 | $newBundle = " new $bundle(),"; 58 | if ($this->editor->hasBelow($appKernel, $newBundle)) { 59 | $message = sprintf('Bundle "%s" is already defined in "AppKernel::registerBundles()".', $bundle); 60 | 61 | throw new \RuntimeException($message); 62 | } 63 | $this->editor->jumpBelow($appKernel, ' );'); 64 | $this->editor->insertAbove($appKernel, $newBundle); 65 | $this->editor->save($appKernel); 66 | 67 | return true; 68 | } 69 | } 70 | ``` 71 | 72 | As you can see it's easier to read and to understand than 73 | [the original PHP token parsing](https://github.com/sensiolabs/SensioGeneratorBundle/blob/8b7a33aa3d22388443b6de0b0cf184122e9f60d2/Manipulator/KernelManipulator.php). 74 | 75 | ## Further documentation 76 | 77 | You can see the current and past versions using one of the following: 78 | 79 | * the `git tag` command 80 | * the [releases page on Github](https://github.com/gnugat/redaktilo/releases) 81 | * the file listing the [changes between versions](CHANGELOG.md) 82 | 83 | You can find more documentation at the following links: 84 | 85 | * [copyright and MIT license](LICENSE) 86 | * [versioning and branching models](VERSIONING.md) 87 | * [contribution instructions](CONTRIBUTING.md) 88 | * [migration to 2.0 instructions](UPGRADE-2.0.md) 89 | 90 | Next readings: 91 | 92 | * [Tutorial](doc/01-tutorial.md) 93 | * [Use cases](doc/02-use-cases.md) 94 | * [Reference](doc/03-reference.md) 95 | * [Vocabulary](doc/04-vocabulary.md) 96 | * [Extending](doc/05-extending.md) 97 | * [Exceptions](doc/06-exceptions.md) 98 | -------------------------------------------------------------------------------- /UPGRADE-2.0.md: -------------------------------------------------------------------------------- 1 | # Backward Compatibility breaks in 2.0 2 | 3 | This file describes the step to follow in order to migrate your projects to 4 | Redaktilo 2.0. 5 | 6 | The changes done in 2.0 can be found in [CHANGELOG](./CHANGELOG.md). 7 | 8 | ## Symfony 5 9 | 10 | Redaktilo 2 bumps the Symfony Filesystem requirement to v5. 11 | 12 | ## PHP 7.2 13 | 14 | Redaktilo bumps the PHP requirement to 7.2. 15 | -------------------------------------------------------------------------------- /UPGRADE-3.0.md: -------------------------------------------------------------------------------- 1 | # Backward Compatibility breaks in 3.0 2 | 3 | This file describes the step to follow in order to migrate your projects to 4 | Redaktilo 3.0. 5 | 6 | The changes done in 3.0 can be found in [CHANGELOG](./CHANGELOG.md). 7 | 8 | ## Misc (1.1) 9 | 10 | Here's a list of features removed, with their replacement: 11 | 12 | * `Editor#has` has been removed, use `Editor#hasBelow` instead 13 | * `LineReplaceCommand` no longer accepts strings, use `Text#setLine` instead 14 | 15 | ## Sanitizers (1.2) 16 | 17 | The following commands now takes a `TextSanitizer` and a `LocationSanitizer` 18 | mandatory argument in their constructor: 19 | 20 | * `LineInsertAboveCommand` 21 | * `LineReplaceCommand` 22 | * `LineInsertBelowCommand` 23 | * `LineRemoveCommand` 24 | 25 | ## Exceptions (1.4) 26 | 27 | The following exceptions have been removed: 28 | 29 | * `Gnugat\Redaktilo\Command\CommandNotFoundException` 30 | * `Gnugat\Redaktilo\Search\NotSupportedException` 31 | * `Gnugat\Redaktilo\Search\PatternNotFoundException` 32 | * `Gnugat\Redaktilo\InvalidLineNumberException` 33 | * `Gnugat\Redaktilo\Service\DifferentLineBreaksFoundException` 34 | 35 | Please use their equivalent (same name) from the following namespace: 36 | `Gnugat\Redaktilo\Exception`. 37 | 38 | ## PHP and Number Search Removal (1.7) 39 | 40 | The following classes have been removed: 41 | 42 | * `LineNumberSearchStrategy`, use `Text#setCurrentLineNumber` instead 43 | * `PhpSearchStrategy` 44 | * `Php/Token` 45 | * `Php/TokenBuilder` 46 | -------------------------------------------------------------------------------- /VERSIONING.md: -------------------------------------------------------------------------------- 1 | # Versioning and branching models 2 | 3 | This file explains the versioning and branching models of this project. 4 | 5 | ## Versioning 6 | 7 | The versioning is inspired by [Semantic Versioning](http://semver.org/): 8 | 9 | > Given a version number MAJOR.MINOR.PATCH, increment the: 10 | > 11 | > 1. MAJOR version when you make incompatible API changes 12 | > 2. MINOR version when you add functionality in a backwards-compatible manner 13 | > 3. PATCH version when you make backwards-compatible bug fixes 14 | 15 | ### Public API 16 | 17 | Classes and methods marked with the `@api` tag are considered to be the public 18 | API of this project. 19 | 20 | ## Branching Model 21 | 22 | The branching is inspired by [@jbenet](https://github.com/jbenet) 23 | [simple git branching model](https://gist.github.com/jbenet/ee6c9ac48068889b0912): 24 | 25 | > 1. `main` must always be deployable. 26 | > 2. **all changes** are made through feature branches (pull-request + merge) 27 | > 3. rebase to avoid/resolve conflicts; merge in to `main` 28 | -------------------------------------------------------------------------------- /bin/tester.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | composer --quiet install --optimize-autoloader 4 | 5 | vendor/bin/phpspec run -fdot && 6 | vendor/bin/phpunit && 7 | vendor/bin/php-cs-fixer fix --dry-run 8 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gnugat/redaktilo", 3 | "license": "MIT", 4 | "type": "library", 5 | "description": "Find, insert, replace and remove lines with an Editor-like object", 6 | "keywords": ["editor", "lines", "find", "insert", "replace", "remove", "file", "edit"], 7 | "autoload": { 8 | "psr-4": { 9 | "Gnugat\\Redaktilo\\": "src/Gnugat/Redaktilo" 10 | } 11 | }, 12 | "autoload-dev": { 13 | "psr-4": { 14 | "example\\Gnugat\\Redaktilo\\": "tests/example" 15 | } 16 | }, 17 | "authors": [ 18 | { 19 | "name": "Loïc Faugeron", 20 | "email": "faugeron.loic@gmail.com", 21 | "homepage": "https://gnugat.github.io", 22 | "role": "Developer" 23 | }, 24 | { 25 | "name": "Loïck Piera", 26 | "email": "pyrech@gmail.com", 27 | "homepage": "https://loickpiera.com/", 28 | "role": "Developer" 29 | } 30 | ], 31 | "require": { 32 | "php": "^7.2 || ^8.0", 33 | "symfony/filesystem": "^5.0" 34 | }, 35 | "require-dev": { 36 | "friends-of-phpspec/phpspec-expect": "^3.1 || ^4.0", 37 | "friendsofphp/php-cs-fixer": "^2.16", 38 | "phpspec/phpspec": "^6.1 || ^7.0", 39 | "phpunit/phpunit": "^8.4" 40 | }, 41 | "extra": { 42 | "branch-alias": { 43 | "dev-main": "2.0-dev" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /couscous.yml: -------------------------------------------------------------------------------- 1 | template: 2 | url: https://github.com/CouscousPHP/Template-Light.git 3 | 4 | baseUrl: http://gnugat.github.io/redaktilo 5 | 6 | title: Redaktilo 7 | subTitle: An easy "line manipulation" PHP lib: jump, insert and do anything! 8 | 9 | github: 10 | user: gnugat 11 | repo: redaktilo 12 | 13 | menu: 14 | items: 15 | home: 16 | text: Home 17 | relativeUrl: 18 | tutorial: 19 | text: Tutorial 20 | relativeUrl: doc/01-tutorial.html 21 | use-cases: 22 | text: Use cases 23 | relativeUrl: doc/02-use-cases.html 24 | reference: 25 | text: Reference 26 | relativeUrl: doc/03-reference.html 27 | vocabulary: 28 | text: Vocabulary 29 | relativeUrl: doc/04-vocabulary.html 30 | extending: 31 | text: Extending 32 | relativeUrl: doc/05-extending.html 33 | exceptions: 34 | text: Exceptions 35 | relativeUrl: doc/06-exceptions.html 36 | -------------------------------------------------------------------------------- /doc/01-tutorial.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | This chapter shows you how to use Redaktilo and contains the following sections: 4 | 5 | * [The Editor](#the-editor) 6 | * [Creating an Editor](#creating-an-editor) 7 | * [Customizing the Editor](#customizing-the-editor) 8 | * [Editing Files](#editing-files) 9 | * [Navigating through the Content](#navigating-through-the-content) 10 | * [Manipulating a Line](#manipulating-a-line) 11 | * [Saving the Modifications](#saving-the-modifications) 12 | * [Next readings](#next-readings) 13 | * [Previous readings](#previous-readings) 14 | 15 | ## The Editor 16 | 17 | The only class you'll be using when you use Redaktilo is the `Editor` class. 18 | This class contains all methods you need to navigate and edit texts (it can also 19 | open and save files). 20 | The editor doesn't have any state, this allows you to use a single 21 | instance throughout the entire application while editing multiple different 22 | texts. 23 | 24 | ### Creating an Editor 25 | 26 | The recommend way to instantiate the `Editor` is by using the `EditorFactory`: 27 | 28 | ```php 29 | use Gnugat\Redaktilo\EditorFactory; 30 | 31 | $editor = EditorFactory::createEditor(); 32 | ``` 33 | 34 | This will create a default editor. 35 | 36 | ### Customizing the Editor 37 | 38 | You can also use the editor builder to customize some things of the editor: 39 | 40 | ```php 41 | // ... 42 | 43 | $editor = EditorFactory::createEditorBuilder() 44 | // ... customize the build (more on this later) 45 | ->getEditor(); 46 | ``` 47 | 48 | ## Editing Files 49 | 50 | Assume you have the following file: 51 | 52 | Bacon 53 | Egg 54 | Sausage 55 | 56 | First things first: you need to open the file. This can be done easily with the 57 | `Editor#open()` method: 58 | 59 | ```php 60 | // ... 61 | 62 | $montyMenu = $editor->open('monty-menu.txt'); 63 | ``` 64 | 65 | This method returns a `Text` instance. This object contains the content of the 66 | file and keeps track of the cursor. When opening the file, the cursor is set 67 | to line 0 ('Bacon'). 68 | 69 | In case the file does not exists, you can force the creation by passing `true` 70 | as the second argument to `Editor#open()`. 71 | 72 | ### Navigating through the Content 73 | 74 | A cursor has been set to the first line. You can move this cursor to any 75 | existing line: 76 | 77 | ```php 78 | // ... 79 | 80 | $editor->jumpBelow($montyMenu, 'Egg'); // Current line: 1 ('Egg') 81 | ``` 82 | 83 | As you can see, there's no need to add the line break character, Redaktilo will 84 | take care of it for you. 85 | 86 | You should note that the lookup is directional: 87 | 88 | ```php 89 | $editor->jumpBelow($montyMenu, 'Bacon'); // Throws \Gnugat\Redaktilo\Exception\PatternNotFoundException, because 'Bacon' is above the current line 90 | 91 | $editor->jumpAbove($montyMenu, 'Bacon'); // Current line: 0 ('Bacon') 92 | ``` 93 | 94 | The match is done only if the line value is exactly the same as the given one: 95 | 96 | ```php 97 | $editor->jumpBelow($montyMenu, 'E'); // Throws an exception. 98 | ``` 99 | 100 | If you just want to know if a line exists, you don't have to deal with 101 | exceptions, you can use `Editor#hasBelow()` and `Editor#hasAbove()` method instead: 102 | 103 | ```php 104 | $editor->hasBelow($montyMenu, 'Beans', 0); // false 105 | ``` 106 | 107 | If you need to go to the first occurence in the whole file (regardless of the 108 | current line), you can use: 109 | 110 | ```php 111 | // Jumps to the first line matching the pattern, starting from the line 0 112 | $editor->jumpBelow($montyMenu, '/eg/', 0); // Current line: 1 (which is 'Egg') 113 | ``` 114 | 115 | The lookup can also be done using regex: 116 | 117 | ```php 118 | $editor->jumpAbove($montyMenu, '/ac/'); // Current line: 0 (which is 'Bacon') 119 | ``` 120 | 121 | At last, you can also loop through all files using `Text#map()`: 122 | 123 | ```php 124 | use Gnugat\Redaktilo\Text; 125 | 126 | // ... 127 | $montyMenu->map(function ($line, $lineNumber, Text $text) { 128 | // this callable is executed for each line in the file 129 | }); 130 | ``` 131 | 132 | ### Manipulating a Line 133 | 134 | Now you're able to navigate through a file and while that's very important in 135 | order to edit a file, it doesn't help much if you can't manipulate lines. 136 | Luckily, Redaktilo contains lots of methods designed for this purpose. 137 | 138 | Using the `Text#setLine()` method, you can manipulate the current line: 139 | 140 | ```php 141 | // ... 142 | 143 | // ... navigate to the first line 144 | $text->setLine('Spam'); // Line 0 now contains 'Spam' instead of 'Bacon' 145 | ``` 146 | 147 | > **Note**: Editor provides a `replace` method which accepts callbacks: 148 | > 149 | > ```php 150 | > // Will be passed the given or current line 151 | > $replace = function ($line) { 152 | > return strtoupper($line); // the line will be replaced by the returned value 153 | > }; 154 | > $editor->replace($text, $replace, $location); 155 | > ``` 156 | 157 | You can also insert lines below or above the current line: 158 | 159 | ```php 160 | // ... 161 | 162 | $editor->insertAbove($montyMenu, 'Beans'); // inserts a line 'Beans' above Line 0 163 | $editor->insertBelow($montyMenu, 'Bacon'); // inserts a line 'Bacon' below line 0 164 | ``` 165 | 166 | Please note that the cursor moves to the inserted line. 167 | 168 | By default, all the manipulation methods work from the current line. If you would 169 | like to manipulate a given line, you can pass its number as the third parameter: 170 | 171 | ```php 172 | $editor->insertAbove($montyMenu, 'Spam', 23); // Line inserted above the line number 23. 173 | ``` 174 | 175 | At last, you can also delete lines: 176 | 177 | ```php 178 | // ... 179 | $editor->remove($montyMenu); // Removes the current line 180 | ``` 181 | 182 | ### Saving the Modifications 183 | 184 | For now the modification is only done in memory, to actually apply your changes 185 | to the file you need to save it: 186 | 187 | ```php 188 | // ... 189 | 190 | $editor->save($montyMenu); 191 | ``` 192 | 193 | The resulting file will be: 194 | 195 | Beans 196 | Spam 197 | Egg 198 | Sausage 199 | 200 | ## Next readings 201 | 202 | * [Use cases](02-use-cases.md) 203 | * [Reference](03-reference.md) 204 | * [Vocabulary](04-vocabulary.md) 205 | * [Extending](05-extending.md) 206 | * [Exceptions](06-exceptions.md) 207 | 208 | ## Previous readings 209 | 210 | * [README](../README.md) 211 | -------------------------------------------------------------------------------- /doc/02-use-cases.md: -------------------------------------------------------------------------------- 1 | # Use cases 2 | 3 | Redaktilo has been created to meet some actual needs, which is why its 4 | Test Suite contains an Example section. 5 | 6 | Let's discover the use cases which led to the creation of Redaktilo: 7 | 8 | * [YAML configuration edition](#yaml-configuration-edition) 9 | * [JSON file edition](#json-file-edition) 10 | * [PHP source code edition](#php-source-code-edition) 11 | * [Next readings](#next-readings) 12 | * [Previous readings](#previous-readings) 13 | 14 | ## YAML configuration edition 15 | 16 | Many projects use the YAML format in their configuration files. 17 | 18 | If you need to add a new parameter, you could use the 19 | [Symfony2 YAML component](http://symfony.com/doc/current/components/yaml/index.html), 20 | which is a small library allowing you to convert a YAML file to a PHP array and 21 | vis-versa. 22 | 23 | The only problem with it: it doesn't keep comments or empty lines. 24 | 25 | Redaktilo fits perfectly for the job. 26 | 27 | > **Note**: see `BundleRoutingTest` for an actual implementation. 28 | 29 | ## JSON file edition 30 | 31 | Some projects use JSON files (like [Composer](https://getcomposer.org/)). 32 | 33 | Just like with Symfony2 YAML component, you could use `json_encode` and 34 | `json_decode` to edit these files, but you would lose empty lines and custom 35 | indentation. 36 | 37 | Redaktilo is again a good candidate. 38 | 39 | ## PHP source code edition 40 | 41 | [GnugatWizardBundle](https://github.com/gnugat/GnugatWizardBundle) automatically 42 | registers bundle installed using composer in a Symfony2 application. 43 | 44 | To do so, it edits the `app/AppKernel.php` file using 45 | [SensioGeneratorBundle](https://github.com/sensiolabs/SensioGeneratorBundle)'s 46 | [KernelManipulator](https://github.com/sensiolabs/SensioGeneratorBundle/blob/8b7a33aa3d22388443b6de0b0cf184122e9f60d2/Manipulator/KernelManipulator.php). 47 | 48 | This class is a little over engineered as it parses PHP tokens, and doesn't 49 | allow to register bundle for a special environment, meaning that 50 | GnugatWizardBundle will need to create its own KernelManipulator. 51 | 52 | The result of a simpler approach (parsing lines instead of PHP tokens) is 53 | Redaktilo! 54 | 55 | > **Note**: see `BundleRegistrationTest` for an actual implementation. 56 | 57 | ## Next readings 58 | 59 | * [Reference](03-reference.md) 60 | * [Vocabulary](04-vocabulary.md) 61 | * [Extending](05-extending.md) 62 | * [Exceptions](06-exceptions.md) 63 | 64 | ## Previous readings 65 | 66 | * [README](../README.md) 67 | * [Tutorial](01-tutorial.md) 68 | -------------------------------------------------------------------------------- /doc/03-reference.md: -------------------------------------------------------------------------------- 1 | # Redaktilo Code Reference 2 | 3 | * [Editor API](#editor-api) 4 | * [Filesystem operations](#filesystem-operations) 5 | * [Content navigation](#content-navigation) 6 | * [Content manipulation](#content-manipulation) 7 | * [Commands](#commands) 8 | * [Text API](#text-api) 9 | * [Side note on LineBreak](#side-note-on-linebreak) 10 | * [File API](#file-api) 11 | 12 | ## Editor API 13 | 14 | The main stateless service: 15 | 16 | ```php 17 | open('/tmp/new.txt'); 68 | } catch (\Gnugat\Redaktilo\Exception\FileNotFoundException $e) { 69 | // The file doesn't exist 70 | } 71 | $file = $editor->open('/tmp/new.txt', true); // Forces file creation when it doesn't exist 72 | 73 | // ... Make some manipulation on the file 74 | 75 | $editor->save($file); // Actually writes on the filesystem 76 | ``` 77 | 78 | ### Content navigation 79 | 80 | `Editor` relies on `SearchEngine` in order to find a given pattern in a `Text` 81 | and provides by default the following `SearchStrategies`: 82 | 83 | * regular expression 84 | * strict equality (`===`) 85 | 86 | If the pattern isn't found, or if the pattern isn't supported by any strategies 87 | an exception will be thrown. If the pattern is found, the `Text`'s current line 88 | number will be set to it. 89 | 90 | The search is done relatively to the current line (or, if the third argument is 91 | given, to the given location): `jumpAbove` will start from it and then the line 92 | above, etc until the top is reached while `jumpBelow` will go downward until the 93 | bottom is reached. 94 | 95 | In order to check the presence of a pattern without having to jump to the line 96 | found, `hasAbove` and `hasBelow` methods can be used: it doesn't throw any 97 | exceptions (checks from top to bottom). 98 | 99 | ```php 100 | open('/tmp/life-of-brian.txt', true); 107 | try { 108 | $editor->jumpAbove($file, '[A guard sniggers]'); // strict equality 109 | } catch (\Gnugat\Redaktilo\Exception\NotSupportedException $e) { 110 | // The pattern isn't supported by any registered strategy (shouldn't occur often) 111 | } catch (\Gnugat\Redaktilo\Exception\PatternNotFoundException $e) { 112 | // The pattern hasn't been found in the file 113 | } 114 | if ($editor->hasBelow($file, '/sniggers/', 0)) { // regular expression 115 | // The pattern exists. 116 | } 117 | ``` 118 | 119 | > **Note**: to jump to a given line number, you can directly use: 120 | > `$text->setCurrentLineNumber($x);`. 121 | 122 | ### Content manipulation 123 | 124 | Manipulations are done by default to the current line (or, if the third argument 125 | is given, to the given location). 126 | 127 | Inserting a new line will set the current one to it. 128 | 129 | ```php 130 | open('/tmp/spam-menu.txt', true); 141 | $editor->insertAbove($spamMenu, 'Egg'); // Current line number: 0 142 | $editor->insertBelow($spamMenu, 'Bacon'); // Current line number: 1 143 | $editor->replace($spamMenu, $replace); 144 | $editor->replaceAll($spamMenu, '/*/', 'Spam'); 145 | $editor->remove($spamMenu); // Current line number: 0 146 | 147 | $editor->save($spamMenu, '/tmp/spam-menu.txt'); // Necessary to actually apply the changes on the filesystem 148 | ``` 149 | 150 | ### Commands 151 | 152 | You can define your own commands and use them through `Editor#run()`. 153 | 154 | ## Text API 155 | 156 | One of the main entity: 157 | 158 | ```php 159 | **Important**: `lines` is an array of string stripped from their line break 191 | > character. 192 | 193 | If you need to manipulate a simple string you can use `Text::fromString`: 194 | 195 | ```php 196 | **Important**: please note that upon creation, the current line number is 205 | > initialized to the first line: `0` (array indexed). 206 | 207 | ### Side note on Line Breaks 208 | 209 | A `StringUtil::detectLineBreak` method is used to find the line break, this is 210 | done using the following rules: 211 | 212 | * `\r\n` for windows 213 | * `\n` for any other operating system 214 | * `PHP_EOL` if no line ending has been found 215 | 216 | ## File API 217 | 218 | The other main entity: 219 | 220 | ```php 221 | open('/tmp/and-now-for-something-completly-different.txt'); 244 | // ... Edit the file 245 | $editor->save($file); // Actually writes on the filesystem 246 | 247 | ## Next readings 248 | 249 | * [Vocabulary](04-vocabulary.md) 250 | * [Extending](05-extending.md) 251 | * [Exceptions](06-exceptions.md) 252 | 253 | ## Previous readings 254 | 255 | * [README](../README.md) 256 | * [Tutorial](01-tutorial.md) 257 | * [Use cases](02-use-cases.md) 258 | -------------------------------------------------------------------------------- /doc/04-vocabulary.md: -------------------------------------------------------------------------------- 1 | # Vocabulary 2 | 3 | * [Actions](#actions) 4 | * [insert](#insert) 5 | * [remove](#remove) 6 | * [replace](#replace) 7 | * [Cursor](#cursor) 8 | * [Directions](#directions) 9 | * [above](#above) 10 | * [below](#below) 11 | * [Editor](#editor) 12 | * [File](#file) 13 | * [Line](#line) 14 | * [Location](#location) 15 | * [Redaktilo](#redaktilo) 16 | * [Text](#text) 17 | 18 | ## Actions 19 | 20 | Here's the vocabulary for the possible actions on a line. 21 | 22 | ### insert 23 | 24 | Should be prefered over the word `add`. 25 | 26 | ### remove 27 | 28 | Should be prefered over the word `delete`. 29 | 30 | ### replace 31 | 32 | Should be prefered over the word `change`. 33 | 34 | ## Cursor 35 | 36 | Also used to mean the current line. 37 | 38 | An indicator used to know the position in the text. 39 | 40 | This is useful when a pattern occurs many times in the text: it enables the 41 | editor to select the wanted one. 42 | 43 | The cursor also enables to manipulate the selected element. 44 | 45 | ## Directions 46 | 47 | Here's the vocabulary to locate something relatively in a collection of lines. 48 | 49 | ### above 50 | 51 | Should be prefered over the words `over`, `before`, `previous` or `up`. 52 | 53 | ### below 54 | 55 | Should be prefered over the words `under`, `after`, `next` or `down`. 56 | 57 | ## Editor 58 | 59 | Also called "text editor". 60 | 61 | A piece of software which is able to change the content of a text. 62 | In the case of Redaktilo, the editor is an object provided by a library. 63 | 64 | ## File 65 | 66 | Also called "text file", to be opposed to "binary file". 67 | 68 | See [Text](#text). 69 | 70 | ## Line 71 | 72 | The unit with which **Redaktilo** works. It's a simple string which ends at the 73 | line break: 74 | 75 | * `\r\n` for texts created on Windows 76 | * `\n` for texts created on the other operating systems 77 | 78 | To make it easier for the developers, **Redaktilo** takes care of the line 79 | break, so you should only provide it with a string stripped of it. 80 | 81 | ## Location 82 | 83 | The given line number, to which you can relatively search or do something. 84 | 85 | ## Redaktilo 86 | 87 | This means `editor` in esperanto. Technically `Tekstoredaktilo` should have been 88 | used (`text editor`), but it was a bit too long for a project name. 89 | 90 | ## Text 91 | 92 | Can contain: 93 | 94 | * plain text 95 | * JSON 96 | * YAML 97 | * PHP 98 | * etc... 99 | 100 | ## Next readings 101 | 102 | * [Extending](05-extending.md) 103 | * [Exceptions](06-exceptions.md) 104 | 105 | ## Previous readings 106 | 107 | * [README](../README.md) 108 | * [Tutorial](01-tutorial.md) 109 | * [Use cases](02-use-cases.md) 110 | * [Reference](03-reference.md) 111 | -------------------------------------------------------------------------------- /doc/05-extending.md: -------------------------------------------------------------------------------- 1 | # Redaktilo Extension Points 2 | 3 | * [Search](#search) 4 | * [SearchStrategy API](#searchstrategy-api) 5 | * [Commands](#commands) 6 | * [Command API](#command-api) 7 | * [Input Sanitizers](#input-sanitizers) 8 | 9 | ## Search 10 | 11 | Use the `EditorBuilder` in order to register custom `SearchStrategy` 12 | implementations: 13 | 14 | ```php 15 | addSearchStrategy($strategy); 24 | 25 | $editor = $builder->getEditor(); 26 | ``` 27 | 28 | The strategy will then be automatically used (if it's the first to support the 29 | given pattern) when calling one of the following `Editor` method: 30 | 31 | * `jumpAbove` 32 | * `jumpBelow` 33 | * `hasAbove` 34 | * `hasBelow` 35 | 36 | If your strategy should be used instead of another already registered strategy 37 | (ie. they support the same pattern), you can give it a higher priority: 38 | 39 | ```php 40 | $builder->addSearchStrategy($strategy, 50); 41 | ``` 42 | 43 | > **Important**: The higher the priority is, the sooner the strategy will be 44 | > returned if it supports the given pattern. 45 | 46 | > **Note**:A default priority of 0 is assigned to strategies if you don't specify 47 | > it. 48 | 49 | ### SearchStrategy API 50 | 51 | A lookup strategy supporting a specific kind of pattern: 52 | 53 | ```php 54 | addCommand($command); 88 | $commandInvoker = $builder->getCommandInvoker(); 89 | $editor = $builder->getEditor(); 90 | $file = $editor->open('/tmp/menu_spam.txt', true); 91 | 92 | $editor->run('do_something', array('text' => $file)); 93 | ``` 94 | 95 | `Editor` actually uses the `CommandInvoker` in its following methods: 96 | 97 | * `insertAbove` 98 | * `insertBelow` 99 | * `remove` 100 | * `replace` 101 | * `replaceAll` 102 | * `run` 103 | 104 | ### Command API 105 | 106 | Executes a task with the given input: 107 | 108 | ```php 109 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | ./tests/example 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Command/Command.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 | namespace Gnugat\Redaktilo\Command; 13 | 14 | /** 15 | * Executes a task with the given input. 16 | * 17 | * @api 18 | */ 19 | interface Command 20 | { 21 | /** @return string */ 22 | public function getName(); 23 | 24 | /** @param array $input */ 25 | public function execute(array $input); 26 | } 27 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Command/CommandInvoker.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 | namespace Gnugat\Redaktilo\Command; 13 | 14 | use Gnugat\Redaktilo\Exception\CommandNotFoundException; 15 | 16 | /** 17 | * Executes a command with the given input. 18 | */ 19 | class CommandInvoker 20 | { 21 | /** @var Command[] */ 22 | private $commands = []; 23 | 24 | /** @param Command $command */ 25 | public function addCommand(Command $command) 26 | { 27 | $this->commands[$command->getName()] = $command; 28 | } 29 | 30 | /** 31 | * @param string $name 32 | * 33 | * @throws CommandNotFoundException 34 | */ 35 | public function run($name, array $input) 36 | { 37 | if (!isset($this->commands[$name])) { 38 | throw new CommandNotFoundException($name, $this->commands); 39 | } 40 | $this->commands[$name]->execute($input); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Command/LineInsertAboveCommand.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 | namespace Gnugat\Redaktilo\Command; 13 | 14 | use Gnugat\Redaktilo\Command\Sanitizer\LocationSanitizer; 15 | use Gnugat\Redaktilo\Command\Sanitizer\TextSanitizer; 16 | 17 | /** 18 | * Inserts the given addition in the given text above the given location. 19 | */ 20 | class LineInsertAboveCommand implements Command 21 | { 22 | /** @var TextSanitizer */ 23 | private $textSanitizer; 24 | 25 | /** @var LocationSanitizer */ 26 | private $locationSanitizer; 27 | 28 | public function __construct(TextSanitizer $textSanitizer, LocationSanitizer $locationSanitizer) 29 | { 30 | $this->textSanitizer = $textSanitizer; 31 | $this->locationSanitizer = $locationSanitizer; 32 | } 33 | 34 | /** {@inheritdoc} */ 35 | public function execute(array $input) 36 | { 37 | $text = $this->textSanitizer->sanitize($input); 38 | $location = $this->locationSanitizer->sanitize($input); 39 | 40 | $addition = $input['addition']; 41 | 42 | $lines = $text->getLines(); 43 | array_splice($lines, $location, 0, $addition); 44 | $text->setLines($lines); 45 | 46 | $text->setCurrentLineNumber($location); 47 | } 48 | 49 | /** {@inheritdoc} */ 50 | public function getName() 51 | { 52 | return 'insert_above'; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Command/LineInsertBelowCommand.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 | namespace Gnugat\Redaktilo\Command; 13 | 14 | use Gnugat\Redaktilo\Command\Sanitizer\LocationSanitizer; 15 | use Gnugat\Redaktilo\Command\Sanitizer\TextSanitizer; 16 | 17 | /** 18 | * Inserts the given addition in the given text below the given location. 19 | */ 20 | class LineInsertBelowCommand implements Command 21 | { 22 | /** @var TextSanitizer */ 23 | private $textSanitizer; 24 | 25 | /** @var LocationSanitizer */ 26 | private $locationSanitizer; 27 | 28 | public function __construct(TextSanitizer $textSanitizer, LocationSanitizer $locationSanitizer) 29 | { 30 | $this->textSanitizer = $textSanitizer; 31 | $this->locationSanitizer = $locationSanitizer; 32 | } 33 | 34 | /** {@inheritdoc} */ 35 | public function execute(array $input) 36 | { 37 | $text = $this->textSanitizer->sanitize($input); 38 | $location = 1 + $this->locationSanitizer->sanitize($input); 39 | 40 | $addition = $input['addition']; 41 | 42 | $lines = $text->getLines(); 43 | array_splice($lines, $location, 0, $addition); 44 | $text->setLines($lines); 45 | 46 | $text->setCurrentLineNumber($location); 47 | } 48 | 49 | /** {@inheritdoc} */ 50 | public function getName() 51 | { 52 | return 'insert_below'; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Command/LineRemoveCommand.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 | namespace Gnugat\Redaktilo\Command; 13 | 14 | use Gnugat\Redaktilo\Command\Sanitizer\LocationSanitizer; 15 | use Gnugat\Redaktilo\Command\Sanitizer\TextSanitizer; 16 | 17 | /** 18 | * Removes the given location in the given text. 19 | */ 20 | class LineRemoveCommand implements Command 21 | { 22 | /** @var TextSanitizer */ 23 | private $textSanitizer; 24 | 25 | /** @var LocationSanitizer */ 26 | private $locationSanitizer; 27 | 28 | public function __construct(TextSanitizer $textSanitizer, LocationSanitizer $locationSanitizer) 29 | { 30 | $this->textSanitizer = $textSanitizer; 31 | $this->locationSanitizer = $locationSanitizer; 32 | } 33 | 34 | /** {@inheritdoc} */ 35 | public function execute(array $input) 36 | { 37 | $text = $this->textSanitizer->sanitize($input); 38 | $location = $this->locationSanitizer->sanitize($input); 39 | 40 | $lines = $text->getLines(); 41 | array_splice($lines, $location, 1); 42 | $text->setLines($lines); 43 | 44 | $lineNumber = ($location === $text->getLength()) ? $location - 1 : $location; 45 | $text->setCurrentLineNumber($lineNumber); 46 | } 47 | 48 | /** {@inheritdoc} */ 49 | public function getName() 50 | { 51 | return 'remove'; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Command/LineReplaceAllCommand.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 | namespace Gnugat\Redaktilo\Command; 13 | 14 | use Gnugat\Redaktilo\Command\Sanitizer\TextSanitizer; 15 | use Gnugat\Redaktilo\Service\ContentFactory; 16 | use Gnugat\Redaktilo\Service\TextFactory; 17 | use Gnugat\Redaktilo\Text; 18 | 19 | /** 20 | * Replaces all occurences of pattern in Text by given replacement. 21 | */ 22 | class LineReplaceAllCommand implements Command 23 | { 24 | /** @var ContentFactory */ 25 | private $contentFactory; 26 | 27 | /** @var TextSanitizer */ 28 | private $textSanitizer; 29 | 30 | /** 31 | * @param TextFactory $textFactory 32 | */ 33 | public function __construct(ContentFactory $contentFactory, TextSanitizer $textSanitizer) 34 | { 35 | $this->contentFactory = $contentFactory; 36 | $this->textSanitizer = $textSanitizer; 37 | } 38 | 39 | /** {@inheritdoc} */ 40 | public function execute(array $input) 41 | { 42 | $text = $this->textSanitizer->sanitize($input); 43 | $pattern = $input['pattern']; 44 | $replacement = $input['replacement']; 45 | 46 | $content = $this->contentFactory->make($text); 47 | $replacedContent = preg_replace($pattern, $replacement, $content); 48 | $replacedText = Text::fromString($replacedContent); 49 | $text->setLines($replacedText->getLines()); 50 | } 51 | 52 | /** {@inheritdoc} */ 53 | public function getName() 54 | { 55 | return 'replace_all'; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Command/LineReplaceCommand.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 | namespace Gnugat\Redaktilo\Command; 13 | 14 | use Gnugat\Redaktilo\Command\Sanitizer\LocationSanitizer; 15 | use Gnugat\Redaktilo\Command\Sanitizer\TextSanitizer; 16 | use Gnugat\Redaktilo\Exception\InvalidArgumentException; 17 | 18 | /** 19 | * Replaces the given location in the given text with the given replacement. 20 | */ 21 | class LineReplaceCommand implements Command 22 | { 23 | /** @var TextSanitizer */ 24 | private $textSanitizer; 25 | 26 | /** @var LocationSanitizer */ 27 | private $locationSanitizer; 28 | 29 | public function __construct(TextSanitizer $textSanitizer, LocationSanitizer $locationSanitizer) 30 | { 31 | $this->textSanitizer = $textSanitizer; 32 | $this->locationSanitizer = $locationSanitizer; 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | * 38 | * @throws InvalidArgumentException If replacement isn't valid 39 | */ 40 | public function execute(array $input) 41 | { 42 | $text = $this->textSanitizer->sanitize($input); 43 | $location = $this->locationSanitizer->sanitize($input); 44 | 45 | if (!is_callable($input['replacement'])) { 46 | throw new InvalidArgumentException('Invalid replacement'); 47 | } 48 | 49 | $line = $text->getLine($location); 50 | $replacement = $input['replacement']($line); 51 | 52 | $text->setLine($replacement, $location); 53 | $text->setCurrentLineNumber($location); 54 | } 55 | 56 | /** {@inheritdoc} */ 57 | public function getName() 58 | { 59 | return 'replace'; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Command/Sanitizer/InputSanitizer.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 | namespace Gnugat\Redaktilo\Command\Sanitizer; 13 | 14 | /** 15 | * Implementations are used by Commands to sanitize the given input. 16 | * 17 | * @api 18 | */ 19 | interface InputSanitizer 20 | { 21 | /** 22 | * @return mixed 23 | */ 24 | public function sanitize(array $input); 25 | } 26 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Command/Sanitizer/LocationSanitizer.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 | namespace Gnugat\Redaktilo\Command\Sanitizer; 13 | 14 | use Gnugat\Redaktilo\Exception\InvalidLineNumberException; 15 | 16 | class LocationSanitizer implements InputSanitizer 17 | { 18 | /** @var TextSanitizer */ 19 | private $textSanitizer; 20 | 21 | /** @param TextSanitizer $textSanitizer */ 22 | public function __construct(TextSanitizer $textSanitizer) 23 | { 24 | $this->textSanitizer = $textSanitizer; 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | * 30 | * @return int 31 | */ 32 | public function sanitize(array $input) 33 | { 34 | $text = $this->textSanitizer->sanitize($input); 35 | $location = isset($input['location']) ? $input['location'] : null; 36 | if (null === $location) { 37 | return $text->getCurrentLineNumber(); 38 | } 39 | if (!is_int($location)) { 40 | throw new InvalidLineNumberException($location, $text, 'The line number should be an integer'); 41 | } 42 | if ($location < 0) { 43 | throw new InvalidLineNumberException($location, $text, 'The line number should be positive'); 44 | } 45 | if ($location >= $text->getLength()) { 46 | throw new InvalidLineNumberException($location, $text, 'The line number should be strictly lower than the number of lines'); 47 | } 48 | 49 | return $location; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Command/Sanitizer/TextSanitizer.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 | namespace Gnugat\Redaktilo\Command\Sanitizer; 13 | 14 | use Gnugat\Redaktilo\Exception\InvalidArgumentException; 15 | use Gnugat\Redaktilo\Text; 16 | 17 | class TextSanitizer implements InputSanitizer 18 | { 19 | /** 20 | * {@inheritdoc} 21 | * 22 | * @return Text 23 | * 24 | * @throws InvalidArgumentException If the text parameter is missing 25 | * @throws InvalidArgumentException If the text parameter is not an instance of Text 26 | */ 27 | public function sanitize(array $input) 28 | { 29 | if (!isset($input['text'])) { 30 | throw new InvalidArgumentException('A \'text\' entry should have been given in the input array'); 31 | } 32 | if (!is_object($input['text']) || !($input['text'] instanceof Text)) { 33 | throw new InvalidArgumentException('The \'text\' entry should be an instance of Text'); 34 | } 35 | 36 | return $input['text']; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Editor.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 | namespace Gnugat\Redaktilo; 13 | 14 | use Gnugat\Redaktilo\Command\CommandInvoker; 15 | use Gnugat\Redaktilo\Exception\PatternNotFoundException; 16 | use Gnugat\Redaktilo\Search\SearchEngine; 17 | use Gnugat\Redaktilo\Service\Filesystem; 18 | 19 | /** 20 | * Provides convenient methods for the following filesystem operations:. 21 | * 22 | * + opening/creating files 23 | * + createing text 24 | * + saving files 25 | * 26 | * Provides convenient methods for the following text operations: 27 | * 28 | * + looking for given lines and setting the current one to it 29 | * + inserting given lines above/below the current one or the given one 30 | * + replacing the current line or the given one 31 | * + removing the current line or the given one 32 | * 33 | * @api 34 | */ 35 | class Editor 36 | { 37 | /** @var Filesystem */ 38 | private $filesystem; 39 | 40 | /** @var SearchEngine */ 41 | private $searchEngine; 42 | 43 | /** @var CommandInvoker */ 44 | private $commandInvoker; 45 | 46 | public function __construct( 47 | Filesystem $filesystem, 48 | SearchEngine $searchEngine, 49 | CommandInvoker $commandInvoker 50 | ) { 51 | $this->filesystem = $filesystem; 52 | $this->searchEngine = $searchEngine; 53 | $this->commandInvoker = $commandInvoker; 54 | } 55 | 56 | /** 57 | * By default opens existing files only, but can be forced to create new ones. 58 | * 59 | * @param string $filename 60 | * @param bool $force 61 | * 62 | * @return File 63 | * 64 | * @throws \Symfony\Component\Filesystem\Exception\FileNotFoundException if the file hasn't be found 65 | * 66 | * @api 67 | */ 68 | public function open($filename, $force = false) 69 | { 70 | if (!$this->filesystem->exists($filename) && $force) { 71 | return $this->filesystem->create($filename); 72 | } 73 | 74 | return $this->filesystem->open($filename); 75 | } 76 | 77 | /** 78 | * File changes are made in memory only, until this methods actually applies 79 | * them on the filesystem. 80 | * 81 | * @throws \Symfony\Component\Filesystem\Exception\IOException if the file cannot be written to 82 | * 83 | * @api 84 | */ 85 | public function save(File $file, $filename = null) 86 | { 87 | if (null !== $filename) { 88 | $file->setFilename($filename); 89 | } 90 | 91 | $this->filesystem->write($file); 92 | } 93 | 94 | /** 95 | * Searches the given pattern in the Text above the current line. 96 | * If the pattern is found, the current line is set to it. 97 | * 98 | * @param mixed $pattern 99 | * @param int $location 100 | * 101 | * @throws PatternNotFoundException If the pattern hasn't been found 102 | * @throws \Gnugat\Redaktilo\Exception\NotSupportedException If the given pattern isn't supported by any registered strategy 103 | * 104 | * @api 105 | */ 106 | public function jumpAbove(Text $text, $pattern, $location = null) 107 | { 108 | $searchStrategy = $this->searchEngine->resolve($pattern); 109 | $foundLineNumber = $searchStrategy->findAbove($text, $pattern, $location); 110 | if (false === $foundLineNumber) { 111 | throw new PatternNotFoundException($pattern, $text); 112 | } 113 | 114 | $text->setCurrentLineNumber($foundLineNumber); 115 | } 116 | 117 | /** 118 | * Searches the given pattern in the Text below the current line. 119 | * If the pattern is found, the current line is set to it. 120 | * 121 | * @param mixed $pattern 122 | * @param int $location 123 | * 124 | * @throws PatternNotFoundException If the pattern hasn't been found 125 | * @throws \Gnugat\Redaktilo\Exception\NotSupportedException If the given pattern isn't supported by any registered strategy 126 | * 127 | * @api 128 | */ 129 | public function jumpBelow(Text $text, $pattern, $location = null) 130 | { 131 | $searchStrategy = $this->searchEngine->resolve($pattern); 132 | $foundLineNumber = $searchStrategy->findBelow($text, $pattern, $location); 133 | if (false === $foundLineNumber) { 134 | throw new PatternNotFoundException($pattern, $text); 135 | } 136 | 137 | $text->setCurrentLineNumber($foundLineNumber); 138 | } 139 | 140 | /** 141 | * Checks the presence of the given pattern in the Text above the current 142 | * line. 143 | * 144 | * @param mixed $pattern 145 | * @param int $location 146 | * 147 | * @return bool 148 | * 149 | * @throws \Gnugat\Redaktilo\Exception\NotSupportedException If the given pattern isn't supported by any registered strategy 150 | * 151 | * @api 152 | */ 153 | public function hasAbove(Text $text, $pattern, $location = null) 154 | { 155 | $searchStrategy = $this->searchEngine->resolve($pattern); 156 | $foundLineNumber = $searchStrategy->findAbove($text, $pattern, $location); 157 | 158 | return false !== $foundLineNumber; 159 | } 160 | 161 | /** 162 | * Checks the presence of the given pattern in the Text below the current 163 | * line. 164 | * 165 | * @param mixed $pattern 166 | * @param int $location 167 | * 168 | * @return bool 169 | * 170 | * @throws \Gnugat\Redaktilo\Exception\NotSupportedException If the given pattern isn't supported by any registered strategy 171 | * 172 | * @api 173 | */ 174 | public function hasBelow(Text $text, $pattern, $location = null) 175 | { 176 | $searchStrategy = $this->searchEngine->resolve($pattern); 177 | $foundLineNumber = $searchStrategy->findBelow($text, $pattern, $location); 178 | 179 | return false !== $foundLineNumber; 180 | } 181 | 182 | /** 183 | * Inserts the given line above the given line number 184 | * (or above the current one if none provided). 185 | * Note: the current line is then set to the new one. 186 | * 187 | * @param string $addition 188 | * @param int $location 189 | * 190 | * @api 191 | */ 192 | public function insertAbove(Text $text, $addition, $location = null) 193 | { 194 | $input = [ 195 | 'text' => $text, 196 | 'location' => $location, 197 | 'addition' => $addition, 198 | ]; 199 | $this->commandInvoker->run('insert_above', $input); 200 | } 201 | 202 | /** 203 | * Inserts the given addition below the given line number 204 | * (or below the current one if none provided). 205 | * Note: the current line is then set to the new one. 206 | * 207 | * @param string $addition 208 | * @param int $location 209 | * 210 | * @api 211 | */ 212 | public function insertBelow(Text $text, $addition, $location = null) 213 | { 214 | $input = [ 215 | 'text' => $text, 216 | 'location' => $location, 217 | 'addition' => $addition, 218 | ]; 219 | $this->commandInvoker->run('insert_below', $input); 220 | } 221 | 222 | /** 223 | * Replaces the line at the given line number 224 | * (or at the current one if none provided) with the given replacement. 225 | * 226 | * @param string $replacement 227 | * @param int $location 228 | * 229 | * @api 230 | */ 231 | public function replace(Text $text, $replacement, $location = null) 232 | { 233 | $input = [ 234 | 'text' => $text, 235 | 'location' => $location, 236 | 'replacement' => $replacement, 237 | ]; 238 | $this->commandInvoker->run('replace', $input); 239 | } 240 | 241 | /** 242 | * Replaces all the occurences which match the given pattern with the given 243 | * replacement. 244 | * 245 | * @param string $pattern 246 | * @param string $replacement 247 | * 248 | * @api 249 | */ 250 | public function replaceAll(Text $text, $pattern, $replacement) 251 | { 252 | $input = [ 253 | 'text' => $text, 254 | 'pattern' => $pattern, 255 | 'replacement' => $replacement, 256 | ]; 257 | $this->commandInvoker->run('replace_all', $input); 258 | } 259 | 260 | /** 261 | * Removes the line at the given location 262 | * (or at the current one if none provided). 263 | * 264 | * @param int $location 265 | * 266 | * @api 267 | */ 268 | public function remove(Text $text, $location = null) 269 | { 270 | $input = [ 271 | 'text' => $text, 272 | 'location' => $location, 273 | ]; 274 | $this->commandInvoker->run('remove', $input); 275 | } 276 | 277 | /** 278 | * Provides access to the CommandInvoker. 279 | * 280 | * @param string $name 281 | * 282 | * @throws \Gnugat\Redaktilo\Exception\CommandNotFoundException If the command isn't found in the CommandInvoker 283 | * 284 | * @api 285 | */ 286 | public function run($name, array $input) 287 | { 288 | $this->commandInvoker->run($name, $input); 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/EditorFactory.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 | namespace Gnugat\Redaktilo; 13 | 14 | use Gnugat\Redaktilo\Service\EditorBuilder; 15 | 16 | /** 17 | * @author Wouter J 18 | * 19 | * @api 20 | */ 21 | final class EditorFactory 22 | { 23 | /** 24 | * @return EditorBuilder 25 | * 26 | * @api 27 | */ 28 | public static function createBuilder() 29 | { 30 | return new EditorBuilder(); 31 | } 32 | 33 | /** 34 | * @return Editor 35 | * 36 | * @api 37 | */ 38 | public static function createEditor() 39 | { 40 | return self::createBuilder()->getEditor(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Exception/CommandNotFoundException.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 | namespace Gnugat\Redaktilo\Exception; 13 | 14 | /** 15 | * Thrown if the name given to CommandInvoker isn't in its collection. 16 | * 17 | * @api 18 | */ 19 | class CommandNotFoundException extends \LogicException implements Exception 20 | { 21 | /** @var string */ 22 | private $name; 23 | 24 | /** @var \Gnugat\Redaktilo\Command\Command[] */ 25 | private $commands; 26 | 27 | /** 28 | * @param string $name 29 | * @param \Gnugat\Redaktilo\Command\Command[] $commands 30 | */ 31 | public function __construct($name, array $commands) 32 | { 33 | $this->name = $name; 34 | $this->commands = $commands; 35 | 36 | $message = sprintf('The command "%s" was not found in CommandInvoker', $name); 37 | 38 | parent::__construct($message); 39 | } 40 | 41 | /** @return string */ 42 | public function getName() 43 | { 44 | return $this->name; 45 | } 46 | 47 | /** @return \Gnugat\Redaktilo\Command\Command[] */ 48 | public function getCommands() 49 | { 50 | return $this->commands; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Exception/DifferentLineBreaksFoundException.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 | namespace Gnugat\Redaktilo\Exception; 13 | 14 | /** 15 | * Thrown if the string given to LineBreak service contains different line breaks. 16 | * 17 | * @api 18 | */ 19 | class DifferentLineBreaksFoundException extends \RuntimeException implements Exception 20 | { 21 | /** @var string */ 22 | private $string; 23 | 24 | /** @var int */ 25 | private $numberLineBreakOther; 26 | 27 | /** @var int */ 28 | private $numberLineBreakWindows; 29 | 30 | /** 31 | * @param string $string 32 | * @param int $numberLineBreakOther 33 | * @param int $numberLineBreakWindows 34 | */ 35 | public function __construct($string, $numberLineBreakOther, $numberLineBreakWindows) 36 | { 37 | $this->string = (string) $string; 38 | $this->numberLineBreakOther = (int) $numberLineBreakOther; 39 | $this->numberLineBreakWindows = (int) $numberLineBreakWindows; 40 | 41 | $message = sprintf( 42 | 'The given string contains different line breaks, %d LF (\'\n\', usually found on Unix/Linux systems) and %d CR+LF (\'\r\n\', usually found on Windows systems)', 43 | $this->numberLineBreakOther, 44 | $this->numberLineBreakWindows 45 | ); 46 | 47 | parent::__construct($message); 48 | } 49 | 50 | /** @return string */ 51 | public function getString() 52 | { 53 | return $this->string; 54 | } 55 | 56 | /** @return int */ 57 | public function getNumberLineBreakOther() 58 | { 59 | return $this->numberLineBreakOther; 60 | } 61 | 62 | /** @return int */ 63 | public function getNumberLineBreakWindows() 64 | { 65 | return $this->numberLineBreakWindows; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Exception/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 | namespace Gnugat\Redaktilo\Exception; 13 | 14 | interface Exception 15 | { 16 | } 17 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Exception/FileNotFoundException.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 | namespace Gnugat\Redaktilo\Exception; 13 | 14 | /** 15 | * Thrown if the file couldn't be opened. 16 | * 17 | * @api 18 | */ 19 | class FileNotFoundException extends \RuntimeException implements Exception 20 | { 21 | /** @var string */ 22 | private $path; 23 | 24 | /** 25 | * @param string $path 26 | */ 27 | public function __construct($path, \Exception $previous = null) 28 | { 29 | $this->path = $path; 30 | 31 | $message = sprintf('Failed to open "%s" because it does not exist.', $path); 32 | 33 | parent::__construct($message, 0, $previous); 34 | } 35 | 36 | /** @return string */ 37 | public function getPath() 38 | { 39 | return $this->path; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Exception/IOException.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 | namespace Gnugat\Redaktilo\Exception; 13 | 14 | /** 15 | * Thrown if the path isn't accessible or the file could not be written to. 16 | * 17 | * @api 18 | */ 19 | class IOException extends \RuntimeException implements Exception 20 | { 21 | /** @var string */ 22 | private $path; 23 | 24 | /** 25 | * @param string $path 26 | * @param string $message 27 | */ 28 | public function __construct($path, $message, \Exception $previous = null) 29 | { 30 | $this->path = $path; 31 | 32 | parent::__construct($message, 0, $previous); 33 | } 34 | 35 | /** @return string */ 36 | public function getPath() 37 | { 38 | return $this->path; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Exception/InvalidArgumentException.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 | namespace Gnugat\Redaktilo\Exception; 13 | 14 | /** 15 | * @api 16 | */ 17 | class InvalidArgumentException extends \InvalidArgumentException implements Exception 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Exception/InvalidLineNumberException.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 | namespace Gnugat\Redaktilo\Exception; 13 | 14 | use Gnugat\Redaktilo\Text; 15 | 16 | /** 17 | * Thrown if the given line number isn't a positive integer strictly inferior to 18 | * the total number of lines in text. 19 | * 20 | * @api 21 | */ 22 | class InvalidLineNumberException extends InvalidArgumentException 23 | { 24 | /** @var mixed */ 25 | private $lineNumber; 26 | 27 | /** @var Text */ 28 | private $text; 29 | 30 | /** 31 | * @param mixed $lineNumber 32 | * @param string $message 33 | */ 34 | public function __construct($lineNumber, Text $text, $message) 35 | { 36 | $this->lineNumber = $lineNumber; 37 | $this->text = $text; 38 | 39 | parent::__construct($message); 40 | } 41 | 42 | /** @return mixed */ 43 | public function getLineNumber() 44 | { 45 | return $this->lineNumber; 46 | } 47 | 48 | /** @return Text */ 49 | public function getText() 50 | { 51 | return $this->text; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Exception/NoFilenameGivenException.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 | namespace Gnugat\Redaktilo\Exception; 13 | 14 | /** 15 | * @author Wouter J 16 | */ 17 | class NoFilenameGivenException extends InvalidArgumentException 18 | { 19 | public function __construct() 20 | { 21 | parent::__construct('No filename given when saving the file.'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Exception/NotSupportedException.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 | namespace Gnugat\Redaktilo\Exception; 13 | 14 | /** 15 | * Thrown if the pattern given to SearchEngine isn't supported by any of its 16 | * registered strategies. 17 | * 18 | * @api 19 | */ 20 | class NotSupportedException extends \LogicException implements Exception 21 | { 22 | /** @var mixed */ 23 | private $pattern; 24 | 25 | /** @var array */ 26 | private $searchStrategies; 27 | 28 | /** 29 | * @param mixed $pattern 30 | */ 31 | public function __construct($pattern, array $searchStrategies) 32 | { 33 | $this->pattern = $pattern; 34 | $this->searchStrategies = $searchStrategies; 35 | 36 | $patternMessage = 'given pattern'; 37 | if (is_string($pattern) || is_int($pattern)) { 38 | $patternMessage .= ' "'.strval($pattern).'"'; 39 | } 40 | 41 | $message = sprintf( 42 | 'The %s isn\'t supported by the Search Strategies registered in SearchEngine', 43 | $patternMessage 44 | ); 45 | 46 | parent::__construct($message); 47 | } 48 | 49 | /** @return mixed */ 50 | public function getPattern() 51 | { 52 | return $this->pattern; 53 | } 54 | 55 | /** @return array */ 56 | public function getSearchStrategies() 57 | { 58 | return $this->searchStrategies; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Exception/PatternNotFoundException.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 | namespace Gnugat\Redaktilo\Exception; 13 | 14 | use Gnugat\Redaktilo\File; 15 | use Gnugat\Redaktilo\Text; 16 | 17 | /** 18 | * Thrown if the pattern given to the SearchEngine couldn't match anything in 19 | * the Text. 20 | * 21 | * @api 22 | */ 23 | class PatternNotFoundException extends \RuntimeException implements Exception 24 | { 25 | /** @var mixed */ 26 | private $pattern; 27 | 28 | /** @var Text */ 29 | private $text; 30 | 31 | /** 32 | * @param mixed $pattern 33 | */ 34 | public function __construct($pattern, Text $text) 35 | { 36 | $this->pattern = $pattern; 37 | $this->text = $text; 38 | 39 | $patternMessage = 'given pattern'; 40 | if (is_string($pattern) || is_int($pattern)) { 41 | $patternMessage .= ' "'.strval($pattern).'"'; 42 | } 43 | $textMessage = 'the given text'; 44 | if ($text instanceof File) { 45 | $textMessage = 'the given file '.$text->getFilename(); 46 | } 47 | 48 | $message = sprintf('The %s couldn\'t be find in %s', $patternMessage, $textMessage); 49 | 50 | parent::__construct($message); 51 | } 52 | 53 | /** @return mixed */ 54 | public function getPattern() 55 | { 56 | return $this->pattern; 57 | } 58 | 59 | /** @return Text */ 60 | public function getText() 61 | { 62 | return $this->text; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/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 | namespace Gnugat\Redaktilo; 13 | 14 | /** 15 | * Redaktilo's base entity representing a file: it is a Text which has a 16 | * filename (which is the absolute path with the file name). 17 | * 18 | * @api 19 | */ 20 | class File extends Text 21 | { 22 | /** @var string|null */ 23 | private $filename; 24 | 25 | /** 26 | * @return string|null 27 | * 28 | * @api 29 | */ 30 | public function getFilename() 31 | { 32 | return $this->filename; 33 | } 34 | 35 | /** 36 | * @param string $filename 37 | * 38 | * @api 39 | */ 40 | public function setFilename($filename) 41 | { 42 | $this->filename = $filename; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Search/LineNumberSearchStrategy.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 | namespace Gnugat\Redaktilo\Search; 13 | 14 | use Gnugat\Redaktilo\Text; 15 | 16 | /** 17 | * Moves x lines above or below the current one. 18 | * 19 | * @deprecated 1.7 Use Text#setCurrentLineNumber instead 20 | */ 21 | class LineNumberSearchStrategy implements SearchStrategy 22 | { 23 | /** {@inheritdoc} */ 24 | public function findAbove(Text $text, $pattern, $location = null) 25 | { 26 | trigger_error(__CLASS__.' has been replaced by Text#setCurrentLineNumber', \E_USER_DEPRECATED); 27 | $foundLineNumber = (null !== $location ? $location : $text->getCurrentLineNumber()) - $pattern; 28 | if (0 > $foundLineNumber || $foundLineNumber >= $text->getLength()) { 29 | return false; 30 | } 31 | 32 | return $foundLineNumber; 33 | } 34 | 35 | /** {@inheritdoc} */ 36 | public function findBelow(Text $text, $pattern, $location = null) 37 | { 38 | trigger_error(__CLASS__.' has been replaced by Text#setCurrentLineNumber', \E_USER_DEPRECATED); 39 | $foundLineNumber = (null !== $location ? $location : $text->getCurrentLineNumber()) + $pattern; 40 | if (0 > $foundLineNumber || $foundLineNumber >= $text->getLength()) { 41 | return false; 42 | } 43 | 44 | return $foundLineNumber; 45 | } 46 | 47 | /** {@inheritdoc} */ 48 | public function supports($pattern) 49 | { 50 | trigger_error(__CLASS__.' has been replaced by Text#setCurrentLineNumber', \E_USER_DEPRECATED); 51 | 52 | return is_int($pattern) && $pattern >= 0; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Search/LineRegexSearchStrategy.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 | namespace Gnugat\Redaktilo\Search; 13 | 14 | /** 15 | * Tries to match the given regex against each lines. 16 | */ 17 | class LineRegexSearchStrategy extends LineSearchStrategy 18 | { 19 | /** {@inheritdoc} */ 20 | protected function findIn(array $lines, $pattern) 21 | { 22 | $found = preg_grep($pattern, $lines); 23 | if (empty($found)) { 24 | return false; 25 | } 26 | 27 | return key($found); 28 | } 29 | 30 | /** {@inheritdoc} */ 31 | public function supports($pattern) 32 | { 33 | if (!is_string($pattern)) { 34 | return false; 35 | } 36 | 37 | // Remove the warning generated by preg_match when the pattern is not valid 38 | $errorHandler = null; 39 | $errorHandler = set_error_handler(function ($errno, $errstr) use (&$errorHandler) { 40 | if (false !== preg_match('/(delimiter|regex|regular expression|modifier|pattern)/', $errstr)) { 41 | return; 42 | } 43 | if ($errorHandler) { 44 | call_user_func_array($errorHandler, func_get_args()); 45 | } 46 | }); 47 | 48 | $supported = !(false === preg_match($pattern, '')); 49 | 50 | restore_error_handler(); 51 | 52 | return $supported; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Search/LineSearchStrategy.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 | namespace Gnugat\Redaktilo\Search; 13 | 14 | use Gnugat\Redaktilo\Text; 15 | 16 | /** 17 | * Prepares a subset of lines for the lookup. 18 | */ 19 | abstract class LineSearchStrategy implements SearchStrategy 20 | { 21 | /** {@inheritdoc} */ 22 | public function findAbove(Text $text, $pattern, $location = null) 23 | { 24 | $location = (null !== $location ? $location : $text->getCurrentLineNumber()); 25 | $lines = $text->getLines(); 26 | $aboveLines = array_slice($lines, 0, $location, true); 27 | $reversedAboveLines = array_reverse($aboveLines, true); 28 | 29 | return $this->findIn($reversedAboveLines, $pattern); 30 | } 31 | 32 | /** {@inheritdoc} */ 33 | public function findBelow(Text $text, $pattern, $location = null) 34 | { 35 | $location = (null !== $location ? $location : $text->getCurrentLineNumber()) + 1; 36 | $lines = $text->getLines(); 37 | $belowLines = array_slice($lines, $location, null, true); 38 | 39 | return $this->findIn($belowLines, $pattern); 40 | } 41 | 42 | /** 43 | * @param mixed $pattern 44 | * 45 | * @return mixed 46 | */ 47 | abstract protected function findIn(array $lines, $pattern); 48 | } 49 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Search/Php/Token.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 | namespace Gnugat\Redaktilo\Search\Php; 13 | 14 | /** 15 | * @deprecated 1.7 No replacement 16 | */ 17 | class Token 18 | { 19 | /** @var int */ 20 | private $number; 21 | 22 | /** @var string */ 23 | private $value; 24 | 25 | /** @var int */ 26 | private $lineNumber; 27 | 28 | /** 29 | * @param int $number 30 | * @param string $value 31 | * @param int $lineNumber 32 | */ 33 | public function __construct($number = null, $value = null, $lineNumber = null) 34 | { 35 | trigger_error(__CLASS__.' is no longer supported and will be removed in version 2', \E_USER_DEPRECATED); 36 | $this->number = $number; 37 | $this->value = $value; 38 | $this->lineNumber = $lineNumber; 39 | } 40 | 41 | /** @return bool */ 42 | public function hasNumber() 43 | { 44 | return null !== $this->number; 45 | } 46 | 47 | /** @return int */ 48 | public function getNumber() 49 | { 50 | return $this->number; 51 | } 52 | 53 | /** @return bool */ 54 | public function hasValue() 55 | { 56 | return null !== $this->value; 57 | } 58 | 59 | /** @return string */ 60 | public function getValue() 61 | { 62 | return $this->value; 63 | } 64 | 65 | /** @return bool */ 66 | public function hasLineNumber() 67 | { 68 | return null !== $this->lineNumber; 69 | } 70 | 71 | /** @return int */ 72 | public function getLineNumber() 73 | { 74 | return $this->lineNumber; 75 | } 76 | 77 | /** 78 | * @return bool 79 | */ 80 | public function isSameAs(Token $token) 81 | { 82 | $hasNumberWildcard = ($this->hasNumber() && $token->hasNumber()); 83 | if ($hasNumberWildcard && $this->number !== $token->number) { 84 | return false; 85 | } 86 | $hasValueWildcard = ($this->hasValue() && $token->hasValue()); 87 | if ($hasValueWildcard && $this->value !== $token->value) { 88 | return false; 89 | } 90 | $hasLineNumberWildcard = ($this->hasLineNumber() && $token->hasLineNumber()); 91 | if ($hasLineNumberWildcard && $this->lineNumber !== $token->lineNumber) { 92 | return false; 93 | } 94 | 95 | return true; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Search/Php/TokenBuilder.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 | namespace Gnugat\Redaktilo\Search\Php; 13 | 14 | /** 15 | * @deprecated 1.7 No replacement 16 | */ 17 | class TokenBuilder 18 | { 19 | /** 20 | * @param mixed $rawToken 21 | * @param int $lineNumber 22 | * 23 | * @return Token 24 | */ 25 | public function makeFromRaw($rawToken, $lineNumber = 0) 26 | { 27 | trigger_error(__CLASS__.' is no longer supported and will be removed in version 2', \E_USER_DEPRECATED); 28 | if (!is_array($rawToken)) { 29 | return new Token(null, $rawToken, $lineNumber); 30 | } 31 | 32 | return new Token($rawToken[0], $rawToken[1], $rawToken[2] - 1); 33 | } 34 | 35 | /** @return Token */ 36 | public function makeFunction() 37 | { 38 | trigger_error(__CLASS__.' is no longer supported and will be removed in version 2', \E_USER_DEPRECATED); 39 | 40 | return new Token(T_FUNCTION, 'function'); 41 | } 42 | 43 | /** @return Token */ 44 | public function makeMethod() 45 | { 46 | trigger_error(__CLASS__.' is no longer supported and will be removed in version 2', \E_USER_DEPRECATED); 47 | 48 | return $this->makeFunction(); 49 | } 50 | 51 | /** @return Token */ 52 | public function makeClass() 53 | { 54 | trigger_error(__CLASS__.' is no longer supported and will be removed in version 2', \E_USER_DEPRECATED); 55 | 56 | return new Token(T_CLASS, 'class'); 57 | } 58 | 59 | /** 60 | * @param string $whitespace 61 | * 62 | * @return Token 63 | */ 64 | public function makeWhitespace($whitespace) 65 | { 66 | trigger_error(__CLASS__.' is no longer supported and will be removed in version 2', \E_USER_DEPRECATED); 67 | 68 | return new Token(T_WHITESPACE, $whitespace); 69 | } 70 | 71 | /** 72 | * @param string $string 73 | * 74 | * @return Token 75 | */ 76 | public function makeString($string) 77 | { 78 | trigger_error(__CLASS__.' is no longer supported and will be removed in version 2', \E_USER_DEPRECATED); 79 | 80 | return new Token(T_STRING, $string); 81 | } 82 | 83 | /** 84 | * @return Token[] 85 | */ 86 | public function buildFromRaw(array $rawTokens) 87 | { 88 | trigger_error(__CLASS__.' is no longer supported and will be removed in version 2', \E_USER_DEPRECATED); 89 | $lineNumber = 0; 90 | $tokens = []; 91 | foreach ($rawTokens as $rawToken) { 92 | $token = $this->makeFromRaw($rawToken, $lineNumber); 93 | if ($token->hasNumber()) { 94 | $lineNumber = $token->getLineNumber() + substr_count($token->getValue(), "\n"); 95 | } 96 | $tokens[] = $token; 97 | } 98 | 99 | return $tokens; 100 | } 101 | 102 | /** 103 | * @param string $className 104 | * 105 | * @return Token[] 106 | */ 107 | public function buildClass($className) 108 | { 109 | trigger_error(__CLASS__.' is no longer supported and will be removed in version 2', \E_USER_DEPRECATED); 110 | $class = $this->makeClass(); 111 | $whitespace = $this->makeWhitespace(null); 112 | $string = $this->makeString($className); 113 | 114 | return [$class, $whitespace, $string]; 115 | } 116 | 117 | /** 118 | * @param string $functionName 119 | * 120 | * @return Token[] 121 | */ 122 | public function buildFunction($functionName) 123 | { 124 | trigger_error(__CLASS__.' is no longer supported and will be removed in version 2', \E_USER_DEPRECATED); 125 | $function = $this->makeFunction(); 126 | $whitespace = $this->makeWhitespace(null); 127 | $string = $this->makeString($functionName); 128 | 129 | return [$function, $whitespace, $string]; 130 | } 131 | 132 | /** 133 | * @param string $methodName 134 | * 135 | * @return Token[] 136 | */ 137 | public function buildMethod($methodName) 138 | { 139 | trigger_error(__CLASS__.' is no longer supported and will be removed in version 2', \E_USER_DEPRECATED); 140 | 141 | return $this->buildFunction($methodName); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Search/PhpSearchStrategy.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 | namespace Gnugat\Redaktilo\Search; 13 | 14 | use Gnugat\Redaktilo\Search\Php\Token; 15 | use Gnugat\Redaktilo\Service\TextToPhpConverter; 16 | use Gnugat\Redaktilo\Text; 17 | 18 | /** 19 | * Finds the given PHP token. 20 | * 21 | * @deprecated 1.7 No replacement 22 | */ 23 | class PhpSearchStrategy implements SearchStrategy 24 | { 25 | /** @var TextToPhpConverter */ 26 | private $converter; 27 | 28 | /** @param TextToPhpConverter $converter */ 29 | public function __construct(TextToPhpConverter $converter) 30 | { 31 | $this->converter = $converter; 32 | } 33 | 34 | /** {@inheritdoc} */ 35 | public function findAbove(Text $text, $pattern, $location = null) 36 | { 37 | trigger_error(__CLASS__.' is no longer supported and will be removed in version 2', \E_USER_DEPRECATED); 38 | $location = (null !== $location ? $location : $text->getCurrentLineNumber()); 39 | $tokens = $this->converter->from($text); 40 | $reversedTokens = array_reverse($tokens); 41 | $total = count($reversedTokens); 42 | for ($index = 0; $index < $total; ++$index) { 43 | $token = $reversedTokens[$index]; 44 | if ($token->getLineNumber() === $location) { 45 | break; 46 | } 47 | } 48 | 49 | return $this->findIn($reversedTokens, $index, $pattern); 50 | } 51 | 52 | /** {@inheritdoc} */ 53 | public function findBelow(Text $text, $pattern, $location = null) 54 | { 55 | trigger_error(__CLASS__.' is no longer supported and will be removed in version 2', \E_USER_DEPRECATED); 56 | $location = (null !== $location ? $location : $text->getCurrentLineNumber()) + 1; 57 | $tokens = $this->converter->from($text); 58 | $total = count($tokens); 59 | for ($index = 0; $index < $total; ++$index) { 60 | $token = $tokens[$index]; 61 | if ($token->getLineNumber() === $location) { 62 | break; 63 | } 64 | } 65 | 66 | return $this->findIn($tokens, $index, $pattern); 67 | } 68 | 69 | /** {@inheritdoc} */ 70 | public function supports($pattern) 71 | { 72 | trigger_error(__CLASS__.' is no longer supported and will be removed in version 2', \E_USER_DEPRECATED); 73 | if (!is_array($pattern)) { 74 | return false; 75 | } 76 | foreach ($pattern as $token) { 77 | if (!$token instanceof Token) { 78 | return false; 79 | } 80 | } 81 | 82 | return true; 83 | } 84 | 85 | /** 86 | * @param int $index 87 | * @param mixed $pattern 88 | * 89 | * @return mixed 90 | */ 91 | private function findIn(array $collection, $index, $pattern) 92 | { 93 | $total = count($collection); 94 | while ($index < $total) { 95 | $found = $this->match($pattern, $collection, $index++); 96 | if (false !== $found) { 97 | $token = $collection[$found]; 98 | 99 | return $token->getLineNumber(); 100 | } 101 | } 102 | 103 | return false; 104 | } 105 | 106 | /** 107 | * @param int $index 108 | * 109 | * @return mixed 110 | */ 111 | private function match(array $wantedTokens, array $tokens, $index) 112 | { 113 | foreach ($wantedTokens as $wantedToken) { 114 | $token = $tokens[$index]; 115 | if (!$token->isSameAs($wantedToken)) { 116 | return false; 117 | } 118 | ++$index; 119 | } 120 | 121 | return $index - 1; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Search/SameSearchStrategy.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 | namespace Gnugat\Redaktilo\Search; 13 | 14 | /** 15 | * Matches the lines which are exactly the same as the given pattern. 16 | */ 17 | class SameSearchStrategy extends LineSearchStrategy 18 | { 19 | /** {@inheritdoc} */ 20 | protected function findIn(array $lines, $pattern) 21 | { 22 | return array_search($pattern, $lines, true); 23 | } 24 | 25 | /** {@inheritdoc} */ 26 | public function supports($pattern) 27 | { 28 | if (!is_string($pattern)) { 29 | return false; 30 | } 31 | $hasNoLineBreak = (false === strpos($pattern, "\n")); 32 | 33 | return $hasNoLineBreak; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Search/SearchEngine.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 | namespace Gnugat\Redaktilo\Search; 13 | 14 | use Gnugat\Redaktilo\Exception\NotSupportedException; 15 | 16 | /** 17 | * Provides the SearchStrategy which supports the given pattern. 18 | */ 19 | class SearchEngine 20 | { 21 | /** @var SearchStrategy[][] */ 22 | private $searchStrategies = []; 23 | 24 | /** @var bool */ 25 | private $isSorted = false; 26 | 27 | /** 28 | * @param int $priority 29 | */ 30 | public function registerStrategy(SearchStrategy $searchStrategy, $priority = 0) 31 | { 32 | $this->searchStrategies[$priority][] = $searchStrategy; 33 | $this->isSorted = false; 34 | } 35 | 36 | /** 37 | * @param mixed $pattern 38 | * 39 | * @return SearchStrategy 40 | * 41 | * @throws NotSupportedException If the pattern isn't supported by any registered strategy 42 | */ 43 | public function resolve($pattern) 44 | { 45 | if (!$this->isSorted) { 46 | $this->sortStrategies(); 47 | } 48 | 49 | foreach ($this->searchStrategies as $priority => $searchStrategies) { 50 | foreach ($searchStrategies as $searchStrategy) { 51 | if ($searchStrategy->supports($pattern)) { 52 | return $searchStrategy; 53 | } 54 | } 55 | } 56 | 57 | throw new NotSupportedException($pattern, $this->searchStrategies); 58 | } 59 | 60 | /** 61 | * Sort registered strategies according to their priority. 62 | */ 63 | private function sortStrategies() 64 | { 65 | krsort($this->searchStrategies); 66 | $this->isSorted = true; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Search/SearchStrategy.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 | namespace Gnugat\Redaktilo\Search; 13 | 14 | use Gnugat\Redaktilo\Text; 15 | 16 | /** 17 | * A lookup strategy supporting a specific kind of pattern. 18 | * 19 | * @api 20 | */ 21 | interface SearchStrategy 22 | { 23 | /** 24 | * Looks for the given pattern from the given line number ($location) to the 25 | * top of the Text. 26 | * If no line number is given, the current line number of the text is used. 27 | * If the pattern doesn't match anything, returns false. 28 | * 29 | * @param mixed $pattern 30 | * @param int $location 31 | * 32 | * @return mixed 33 | */ 34 | public function findAbove(Text $text, $pattern, $location = null); 35 | 36 | /** 37 | * Looks for the given pattern from the given line number ($location) to the 38 | * bottom of the Text. 39 | * If no line number is given, the current line number of the text is used. 40 | * If the pattern doesn't match anything, returns false. 41 | * 42 | * @param mixed $pattern 43 | * @param int $location 44 | * 45 | * @return mixed 46 | */ 47 | public function findBelow(Text $text, $pattern, $location = null); 48 | 49 | /** 50 | * @param mixed $pattern 51 | * 52 | * @return bool 53 | */ 54 | public function supports($pattern); 55 | } 56 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Service/ContentFactory.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 | namespace Gnugat\Redaktilo\Service; 13 | 14 | use Gnugat\Redaktilo\Text; 15 | 16 | /** 17 | * Converts a Text back to a string content. 18 | */ 19 | class ContentFactory 20 | { 21 | /** 22 | * @return string 23 | */ 24 | public function make(Text $text) 25 | { 26 | return implode($text->getLineBreak(), $text->getLines()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Service/EditorBuilder.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 | namespace Gnugat\Redaktilo\Service; 13 | 14 | use Gnugat\Redaktilo\Command\Command; 15 | use Gnugat\Redaktilo\Command\CommandInvoker; 16 | use Gnugat\Redaktilo\Command\LineInsertAboveCommand; 17 | use Gnugat\Redaktilo\Command\LineInsertBelowCommand; 18 | use Gnugat\Redaktilo\Command\LineRemoveCommand; 19 | use Gnugat\Redaktilo\Command\LineReplaceAllCommand; 20 | use Gnugat\Redaktilo\Command\LineReplaceCommand; 21 | use Gnugat\Redaktilo\Command\Sanitizer\LocationSanitizer; 22 | use Gnugat\Redaktilo\Command\Sanitizer\TextSanitizer; 23 | use Gnugat\Redaktilo\Editor; 24 | use Gnugat\Redaktilo\Search\LineNumberSearchStrategy; 25 | use Gnugat\Redaktilo\Search\LineRegexSearchStrategy; 26 | use Gnugat\Redaktilo\Search\Php\TokenBuilder; 27 | use Gnugat\Redaktilo\Search\PhpSearchStrategy; 28 | use Gnugat\Redaktilo\Search\SameSearchStrategy; 29 | use Gnugat\Redaktilo\Search\SearchEngine; 30 | use Gnugat\Redaktilo\Search\SearchStrategy; 31 | use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; 32 | 33 | /** 34 | * @author Wouter J 35 | */ 36 | final class EditorBuilder 37 | { 38 | /** @var TextToPhpConverter */ 39 | private $phpConverter; 40 | 41 | /** @var SearchEngine|null */ 42 | private $searchEngine; 43 | 44 | /** @var SearchStrategy[][] */ 45 | private $searchStrategies = []; 46 | 47 | /** @var CommandInvoker|null */ 48 | private $commandInvoker; 49 | 50 | /** @var Command[] */ 51 | private $commands = []; 52 | 53 | /** @var Filesystem */ 54 | private $filesystem; 55 | 56 | /** @var TextSanitizer */ 57 | private $textSanitizer; 58 | 59 | /** @var LocationSanitizer */ 60 | private $locationSanitizer; 61 | 62 | /** @var ContentFactory */ 63 | private $contentFactory; 64 | 65 | /** @return TextToPhpConverter */ 66 | protected function getPhpConverter() 67 | { 68 | if ($this->phpConverter) { 69 | return $this->phpConverter; 70 | } 71 | $tokenBuilder = new TokenBuilder(); 72 | 73 | return $this->phpConverter = new TextToPhpConverter($tokenBuilder); 74 | } 75 | 76 | /** @return SearchEngine */ 77 | protected function getSearchEngine() 78 | { 79 | if ($this->searchEngine) { 80 | return $this->searchEngine; 81 | } 82 | 83 | $engine = new SearchEngine(); 84 | $phpConverter = $this->getPhpConverter(); 85 | 86 | $engine->registerStrategy(new PhpSearchStrategy($phpConverter)); 87 | $engine->registerStrategy(new LineRegexSearchStrategy(), 20); 88 | $engine->registerStrategy(new SameSearchStrategy(), 10); 89 | $engine->registerStrategy(new LineNumberSearchStrategy()); 90 | 91 | foreach ($this->searchStrategies as $priority => $strategies) { 92 | foreach ($strategies as $strategy) { 93 | $engine->registerStrategy($strategy, $priority); 94 | } 95 | } 96 | 97 | return $engine; 98 | } 99 | 100 | /** @return CommandInvoker */ 101 | protected function getCommandInvoker() 102 | { 103 | if ($this->commandInvoker) { 104 | return $this->commandInvoker; 105 | } 106 | $commandInvoker = new CommandInvoker(); 107 | 108 | $commandInvoker->addCommand(new LineInsertAboveCommand($this->getTextSanitizer(), $this->getLocationSanitizer())); 109 | $commandInvoker->addCommand(new LineInsertBelowCommand($this->getTextSanitizer(), $this->getLocationSanitizer())); 110 | $commandInvoker->addCommand(new LineReplaceAllCommand($this->getContentFactory(), $this->getTextSanitizer())); 111 | $commandInvoker->addCommand(new LineReplaceCommand($this->getTextSanitizer(), $this->getLocationSanitizer())); 112 | $commandInvoker->addCommand(new LineRemoveCommand($this->getTextSanitizer(), $this->getLocationSanitizer())); 113 | 114 | foreach ($this->commands as $command) { 115 | $commandInvoker->addCommand($command); 116 | } 117 | 118 | return $commandInvoker; 119 | } 120 | 121 | /** @return Filesystem */ 122 | protected function getFilesystem() 123 | { 124 | if ($this->filesystem) { 125 | return $this->filesystem; 126 | } 127 | 128 | return new Filesystem(new SymfonyFilesystem(), $this->getContentFactory()); 129 | } 130 | 131 | /** @return Editor */ 132 | public function getEditor() 133 | { 134 | return new Editor( 135 | $this->getFilesystem(), 136 | $this->getSearchEngine(), 137 | $this->getCommandInvoker() 138 | ); 139 | } 140 | 141 | /** 142 | * @param int $priority 143 | * 144 | * @return $this 145 | */ 146 | public function addSearchStrategy(SearchStrategy $searchStrategy, $priority = 0) 147 | { 148 | $this->searchStrategies[$priority][] = $searchStrategy; 149 | 150 | return $this; 151 | } 152 | 153 | /** 154 | * @return $this 155 | */ 156 | public function setSearchEngine(SearchEngine $searchEngine) 157 | { 158 | $this->searchEngine = $searchEngine; 159 | 160 | return $this; 161 | } 162 | 163 | /** 164 | * @return $this 165 | */ 166 | public function addCommand(Command $command) 167 | { 168 | $this->commands[] = $command; 169 | 170 | return $this; 171 | } 172 | 173 | /** 174 | * @return $this 175 | */ 176 | public function setCommandInvoker(CommandInvoker $commandInvoker) 177 | { 178 | $this->commandInvoker = $commandInvoker; 179 | 180 | return $this; 181 | } 182 | 183 | /** @return TextSanitizer */ 184 | protected function getTextSanitizer() 185 | { 186 | if ($this->textSanitizer) { 187 | return $this->textSanitizer; 188 | } 189 | 190 | return $this->textSanitizer = new TextSanitizer(); 191 | } 192 | 193 | /** @return LocationSanitizer */ 194 | protected function getLocationSanitizer() 195 | { 196 | if ($this->locationSanitizer) { 197 | return $this->locationSanitizer; 198 | } 199 | 200 | return $this->locationSanitizer = new LocationSanitizer($this->getTextSanitizer()); 201 | } 202 | 203 | /** @return ContentFactory */ 204 | protected function getContentFactory() 205 | { 206 | if ($this->contentFactory) { 207 | return $this->contentFactory; 208 | } 209 | 210 | return $this->contentFactory = new ContentFactory(); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Service/Filesystem.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 | namespace Gnugat\Redaktilo\Service; 13 | 14 | use Gnugat\Redaktilo\Exception\FileNotFoundException; 15 | use Gnugat\Redaktilo\Exception\IOException; 16 | use Gnugat\Redaktilo\Exception\NoFilenameGivenException; 17 | use Gnugat\Redaktilo\File; 18 | use Symfony\Component\Filesystem\Exception\IOException as SymfonyIOException; 19 | use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; 20 | 21 | /** 22 | * Manages actual operations on the filesystem using File as a data source. 23 | */ 24 | class Filesystem 25 | { 26 | /** @var SymfonyFilesystem */ 27 | private $symfonyFilesystem; 28 | 29 | /** @var ContentFactory */ 30 | private $contentFactory; 31 | 32 | /** 33 | * @param ContentFactory $contentFactory 34 | */ 35 | public function __construct( 36 | SymfonyFilesystem $symfonyFilesystem, 37 | ContentFactory $contentFactory = null 38 | ) { 39 | $this->symfonyFilesystem = $symfonyFilesystem; 40 | // @deprecated 1.3 ContentFactory becomes mandatory 41 | $this->contentFactory = $contentFactory ?: new ContentFactory(); 42 | } 43 | 44 | /** 45 | * Makes a File out of an existing file. 46 | * 47 | * @param string $filename 48 | * 49 | * @return File 50 | * 51 | * @throws FileNotFoundException 52 | */ 53 | public function open($filename) 54 | { 55 | if (!$this->exists($filename) || false === $content = file_get_contents($filename)) { 56 | throw new FileNotFoundException($filename); 57 | } 58 | 59 | return $this->makeFile($filename, $content); 60 | } 61 | 62 | /** 63 | * Makes a File out of a new file. 64 | * 65 | * @see exists() 66 | * 67 | * @param string $filename 68 | * 69 | * @return File 70 | * 71 | * @throws IOException if the path isn't accessible 72 | */ 73 | public function create($filename) 74 | { 75 | if ($this->exists($filename)) { 76 | $message = sprintf('Failed to create "%s" because its path is not accessible.', $filename); 77 | 78 | throw new IOException($filename, $message); 79 | } 80 | 81 | return $this->makeFile($filename, ''); 82 | } 83 | 84 | private function makeFile($filename, $content) 85 | { 86 | $file = File::fromString($content); 87 | $file->setFilename($filename); 88 | 89 | return $file; 90 | } 91 | 92 | /** 93 | * Possible reasons of failure: 94 | * + path doesn't exists 95 | * + path isn't accessible. 96 | * 97 | * @param string $filename 98 | * 99 | * @return bool 100 | */ 101 | public function exists($filename) 102 | { 103 | return file_exists($filename); 104 | } 105 | 106 | /** 107 | * Atomically writes the given File's content on the actual file. 108 | * 109 | * @throws IOException if the file cannot be written to 110 | */ 111 | public function write(File $file) 112 | { 113 | $filename = $file->getFilename(); 114 | if (null === $filename) { 115 | throw new NoFilenameGivenException(); 116 | } 117 | $content = $this->contentFactory->make($file); 118 | 119 | try { 120 | $this->symfonyFilesystem->dumpFile($filename, $content, null); 121 | } catch (SymfonyIOException $e) { 122 | $message = sprintf('Failed to write "%s".', $filename); 123 | 124 | throw new IOException($filename, $message, $e); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Service/TextToPhpConverter.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 | namespace Gnugat\Redaktilo\Service; 13 | 14 | use Gnugat\Redaktilo\Search\Php\TokenBuilder; 15 | use Gnugat\Redaktilo\Text; 16 | 17 | /** 18 | * Converts the given text's content into PHP tokens. 19 | */ 20 | class TextToPhpConverter 21 | { 22 | /** @param TokenBuilder $tokenBuilder */ 23 | public function __construct(TokenBuilder $tokenBuilder) 24 | { 25 | $this->tokenBuilder = $tokenBuilder; 26 | } 27 | 28 | /** 29 | * @return array 30 | */ 31 | public function from(Text $text) 32 | { 33 | $lines = $text->getLines(); 34 | $lineBreak = $text->getLineBreak(); 35 | $content = implode($lineBreak, $lines); 36 | $rawTokens = token_get_all($content); 37 | 38 | return $this->tokenBuilder->buildFromRaw($rawTokens); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/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 | namespace Gnugat\Redaktilo; 13 | 14 | use Gnugat\Redaktilo\Exception\DifferentLineBreaksFoundException; 15 | use Gnugat\Redaktilo\Exception\InvalidArgumentException; 16 | use Gnugat\Redaktilo\Exception\InvalidLineNumberException; 17 | use Gnugat\Redaktilo\Util\StringUtil; 18 | 19 | /** 20 | * Redaktilo's base entity representing a collection of lines: each line is 21 | * stripped from its line break character. 22 | * This character is centralized in a property. 23 | * 24 | * When Text is created, the current line number is set to 0. 25 | * 26 | * @api 27 | */ 28 | class Text 29 | { 30 | /** @var array */ 31 | protected $lines; 32 | 33 | /** @var int */ 34 | protected $length; 35 | 36 | /** @var string */ 37 | protected $lineBreak; 38 | 39 | /** @var int */ 40 | protected $currentLineNumber = 0; 41 | 42 | /** 43 | * @param string $lineBreak 44 | */ 45 | protected function __construct(array $lines, $lineBreak = PHP_EOL) 46 | { 47 | $this->setLines($lines); 48 | $this->lineBreak = $lineBreak; 49 | } 50 | 51 | /** 52 | * Creates a Text instance from a string. 53 | * 54 | * @param $string 55 | * 56 | * @return static 57 | */ 58 | public static function fromString($string) 59 | { 60 | try { 61 | $lineBreak = StringUtil::detectLineBreak($string); 62 | } catch (DifferentLineBreaksFoundException $e) { 63 | $lineBreak = $e->getNumberLineBreakOther() >= $e->getNumberLineBreakWindows() 64 | ? StringUtil::LINE_BREAK_OTHER 65 | : StringUtil::LINE_BREAK_WINDOWS; 66 | } 67 | 68 | return new static(StringUtil::breakIntoLines($string), $lineBreak); 69 | } 70 | 71 | /** 72 | * Creates a Text instance from an array of lines. 73 | * 74 | * @param string $lineBreak 75 | * 76 | * @return static 77 | */ 78 | public static function fromArray(array $lines, $lineBreak = PHP_EOL) 79 | { 80 | return new static($lines, $lineBreak); 81 | } 82 | 83 | /** 84 | * @return array 85 | * 86 | * @api 87 | */ 88 | public function getLines() 89 | { 90 | return $this->lines; 91 | } 92 | 93 | /** 94 | * @api 95 | */ 96 | public function setLines(array $lines) 97 | { 98 | $this->lines = $lines; 99 | $this->length = count($lines); 100 | } 101 | 102 | /** 103 | * @return int 104 | * 105 | * @api 106 | */ 107 | public function getLength() 108 | { 109 | return $this->length; 110 | } 111 | 112 | /** 113 | * @return string 114 | * 115 | * @api 116 | */ 117 | public function getLineBreak() 118 | { 119 | return $this->lineBreak; 120 | } 121 | 122 | /** 123 | * @param string $lineBreak 124 | * 125 | * @api 126 | */ 127 | public function setLineBreak($lineBreak) 128 | { 129 | $this->lineBreak = $lineBreak; 130 | } 131 | 132 | /** 133 | * @return int 134 | * 135 | * @api 136 | */ 137 | public function getCurrentLineNumber() 138 | { 139 | return $this->currentLineNumber; 140 | } 141 | 142 | /** 143 | * @param int $lineNumber 144 | * 145 | * @throws InvalidLineNumberException if $lineNumber is not an integer 146 | * @throws InvalidLineNumberException if $lineNumber is negative 147 | * @throws InvalidLineNumberException if $lineNumber is greater or equal than the number of lines 148 | * 149 | * @api 150 | */ 151 | public function setCurrentLineNumber($lineNumber) 152 | { 153 | $this->checkIfLineNumberIsValid($lineNumber); 154 | $this->currentLineNumber = $lineNumber; 155 | } 156 | 157 | /** 158 | * @param int $lineNumber 159 | * 160 | * @return string 161 | * 162 | * @throws InvalidLineNumberException if $lineNumber is not an integer 163 | * @throws InvalidLineNumberException if $lineNumber is negative 164 | * @throws InvalidLineNumberException if $lineNumber is greater or equal than the number of lines 165 | * 166 | * @api 167 | */ 168 | public function getLine($lineNumber = null) 169 | { 170 | if (null === $lineNumber) { 171 | $lineNumber = $this->currentLineNumber; 172 | } 173 | $this->checkIfLineNumberIsValid($lineNumber); 174 | 175 | return $this->lines[$lineNumber]; 176 | } 177 | 178 | /** 179 | * @param string $line 180 | * @param int $lineNumber 181 | * 182 | * @throws InvalidLineNumberException if $lineNumber is not an integer 183 | * @throws InvalidLineNumberException if $lineNumber is negative 184 | * @throws InvalidLineNumberException if $lineNumber is greater or equal than the number of lines 185 | * 186 | * @api 187 | */ 188 | public function setLine($line, $lineNumber = null) 189 | { 190 | if (null === $lineNumber) { 191 | $lineNumber = $this->currentLineNumber; 192 | } 193 | $this->checkIfLineNumberIsValid($lineNumber); 194 | $this->lines[$lineNumber] = $line; 195 | } 196 | 197 | /** 198 | * Calls the given callback for each line in Text. 199 | * 200 | * @param callable $callback 201 | * 202 | * @throws InvalidArgumentException if $callback is not callable 203 | */ 204 | public function map($callback) 205 | { 206 | if (!is_callable($callback)) { 207 | throw new InvalidArgumentException('Callback has to be a valid callable, '.gettype($callback).' given.'); 208 | } 209 | 210 | for ($i = 0; $this->checkIfLineNumberIsValid($i, false); ++$i) { 211 | $this->setCurrentLineNumber($i); 212 | call_user_func($callback, $this->getLine($i), $i, $this); 213 | } 214 | } 215 | 216 | /** 217 | * @param int $number 218 | * 219 | * @throws InvalidLineNumberException if $number is not an integer 220 | * @throws InvalidLineNumberException if $number is negative 221 | * @throws InvalidLineNumberException if $number is greater or equal than the length 222 | * @throws InvalidLineNumberException if the result would be greater or equal than the length 223 | * 224 | * @api 225 | */ 226 | public function incrementCurrentLineNumber($number) 227 | { 228 | $this->checkIfLineNumberIsValid($number); 229 | $newCurrentLineNumber = $this->currentLineNumber + $number; 230 | $this->checkIfLineNumberIsValid($newCurrentLineNumber); 231 | $this->currentLineNumber = $newCurrentLineNumber; 232 | } 233 | 234 | /** 235 | * @param int $number 236 | * 237 | * @throws InvalidLineNumberException if $number is not an integer 238 | * @throws InvalidLineNumberException if $number is negative 239 | * @throws InvalidLineNumberException if $number is greater or equal than the length 240 | * @throws InvalidLineNumberException if the result would be negative 241 | * 242 | * @api 243 | */ 244 | public function decrementCurrentLineNumber($number) 245 | { 246 | $this->checkIfLineNumberIsValid($number); 247 | $newCurrentLineNumber = $this->currentLineNumber - $number; 248 | $this->checkIfLineNumberIsValid($newCurrentLineNumber); 249 | $this->currentLineNumber = $newCurrentLineNumber; 250 | } 251 | 252 | /** 253 | * @param int $lineNumber 254 | * 255 | * @throws InvalidLineNumberException if $lineNumber is not an integer 256 | * @throws InvalidLineNumberException if $lineNumber is negative 257 | * @throws InvalidLineNumberException if $lineNumber is greater or equal than the length 258 | */ 259 | protected function checkIfLineNumberIsValid($lineNumber, $throw = true) 260 | { 261 | if (!is_int($lineNumber)) { 262 | $e = new InvalidLineNumberException($lineNumber, $this, 'The line number should be an integer'); 263 | } elseif ($lineNumber < 0) { 264 | $e = new InvalidLineNumberException($lineNumber, $this, 'The line number should be positive'); 265 | } elseif ($lineNumber >= $this->length) { 266 | $e = new InvalidLineNumberException($lineNumber, $this, 'The line number should be strictly lower than the number of lines'); 267 | } 268 | 269 | if (isset($e)) { 270 | if ($throw) { 271 | throw $e; 272 | } 273 | 274 | return false; 275 | } 276 | 277 | return true; 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/Gnugat/Redaktilo/Util/StringUtil.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 | namespace Gnugat\Redaktilo\Util; 13 | 14 | use Gnugat\Redaktilo\Exception\DifferentLineBreaksFoundException; 15 | 16 | /** 17 | * @author Wouter J 18 | */ 19 | final class StringUtil 20 | { 21 | const LINE_BREAK_OTHER = "\n"; 22 | const LINE_BREAK_WINDOWS = "\r\n"; 23 | 24 | public static function breakIntoLines($string) 25 | { 26 | return preg_split('/\R/', $string); 27 | } 28 | 29 | public static function detectLineBreak($string) 30 | { 31 | $numberLineBreakWindows = substr_count($string, self::LINE_BREAK_WINDOWS); 32 | $numberLineBreakOther = substr_count($string, self::LINE_BREAK_OTHER) - $numberLineBreakWindows; 33 | 34 | if (0 === $numberLineBreakOther && 0 === $numberLineBreakWindows) { 35 | return PHP_EOL; 36 | } 37 | 38 | if ($numberLineBreakOther > 0 && $numberLineBreakWindows > 0) { 39 | throw new DifferentLineBreaksFoundException($string, $numberLineBreakOther, $numberLineBreakWindows); 40 | } 41 | 42 | return $numberLineBreakOther > 0 ? self::LINE_BREAK_OTHER : self::LINE_BREAK_WINDOWS; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/example/BundleRegistrationTest.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 | namespace example\Gnugat\Redaktilo; 13 | 14 | use Gnugat\Redaktilo\EditorFactory; 15 | use PHPUnit\Framework\TestCase; 16 | use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; 17 | 18 | class BundleRegistrationTest extends TestCase 19 | { 20 | const APP_KERNEL = '%s/tests/fixtures/%s/AppKernel.php'; 21 | 22 | private $appKernelPath; 23 | private $expectedAppKernelPath; 24 | 25 | private $editor; 26 | 27 | protected function setUp(): void 28 | { 29 | $rootPath = __DIR__.'/../..'; 30 | 31 | $sourceFilename = sprintf(self::APP_KERNEL, $rootPath, 'sources'); 32 | $copyFilename = sprintf(self::APP_KERNEL, $rootPath, 'copies'); 33 | $expectationFilename = sprintf(self::APP_KERNEL, $rootPath, 'expectations'); 34 | 35 | $fileCopier = new SymfonyFilesystem(); 36 | $fileCopier->copy($sourceFilename, $copyFilename, true); 37 | 38 | $this->appKernelPath = $copyFilename; 39 | $this->expectedAppKernelPath = $expectationFilename; 40 | $this->editor = EditorFactory::createEditor(); 41 | } 42 | 43 | public function testItRegistersBundleInSymfonyApplication() 44 | { 45 | $appKernel = $this->editor->open($this->appKernelPath); 46 | $this->editor->jumpBelow($appKernel, ' );'); 47 | $this->editor->insertAbove($appKernel, ' new Gnugat\WizardBundle\GnugatWizardBundle(),'); 48 | $this->editor->save($appKernel); 49 | 50 | $expected = file_get_contents($this->expectedAppKernelPath); 51 | $actual = file_get_contents($this->appKernelPath); 52 | 53 | $this->assertSame($expected, $actual); 54 | } 55 | 56 | public function testItDetectsBundlePresence() 57 | { 58 | $file = $this->editor->open($this->expectedAppKernelPath); 59 | 60 | $isBundlePresent = $this->editor->hasBelow($file, ' new Gnugat\WizardBundle\GnugatWizardBundle(),', 0); 61 | 62 | $this->assertTrue($isBundlePresent); 63 | } 64 | 65 | public function testItDetectsBundleAbsence() 66 | { 67 | $file = $this->editor->open($this->appKernelPath); 68 | 69 | $isBundlePresent = $this->editor->hasBelow($file, ' new Gnugat\WizardBundle\GnugatWizardBundle(),', 0); 70 | 71 | $this->assertFalse($isBundlePresent); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/example/BundleRoutingTest.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 | namespace Sensio\Bundle\GeneratorBundle\Manipulator; 13 | 14 | use Gnugat\Redaktilo\EditorFactory; 15 | use PHPUnit\Framework\TestCase; 16 | use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; 17 | 18 | class BundleRoutingTest extends TestCase 19 | { 20 | const CONFIG = '%s/tests/fixtures/%s/routing.yml'; 21 | 22 | private $configPath; 23 | private $expectedConfigPath; 24 | 25 | protected function setUp(): void 26 | { 27 | $rootPath = __DIR__.'/../..'; 28 | 29 | $sourceFilename = sprintf(self::CONFIG, $rootPath, 'sources'); 30 | $copyFilename = sprintf(self::CONFIG, $rootPath, 'copies'); 31 | $expectationFilename = sprintf(self::CONFIG, $rootPath, 'expectations'); 32 | 33 | $fileCopier = new SymfonyFilesystem(); 34 | $fileCopier->copy($sourceFilename, $copyFilename, true); 35 | 36 | $this->configPath = $copyFilename; 37 | $this->expectedConfigPath = $expectationFilename; 38 | } 39 | 40 | public function testItAddsAnnotatedRoute() 41 | { 42 | $editor = EditorFactory::createEditor(); 43 | 44 | $file = $editor->open($this->configPath, true); 45 | 46 | $definitionLine = 'acme_demo:'; 47 | $resourceLine = ' resource: "@AcmeDemoBundle/Controller/"'; 48 | $typeLine = ' type: annotation'; 49 | $prefixLine = ' prefix: /'; 50 | $emptyLine = ''; 51 | 52 | $editor->insertAbove($file, $definitionLine); 53 | $editor->insertBelow($file, $resourceLine); 54 | $editor->insertBelow($file, $typeLine); 55 | $editor->insertBelow($file, $prefixLine); 56 | $editor->insertBelow($file, $emptyLine); 57 | 58 | $editor->save($file); 59 | 60 | $expected = file_get_contents($this->expectedConfigPath); 61 | $actual = file_get_contents($this->configPath); 62 | 63 | $this->assertSame($expected, $actual); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/example/DocumentationReformattingTest.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 | namespace example\Gnugat\Redaktilo; 13 | 14 | use Gnugat\Redaktilo\EditorFactory; 15 | use PHPUnit\Framework\TestCase; 16 | use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; 17 | 18 | class DocumentationReformattingTest extends TestCase 19 | { 20 | const APP_KERNEL = '%s/tests/fixtures/%s/doctrine.rst'; 21 | 22 | private $originalPath; 23 | private $expectedPath; 24 | 25 | protected function setUp(): void 26 | { 27 | $rootPath = __DIR__.'/../..'; 28 | 29 | $sourceFilename = sprintf(self::APP_KERNEL, $rootPath, 'sources'); 30 | $copyFilename = sprintf(self::APP_KERNEL, $rootPath, 'copies'); 31 | $expectationFilename = sprintf(self::APP_KERNEL, $rootPath, 'expectations'); 32 | 33 | $fileCopier = new SymfonyFilesystem(); 34 | $fileCopier->copy($sourceFilename, $copyFilename, true); 35 | 36 | $this->originalPath = $copyFilename; 37 | $this->expectedPath = $expectationFilename; 38 | } 39 | 40 | public function testItRemovesCommandDollarSigns() 41 | { 42 | $editor = EditorFactory::createEditor(); 43 | $file = $editor->open($this->originalPath); 44 | $editor->replaceAll($file, '/\$ /', ''); 45 | $editor->save($file); 46 | 47 | $expected = file_get_contents($this->expectedPath); 48 | $actual = file_get_contents($this->originalPath); 49 | 50 | $this->assertSame($expected, $actual); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/fixtures/expectations/AppKernel.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class AppKernel extends Kernel 12 | { 13 | /** 14 | * {@inheritdoc} 15 | */ 16 | public function registerBundles() 17 | { 18 | $bundles = array( 19 | new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), 20 | new Gnugat\WizardBundle\GnugatWizardBundle(), 21 | ); 22 | 23 | if (in_array($this->getEnvironment(), array('dev', 'test'))) { 24 | } 25 | 26 | return $bundles; 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function registerContainerConfiguration(LoaderInterface $loader) 33 | { 34 | $loader->load(__DIR__.'/config/config.yml'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/fixtures/expectations/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gnugat/redaktilo", 3 | } 4 | -------------------------------------------------------------------------------- /tests/fixtures/expectations/life-of-brian-insert.txt: -------------------------------------------------------------------------------- 1 | Pontius Pilate: 'I will not have my fwends widiculed by the common soldiewy. Anybody else feel like a little... giggle... when I mention my fwiend... Biggus...' 2 | [A guard sniggers] 3 | Pontius Pilate: '...Dickus?' 4 | [More sniggering] 5 | Pontius Pilate: 'What about you? Do you find it... wisible... when I say the name... Biggus...' 6 | [Sniggering] 7 | Pontius Pilate: '...Dickus?' 8 | [Both guards snigger] 9 | Pontius Pilate: 'He has a wife, you know. You know what she's called? She's called... Incontinentia... Incontinentia Buttocks.' 10 | -------------------------------------------------------------------------------- /tests/fixtures/expectations/life-of-brian-remove.txt: -------------------------------------------------------------------------------- 1 | Pontius Pilate: 'I will not have my fwends widiculed by the common soldiewy. Anybody else feel like a little... giggle... when I mention my fwiend... Biggus...' 2 | [A guard sniggers] 3 | [More sniggering] 4 | Pontius Pilate: 'What about you? Do you find it... wisible... when I say the name... Biggus...' 5 | [Sniggering] 6 | [Both guards snigger] 7 | Pontius Pilate: 'He has a wife, you know. You know what she's called? She's called... Incontinentia... Incontinentia Buttocks.' 8 | -------------------------------------------------------------------------------- /tests/fixtures/expectations/life-of-brian-replace-all.txt: -------------------------------------------------------------------------------- 1 | Pontius Pilate: 'I will not have my fwends widiculed by the common soldiewy. Anybody else feel like a little... giggle... when I mention my fwiend... Biggus...' 2 | [A guard mockers] 3 | Pontius Pilate: '...Dickus?' 4 | [More mockering] 5 | Pontius Pilate: 'What about you? Do you find it... wisible... when I say the name... Biggus...' 6 | [Sniggering] 7 | [Both guards mocker] 8 | Pontius Pilate: 'He has a wife, you know. You know what she's called? She's called... Incontinentia... Incontinentia Buttocks.' 9 | -------------------------------------------------------------------------------- /tests/fixtures/expectations/life-of-brian-replace.txt: -------------------------------------------------------------------------------- 1 | Pontius Pilate: 'I will not have my fwends widiculed by the common soldiewy. Anybody else feel like a little... giggle... when I mention my fwiend... Biggus...' 2 | [A guard sniggers] 3 | Pontius Pilate: '...Dickus?' 4 | [More sniggering] 5 | Pontius Pilate: 'What about you? Do you find it... wisible... when I say the name... Biggus...' 6 | [Even more sniggering] 7 | [Both guards snigger] 8 | Pontius Pilate: 'He has a wife, you know. You know what she's called? She's called... Incontinentia... Incontinentia Buttocks.' 9 | -------------------------------------------------------------------------------- /tests/fixtures/expectations/routing.yml: -------------------------------------------------------------------------------- 1 | acme_demo: 2 | resource: "@AcmeDemoBundle/Controller/" 3 | type: annotation 4 | prefix: / 5 | 6 | -------------------------------------------------------------------------------- /tests/fixtures/expectations/to-indent.py: -------------------------------------------------------------------------------- 1 | def to_indent(): 2 | print('To indent') 3 | -------------------------------------------------------------------------------- /tests/fixtures/sources/AppKernel.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class AppKernel extends Kernel 12 | { 13 | /** 14 | * {@inheritdoc} 15 | */ 16 | public function registerBundles() 17 | { 18 | $bundles = array( 19 | new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), 20 | ); 21 | 22 | if (in_array($this->getEnvironment(), array('dev', 'test'))) { 23 | } 24 | 25 | return $bundles; 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function registerContainerConfiguration(LoaderInterface $loader) 32 | { 33 | $loader->load(__DIR__.'/config/config.yml'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/fixtures/sources/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gnugat/redaktilo" 3 | } 4 | -------------------------------------------------------------------------------- /tests/fixtures/sources/copy-me.txt: -------------------------------------------------------------------------------- 1 | We are the knigths who say ni! 2 | -------------------------------------------------------------------------------- /tests/fixtures/sources/life-of-brian.txt: -------------------------------------------------------------------------------- 1 | Pontius Pilate: 'I will not have my fwends widiculed by the common soldiewy. Anybody else feel like a little... giggle... when I mention my fwiend... Biggus...' 2 | [A guard sniggers] 3 | Pontius Pilate: '...Dickus?' 4 | [More sniggering] 5 | Pontius Pilate: 'What about you? Do you find it... wisible... when I say the name... Biggus...' 6 | [Sniggering] 7 | [Both guards snigger] 8 | Pontius Pilate: 'He has a wife, you know. You know what she's called? She's called... Incontinentia... Incontinentia Buttocks.' 9 | -------------------------------------------------------------------------------- /tests/fixtures/sources/php-sample.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 | namespace spec\Gnugat\Redaktilo\Command; 13 | 14 | use Gnugat\Redaktilo\Command\Command; 15 | use PhpSpec\ObjectBehavior; 16 | 17 | class CommandInvokerSpec extends ObjectBehavior 18 | { 19 | function let(Command $command) 20 | { 21 | $this->addCommand($command); 22 | } 23 | 24 | function it_fails_when_the_command_is_not_supported(Command $command) 25 | { 26 | $name = 'test'; 27 | $exception = '\Gnugat\Redaktilo\Exception\CommandNotFoundException'; 28 | $input = []; 29 | 30 | $command->getName()->willReturn('some-command'); 31 | 32 | $this->addCommand($command); 33 | $this->shouldThrow($exception)->duringRun($name, $input); 34 | } 35 | 36 | function it_executes_the_wanted_command(Command $command) 37 | { 38 | $name = 'another-command'; 39 | $input = []; 40 | 41 | $command->getName()->willReturn('another-command'); 42 | $command->execute($input)->shouldBeCalled(); 43 | 44 | $this->addCommand($command); 45 | $this->run($name, $input); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/Command/LineInsertAboveCommandSpec.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 | namespace spec\Gnugat\Redaktilo\Command; 13 | 14 | use Gnugat\Redaktilo\Command\Sanitizer\LocationSanitizer; 15 | use Gnugat\Redaktilo\Command\Sanitizer\TextSanitizer; 16 | use Gnugat\Redaktilo\Text; 17 | use PhpSpec\ObjectBehavior; 18 | 19 | class LineInsertAboveCommandSpec extends ObjectBehavior 20 | { 21 | const ORIGINAL_FILENAME = '%s/tests/fixtures/sources/life-of-brian.txt'; 22 | const EXPECTED_FILENAME = '%s/tests/fixtures/expectations/life-of-brian-insert.txt'; 23 | 24 | private $rootPath; 25 | 26 | function let(TextSanitizer $textSanitizer, LocationSanitizer $locationSanitizer, Text $text) 27 | { 28 | $this->rootPath = __DIR__.'/../../../../../'; 29 | 30 | $filename = sprintf(self::ORIGINAL_FILENAME, $this->rootPath); 31 | $lines = file($filename, FILE_IGNORE_NEW_LINES); 32 | 33 | $text->getLines()->willReturn($lines); 34 | 35 | $this->beConstructedWith( 36 | $textSanitizer, 37 | $locationSanitizer 38 | ); 39 | } 40 | 41 | function it_is_a_command() 42 | { 43 | $this->shouldImplement('Gnugat\Redaktilo\Command\Command'); 44 | } 45 | 46 | function it_inserts_new_lines(TextSanitizer $textSanitizer, LocationSanitizer $locationSanitizer, Text $text) 47 | { 48 | $expectedFilename = sprintf(self::EXPECTED_FILENAME, $this->rootPath); 49 | $expectedLines = file($expectedFilename, FILE_IGNORE_NEW_LINES); 50 | 51 | $lineNumber = 6; 52 | 53 | $input = [ 54 | 'text' => $text, 55 | 'location' => $lineNumber, 56 | 'addition' => "Pontius Pilate: '...Dickus?'", 57 | ]; 58 | 59 | $textSanitizer->sanitize($input)->willReturn($text); 60 | $locationSanitizer->sanitize($input)->willReturn($lineNumber); 61 | 62 | $text->setLines($expectedLines)->shouldBeCalled(); 63 | $text->setCurrentLineNumber($lineNumber)->shouldBeCalled(); 64 | 65 | $this->execute($input); 66 | 67 | $input = [ 68 | 'text' => $text, 69 | 'addition' => "Pontius Pilate: '...Dickus?'", 70 | ]; 71 | 72 | $textSanitizer->sanitize($input)->willReturn($text); 73 | $locationSanitizer->sanitize($input)->willReturn($lineNumber); 74 | 75 | $text->getCurrentLineNumber()->willReturn($lineNumber); 76 | $text->setCurrentLineNumber($lineNumber)->shouldBeCalled(); 77 | 78 | $this->execute($input); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/Command/LineInsertBelowCommandSpec.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 | namespace spec\Gnugat\Redaktilo\Command; 13 | 14 | use Gnugat\Redaktilo\Command\Sanitizer\LocationSanitizer; 15 | use Gnugat\Redaktilo\Command\Sanitizer\TextSanitizer; 16 | use Gnugat\Redaktilo\Text; 17 | use PhpSpec\ObjectBehavior; 18 | 19 | class LineInsertBelowCommandSpec extends ObjectBehavior 20 | { 21 | const ORIGINAL_FILENAME = '%s/tests/fixtures/sources/life-of-brian.txt'; 22 | const EXPECTED_FILENAME = '%s/tests/fixtures/expectations/life-of-brian-insert.txt'; 23 | 24 | private $rootPath; 25 | 26 | function let(TextSanitizer $textSanitizer, LocationSanitizer $locationSanitizer, Text $text) 27 | { 28 | $this->rootPath = __DIR__.'/../../../../../'; 29 | 30 | $filename = sprintf(self::ORIGINAL_FILENAME, $this->rootPath); 31 | $lines = file($filename, FILE_IGNORE_NEW_LINES); 32 | 33 | $text->getLines()->willReturn($lines); 34 | 35 | $this->beConstructedWith( 36 | $textSanitizer, 37 | $locationSanitizer 38 | ); 39 | } 40 | 41 | function it_is_a_command() 42 | { 43 | $this->shouldImplement('Gnugat\Redaktilo\Command\Command'); 44 | } 45 | 46 | function it_inserts_new_lines(TextSanitizer $textSanitizer, LocationSanitizer $locationSanitizer, Text $text) 47 | { 48 | $expectedFilename = sprintf(self::EXPECTED_FILENAME, $this->rootPath); 49 | $expectedLines = file($expectedFilename, FILE_IGNORE_NEW_LINES); 50 | 51 | $lineNumber = 6; 52 | 53 | $input = [ 54 | 'text' => $text, 55 | 'location' => $lineNumber - 1, 56 | 'addition' => "Pontius Pilate: '...Dickus?'", 57 | ]; 58 | 59 | $textSanitizer->sanitize($input)->willReturn($text); 60 | $locationSanitizer->sanitize($input)->willReturn($lineNumber - 1); 61 | 62 | $text->setLines($expectedLines)->shouldBeCalled(); 63 | $text->setCurrentLineNumber($lineNumber)->shouldBeCalled(); 64 | 65 | $this->execute($input); 66 | 67 | $input = [ 68 | 'text' => $text, 69 | 'addition' => "Pontius Pilate: '...Dickus?'", 70 | ]; 71 | 72 | $textSanitizer->sanitize($input)->willReturn($text); 73 | $locationSanitizer->sanitize($input)->willReturn($lineNumber - 1); 74 | 75 | $text->getCurrentLineNumber()->willReturn($lineNumber - 1); 76 | $text->setCurrentLineNumber($lineNumber)->shouldBeCalled(); 77 | 78 | $this->execute($input); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/Command/LineRemoveCommandSpec.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 | namespace spec\Gnugat\Redaktilo\Command; 13 | 14 | use Gnugat\Redaktilo\Command\Sanitizer\LocationSanitizer; 15 | use Gnugat\Redaktilo\Command\Sanitizer\TextSanitizer; 16 | use Gnugat\Redaktilo\Text; 17 | use PhpSpec\ObjectBehavior; 18 | 19 | class LineRemoveCommandSpec extends ObjectBehavior 20 | { 21 | const ORIGINAL_FILENAME = '%s/tests/fixtures/sources/life-of-brian.txt'; 22 | const EXPECTED_FILENAME = '%s/tests/fixtures/expectations/life-of-brian-remove.txt'; 23 | 24 | private $rootPath; 25 | 26 | function let(TextSanitizer $textSanitizer, LocationSanitizer $locationSanitizer, Text $text) 27 | { 28 | $this->rootPath = __DIR__.'/../../../../../'; 29 | 30 | $filename = sprintf(self::ORIGINAL_FILENAME, $this->rootPath); 31 | $lines = file($filename, FILE_IGNORE_NEW_LINES); 32 | 33 | $text->getLines()->willReturn($lines); 34 | 35 | $this->beConstructedWith( 36 | $textSanitizer, 37 | $locationSanitizer 38 | ); 39 | } 40 | 41 | function it_is_a_command() 42 | { 43 | $this->shouldImplement('Gnugat\Redaktilo\Command\Command'); 44 | } 45 | 46 | function it_removes_lines(TextSanitizer $textSanitizer, LocationSanitizer $locationSanitizer, Text $text) 47 | { 48 | $expectedFilename = sprintf(self::EXPECTED_FILENAME, $this->rootPath); 49 | $expectedLines = file($expectedFilename, FILE_IGNORE_NEW_LINES); 50 | 51 | $lineNumber = 2; 52 | 53 | $input = [ 54 | 'text' => $text, 55 | 'location' => $lineNumber, 56 | ]; 57 | 58 | $textSanitizer->sanitize($input)->willReturn($text); 59 | $locationSanitizer->sanitize($input)->willReturn($lineNumber); 60 | 61 | $text->setLines($expectedLines)->shouldBeCalled(); 62 | $text->getLength()->willReturn(count($expectedLines)); 63 | $text->setCurrentLineNumber($lineNumber)->shouldBeCalled(); 64 | 65 | $this->execute($input); 66 | 67 | $input = [ 68 | 'text' => $text, 69 | ]; 70 | 71 | $textSanitizer->sanitize($input)->willReturn($text); 72 | $locationSanitizer->sanitize($input)->willReturn($lineNumber); 73 | 74 | $text->getCurrentLineNumber()->willReturn($lineNumber); 75 | $text->setCurrentLineNumber($lineNumber)->shouldBeCalled(); 76 | 77 | $this->execute($input); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/Command/LineReplaceAllCommandSpec.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 | namespace spec\Gnugat\Redaktilo\Command; 13 | 14 | use Gnugat\Redaktilo\Command\Sanitizer\TextSanitizer; 15 | use Gnugat\Redaktilo\EditorFactory; 16 | use Gnugat\Redaktilo\Service\ContentFactory; 17 | use PhpSpec\ObjectBehavior; 18 | 19 | class LineReplaceAllCommandSpec extends ObjectBehavior 20 | { 21 | const PATTERN = '/snigger/'; 22 | const REPLACEMENT = 'mocker'; 23 | 24 | private $contentFactory; 25 | 26 | function let() 27 | { 28 | $this->contentFactory = new ContentFactory(); 29 | $textSanitizer = new TextSanitizer(); 30 | 31 | $this->beConstructedWith($this->contentFactory, $textSanitizer); 32 | } 33 | 34 | function it_is_a_command() 35 | { 36 | $this->shouldImplement('Gnugat\Redaktilo\Command\Command'); 37 | } 38 | 39 | function it_replaces_all_occurences() 40 | { 41 | $editor = EditorFactory::createEditor(); 42 | $filename = __DIR__.'/../../../../fixtures/sources/life-of-brian.txt'; 43 | $text = $editor->open($filename); 44 | 45 | $expectedFilename = __DIR__.'/../../../../fixtures/expectations/life-of-brian-replace-all.txt'; 46 | $expectedContent = file_get_contents($expectedFilename); 47 | 48 | $this->execute([ 49 | 'text' => $text, 50 | 'pattern' => self::PATTERN, 51 | 'replacement' => self::REPLACEMENT, 52 | ]); 53 | 54 | $actualContent = $this->contentFactory->make($text); 55 | expect($actualContent)->toBe($expectedContent); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/Command/LineReplaceCommandSpec.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 | namespace spec\Gnugat\Redaktilo\Command; 13 | 14 | use Gnugat\Redaktilo\Command\Sanitizer\LocationSanitizer; 15 | use Gnugat\Redaktilo\Command\Sanitizer\TextSanitizer; 16 | use Gnugat\Redaktilo\Text; 17 | use PhpSpec\ObjectBehavior; 18 | 19 | class LineReplaceCommandSpec extends ObjectBehavior 20 | { 21 | const ORIGINAL_FILENAME = '%s/tests/fixtures/sources/life-of-brian.txt'; 22 | const EXPECTED_FILENAME = '%s/tests/fixtures/expectations/life-of-brian-replace.txt'; 23 | 24 | const LINE_NUMBER = 5; 25 | 26 | private $rootPath; 27 | 28 | function let(TextSanitizer $textSanitizer, LocationSanitizer $locationSanitizer, Text $text) 29 | { 30 | $this->rootPath = __DIR__.'/../../../../../'; 31 | 32 | $filename = sprintf(self::ORIGINAL_FILENAME, $this->rootPath); 33 | $lines = file($filename, FILE_IGNORE_NEW_LINES); 34 | $line = $lines[self::LINE_NUMBER]; 35 | 36 | $text->getLine(self::LINE_NUMBER)->willReturn($line); 37 | 38 | $this->beConstructedWith( 39 | $textSanitizer, 40 | $locationSanitizer 41 | ); 42 | } 43 | 44 | function it_is_a_command() 45 | { 46 | $this->shouldImplement('Gnugat\Redaktilo\Command\Command'); 47 | } 48 | 49 | function it_replaces_line(TextSanitizer $textSanitizer, LocationSanitizer $locationSanitizer, Text $text) 50 | { 51 | $expectedFilename = sprintf(self::EXPECTED_FILENAME, $this->rootPath); 52 | $expectedLines = file($expectedFilename, FILE_IGNORE_NEW_LINES); 53 | $expectedLine = $expectedLines[self::LINE_NUMBER]; 54 | $replacement = function ($line) { 55 | return '[Even more sniggering]'; 56 | }; 57 | 58 | $input = [ 59 | 'text' => $text, 60 | 'location' => self::LINE_NUMBER, 61 | 'replacement' => $replacement, 62 | ]; 63 | 64 | $textSanitizer->sanitize($input)->willReturn($text); 65 | $locationSanitizer->sanitize($input)->willReturn(self::LINE_NUMBER); 66 | 67 | $text->setLine($expectedLine, self::LINE_NUMBER)->shouldBeCalled(); 68 | $text->setCurrentLineNumber(self::LINE_NUMBER)->shouldBeCalled(); 69 | 70 | $this->execute($input); 71 | 72 | $input = [ 73 | 'text' => $text, 74 | 'replacement' => $replacement, 75 | ]; 76 | 77 | $textSanitizer->sanitize($input)->willReturn($text); 78 | $locationSanitizer->sanitize($input)->willReturn(self::LINE_NUMBER); 79 | 80 | $text->getCurrentLineNumber()->willReturn(self::LINE_NUMBER); 81 | $text->setCurrentLineNumber(self::LINE_NUMBER)->shouldBeCalled(); 82 | 83 | $this->execute($input); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/Command/Sanitizer/LocationSanitizerSpec.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 | namespace spec\Gnugat\Redaktilo\Command\Sanitizer; 13 | 14 | use Gnugat\Redaktilo\Command\Sanitizer\TextSanitizer; 15 | use Gnugat\Redaktilo\Text; 16 | use PhpSpec\ObjectBehavior; 17 | 18 | class LocationSanitizerSpec extends ObjectBehavior 19 | { 20 | const LINE_NUMBER = 42; 21 | 22 | function let(TextSanitizer $textSanitizer, Text $text) 23 | { 24 | $text->getCurrentLineNumber()->willReturn(self::LINE_NUMBER); 25 | $text->getLength()->willReturn(50); 26 | 27 | $this->beConstructedWith($textSanitizer); 28 | } 29 | 30 | function it_uses_current_line_if_no_location_is_given(TextSanitizer $textSanitizer, Text $text) 31 | { 32 | $input = [ 33 | 'text' => $text, 34 | ]; 35 | 36 | $textSanitizer->sanitize($input)->willReturn($text); 37 | $this->sanitize($input)->shouldReturn(self::LINE_NUMBER); 38 | } 39 | 40 | function it_fails_when_the_line_number_is_invalid(TextSanitizer $textSanitizer, Text $text) 41 | { 42 | $exception = '\Gnugat\Redaktilo\Exception\InvalidLineNumberException'; 43 | 44 | $input = [ 45 | 'location' => 'toto', 46 | ]; 47 | $textSanitizer->sanitize($input)->willReturn($text); 48 | $this->shouldThrow($exception)->duringSanitize($input); 49 | 50 | $input = [ 51 | 'location' => -1, 52 | ]; 53 | $textSanitizer->sanitize($input)->willReturn($text); 54 | $this->shouldThrow($exception)->duringSanitize($input); 55 | 56 | $input = [ 57 | 'location' => 55, 58 | ]; 59 | $textSanitizer->sanitize($input)->willReturn($text); 60 | $this->shouldThrow($exception)->duringSanitize($input); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/Command/Sanitizer/TextSanitizerSpec.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 | namespace spec\Gnugat\Redaktilo\Command\Sanitizer; 13 | 14 | use Gnugat\Redaktilo\File; 15 | use Gnugat\Redaktilo\Text; 16 | use PhpSpec\ObjectBehavior; 17 | 18 | class TextSanitizerSpec extends ObjectBehavior 19 | { 20 | function it_fails_when_no_text_is_given() 21 | { 22 | $exception = '\Gnugat\Redaktilo\Exception\InvalidArgumentException'; 23 | 24 | $input = []; 25 | $this->shouldThrow($exception)->duringSanitize($input); 26 | 27 | $input = [ 28 | 'replacement' => 'I\'m your father', 29 | ]; 30 | $this->shouldThrow($exception)->duringSanitize($input); 31 | 32 | $input = [ 33 | 'text' => null, 34 | ]; 35 | $this->shouldThrow($exception)->duringSanitize($input); 36 | } 37 | 38 | function it_returns_the_given_text_instance(Text $text, File $file) 39 | { 40 | $input = [ 41 | 'text' => $text, 42 | ]; 43 | $this->sanitize($input)->shouldReturn($text); 44 | 45 | $input = [ 46 | 'text' => $file, 47 | ]; 48 | $this->sanitize($input)->shouldReturn($file); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/EditorFactorySpec.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 | namespace spec\Gnugat\Redaktilo; 13 | 14 | use PhpSpec\ObjectBehavior; 15 | 16 | class EditorFactorySpec extends ObjectBehavior 17 | { 18 | function it_creates_default_editor_instances() 19 | { 20 | $this->createEditor()->shouldReturnAnInstanceOf('Gnugat\Redaktilo\Editor'); 21 | } 22 | 23 | function it_creates_builders() 24 | { 25 | $this->createBuilder()->shouldReturnAnInstanceOf('Gnugat\Redaktilo\Service\EditorBuilder'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/EditorSpec.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 | namespace spec\Gnugat\Redaktilo; 13 | 14 | use Gnugat\Redaktilo\Command\CommandInvoker; 15 | use Gnugat\Redaktilo\File; 16 | use Gnugat\Redaktilo\Search\SearchEngine; 17 | use Gnugat\Redaktilo\Search\SearchStrategy; 18 | use Gnugat\Redaktilo\Service\Filesystem; 19 | use Gnugat\Redaktilo\Text; 20 | use PhpSpec\ObjectBehavior; 21 | 22 | class EditorSpec extends ObjectBehavior 23 | { 24 | const FILENAME = '/tmp/file-to-edit.txt'; 25 | 26 | function let( 27 | File $file, 28 | Filesystem $filesystem, 29 | SearchEngine $searchEngine, 30 | CommandInvoker $commandInvoker 31 | ) { 32 | $file->getFilename()->willReturn(self::FILENAME); 33 | 34 | $this->beConstructedWith( 35 | $filesystem, 36 | $searchEngine, 37 | $commandInvoker 38 | ); 39 | } 40 | 41 | function it_opens_existing_files(Filesystem $filesystem, File $file) 42 | { 43 | $filesystem->exists(self::FILENAME)->willReturn(true); 44 | $filesystem->open(self::FILENAME)->willReturn($file); 45 | 46 | $this->open(self::FILENAME)->shouldReturn($file); 47 | } 48 | 49 | function it_cannot_open_new_files(Filesystem $filesystem, File $file) 50 | { 51 | $exception = new \Gnugat\Redaktilo\Exception\FileNotFoundException( 52 | self::FILENAME 53 | ); 54 | 55 | $filesystem->exists(self::FILENAME)->willReturn(false); 56 | $filesystem->open(self::FILENAME)->willThrow($exception); 57 | 58 | $this->shouldThrow($exception)->duringOpen(self::FILENAME); 59 | } 60 | 61 | function it_creates_new_files(Filesystem $filesystem, File $file) 62 | { 63 | $filesystem->exists(self::FILENAME)->willReturn(false); 64 | $filesystem->create(self::FILENAME)->willReturn($file); 65 | 66 | $this->open(self::FILENAME, true); 67 | } 68 | 69 | function it_saves_files(Filesystem $filesystem, File $file) 70 | { 71 | $filesystem->write($file)->shouldBeCalled(); 72 | 73 | $this->save($file); 74 | } 75 | 76 | function it_moves_the_cursor_above_the_current_line( 77 | SearchEngine $searchEngine, 78 | SearchStrategy $searchStrategy, 79 | Text $text 80 | ) { 81 | $pattern = 'Nobody expects the Spanish Inquisition!'; 82 | $foundLineNumber = 4423; 83 | 84 | $searchEngine->resolve($pattern)->willReturn($searchStrategy); 85 | $searchStrategy->findAbove($text, $pattern, null)->willReturn($foundLineNumber); 86 | $text->setCurrentLineNumber($foundLineNumber)->shouldBeCalled(); 87 | 88 | $this->jumpAbove($text, $pattern); 89 | 90 | $searchStrategy->findAbove($text, $pattern, null)->willReturn(false); 91 | $exception = '\Gnugat\Redaktilo\Exception\PatternNotFoundException'; 92 | $this->shouldThrow($exception)->duringJumpAbove($text, $pattern); 93 | } 94 | 95 | function it_moves_the_cursor_above_the_given_line( 96 | SearchEngine $searchEngine, 97 | SearchStrategy $searchStrategy, 98 | Text $text 99 | ) { 100 | $pattern = 'Nobody expects the Spanish Inquisition!'; 101 | $foundLineNumber = 4423; 102 | 103 | $searchEngine->resolve($pattern)->willReturn($searchStrategy); 104 | $searchStrategy->findAbove($text, $pattern, 0)->willReturn($foundLineNumber); 105 | $text->setCurrentLineNumber($foundLineNumber)->shouldBeCalled(); 106 | 107 | $this->jumpAbove($text, $pattern, 0); 108 | 109 | $searchStrategy->findAbove($text, $pattern, 0)->willReturn(false); 110 | $exception = '\Gnugat\Redaktilo\Exception\PatternNotFoundException'; 111 | $this->shouldThrow($exception)->duringJumpAbove($text, $pattern, 0); 112 | } 113 | 114 | function it_moves_the_cursor_below_the_current_line( 115 | SearchEngine $searchEngine, 116 | SearchStrategy $searchStrategy, 117 | Text $text 118 | ) { 119 | $pattern = 'No one expects the Spanish inquisition!'; 120 | $foundLineNumber = 42; 121 | 122 | $searchEngine->resolve($pattern)->willReturn($searchStrategy); 123 | $searchStrategy->findBelow($text, $pattern, null)->willReturn($foundLineNumber); 124 | $text->setCurrentLineNumber($foundLineNumber)->shouldBeCalled(); 125 | 126 | $this->jumpBelow($text, $pattern); 127 | 128 | $searchStrategy->findBelow($text, $pattern, null)->willReturn(false); 129 | $exception = '\Gnugat\Redaktilo\Exception\PatternNotFoundException'; 130 | $this->shouldThrow($exception)->duringJumpBelow($text, $pattern); 131 | } 132 | 133 | function it_moves_the_cursor_below_the_given_line( 134 | SearchEngine $searchEngine, 135 | SearchStrategy $searchStrategy, 136 | Text $text 137 | ) { 138 | $pattern = 'No one expects the Spanish inquisition!'; 139 | $foundLineNumber = 42; 140 | 141 | $searchEngine->resolve($pattern)->willReturn($searchStrategy); 142 | $searchStrategy->findBelow($text, $pattern, 0)->willReturn($foundLineNumber); 143 | $text->setCurrentLineNumber($foundLineNumber)->shouldBeCalled(); 144 | 145 | $this->jumpBelow($text, $pattern, 0); 146 | 147 | $searchStrategy->findBelow($text, $pattern, 0)->willReturn(false); 148 | $exception = '\Gnugat\Redaktilo\Exception\PatternNotFoundException'; 149 | $this->shouldThrow($exception)->duringJumpBelow($text, $pattern, 0); 150 | } 151 | 152 | function it_checks_pattern_existence_above_the_current_line( 153 | SearchEngine $searchEngine, 154 | SearchStrategy $searchStrategy, 155 | Text $text 156 | ) { 157 | $pattern = 'Nobody expects the Spanish Inquisition!'; 158 | $foundLineNumber = 4423; 159 | 160 | $searchEngine->resolve($pattern)->willReturn($searchStrategy); 161 | $searchStrategy->findAbove($text, $pattern, null)->willReturn($foundLineNumber); 162 | 163 | $this->hasAbove($text, $pattern)->shouldBe(true); 164 | 165 | $searchStrategy->findAbove($text, $pattern, null)->willReturn(false); 166 | $this->hasAbove($text, $pattern)->shouldBe(false); 167 | } 168 | 169 | function it_checks_pattern_existence_above_the_given_line( 170 | SearchEngine $searchEngine, 171 | SearchStrategy $searchStrategy, 172 | Text $text 173 | ) { 174 | $pattern = 'Nobody expects the Spanish Inquisition!'; 175 | $foundLineNumber = 4423; 176 | 177 | $searchEngine->resolve($pattern)->willReturn($searchStrategy); 178 | $searchStrategy->findAbove($text, $pattern, 0)->willReturn($foundLineNumber); 179 | 180 | $this->hasAbove($text, $pattern, 0)->shouldBe(true); 181 | 182 | $searchStrategy->findAbove($text, $pattern, 0)->willReturn(false); 183 | $this->hasAbove($text, $pattern, 0)->shouldBe(false); 184 | } 185 | 186 | function it_checks_pattern_existence_below_the_current_line( 187 | SearchEngine $searchEngine, 188 | SearchStrategy $searchStrategy, 189 | Text $text 190 | ) { 191 | $pattern = 'No one expects the Spanish inquisition!'; 192 | $foundLineNumber = 42; 193 | 194 | $searchEngine->resolve($pattern)->willReturn($searchStrategy); 195 | $searchStrategy->findBelow($text, $pattern, null)->willReturn($foundLineNumber); 196 | 197 | $this->hasBelow($text, $pattern)->shouldBe(true); 198 | 199 | $searchStrategy->findBelow($text, $pattern, null)->willReturn(false); 200 | $this->hasBelow($text, $pattern)->shouldBe(false); 201 | } 202 | 203 | function it_checks_pattern_existence_below_the_given_line( 204 | SearchEngine $searchEngine, 205 | SearchStrategy $searchStrategy, 206 | Text $text 207 | ) { 208 | $pattern = 'No one expects the Spanish inquisition!'; 209 | $foundLineNumber = 42; 210 | 211 | $searchEngine->resolve($pattern)->willReturn($searchStrategy); 212 | $searchStrategy->findBelow($text, $pattern, 0)->willReturn($foundLineNumber); 213 | 214 | $this->hasBelow($text, $pattern, 0)->shouldBe(true); 215 | 216 | $searchStrategy->findBelow($text, $pattern, 0)->willReturn(false); 217 | $this->hasBelow($text, $pattern, 0)->shouldBe(false); 218 | } 219 | 220 | function it_inserts_lines_above_the_current_one( 221 | CommandInvoker $commandInvoker, 222 | Text $text 223 | ) { 224 | $addition = 'We are the knights who say Ni!'; 225 | $input = [ 226 | 'text' => $text, 227 | 'location' => null, 228 | 'addition' => $addition, 229 | ]; 230 | 231 | $commandInvoker->run('insert_above', $input)->shouldBeCalled(); 232 | 233 | $this->insertAbove($text, $addition); 234 | } 235 | 236 | function it_inserts_lines_above_the_given_one( 237 | CommandInvoker $commandInvoker, 238 | Text $text 239 | ) { 240 | $lineNumber = 43; 241 | $addition = 'We are the knights who say Ni!'; 242 | $input = [ 243 | 'text' => $text, 244 | 'location' => $lineNumber, 245 | 'addition' => $addition, 246 | ]; 247 | 248 | $commandInvoker->run('insert_above', $input)->shouldBeCalled(); 249 | 250 | $this->insertAbove($text, $addition, $lineNumber); 251 | } 252 | 253 | function it_inserts_lines_below_the_current_one( 254 | CommandInvoker $commandInvoker, 255 | Text $text 256 | ) { 257 | $addition = 'We are the knights who say Ni!'; 258 | $input = [ 259 | 'text' => $text, 260 | 'location' => null, 261 | 'addition' => $addition, 262 | ]; 263 | 264 | $commandInvoker->run('insert_below', $input)->shouldBeCalled(); 265 | 266 | $this->insertBelow($text, $addition); 267 | } 268 | 269 | function it_inserts_lines_below_the_given_one( 270 | CommandInvoker $commandInvoker, 271 | Text $text 272 | ) { 273 | $lineNumber = 43; 274 | $addition = 'We are the knights who say Ni!'; 275 | $input = [ 276 | 'text' => $text, 277 | 'location' => $lineNumber, 278 | 'addition' => $addition, 279 | ]; 280 | 281 | $commandInvoker->run('insert_below', $input)->shouldBeCalled(); 282 | 283 | $this->insertBelow($text, $addition, $lineNumber); 284 | } 285 | 286 | function it_replaces_the_current_line( 287 | CommandInvoker $commandInvoker, 288 | Text $text 289 | ) { 290 | $replacement = 'We are knights who say Ni!'; 291 | $input = [ 292 | 'text' => $text, 293 | 'location' => null, 294 | 'replacement' => $replacement, 295 | ]; 296 | 297 | $commandInvoker->run('replace', $input)->shouldBeCalled(); 298 | 299 | $this->replace($text, $replacement); 300 | } 301 | 302 | function it_replaces_the_given_line( 303 | CommandInvoker $commandInvoker, 304 | Text $text 305 | ) { 306 | $lineNumber = 43; 307 | $replacement = 'We are knights who say Ni!'; 308 | $input = [ 309 | 'text' => $text, 310 | 'location' => $lineNumber, 311 | 'replacement' => $replacement, 312 | ]; 313 | 314 | $commandInvoker->run('replace', $input)->shouldBeCalled(); 315 | 316 | $this->replace($text, $replacement, $lineNumber); 317 | } 318 | 319 | function it_replaces_all_occurences( 320 | CommandInvoker $commandInvoker, 321 | Text $text 322 | ) { 323 | $pattern = '/*/'; 324 | $replacement = 'Spam'; 325 | $input = [ 326 | 'text' => $text, 327 | 'pattern' => $pattern, 328 | 'replacement' => $replacement, 329 | ]; 330 | 331 | $commandInvoker->run('replace_all', $input)->shouldBeCalled(); 332 | 333 | $this->replaceAll($text, $pattern, $replacement); 334 | } 335 | 336 | function it_removes_the_current_line( 337 | CommandInvoker $commandInvoker, 338 | Text $text 339 | ) { 340 | $input = [ 341 | 'text' => $text, 342 | 'location' => null, 343 | ]; 344 | 345 | $commandInvoker->run('remove', $input)->shouldBeCalled(); 346 | 347 | $this->remove($text); 348 | } 349 | 350 | function it_removes_the_given_line( 351 | CommandInvoker $commandInvoker, 352 | Text $text 353 | ) { 354 | $lineNumber = 43; 355 | $input = [ 356 | 'text' => $text, 357 | 'location' => $lineNumber, 358 | ]; 359 | 360 | $commandInvoker->run('remove', $input)->shouldBeCalled(); 361 | 362 | $this->remove($text, $lineNumber); 363 | } 364 | 365 | function it_runs_a_command(CommandInvoker $commandInvoker) 366 | { 367 | $name = 'walk_in_a_silly_way'; 368 | $input = []; 369 | 370 | $commandInvoker->run($name, $input)->shouldBeCalled(); 371 | 372 | $this->run($name, $input); 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/FileSpec.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 | namespace spec\Gnugat\Redaktilo; 13 | 14 | use PhpSpec\ObjectBehavior; 15 | 16 | class FileSpec extends ObjectBehavior 17 | { 18 | function let() 19 | { 20 | $this->beConstructedThrough('fromString', ['Hello World']); 21 | } 22 | 23 | function it_is_a_text() 24 | { 25 | $this->shouldBeAnInstanceOf('Gnugat\Redaktilo\Text'); 26 | } 27 | 28 | function it_can_have_a_filename() 29 | { 30 | $this->setFilename('tmp/old.txt'); 31 | $this->getFilename()->shouldBe('tmp/old.txt'); 32 | 33 | $this->setFilename('tmp/new.txt'); 34 | $this->getFilename()->shouldBe('tmp/new.txt'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/Search/LineRegexSearchStrategySpec.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 | namespace spec\Gnugat\Redaktilo\Search; 13 | 14 | use Gnugat\Redaktilo\Text; 15 | use PhpSpec\ObjectBehavior; 16 | 17 | class LineRegexSearchStrategySpec extends ObjectBehavior 18 | { 19 | const FILENAME = '%s/tests/fixtures/sources/life-of-brian.txt'; 20 | 21 | function let(Text $text) 22 | { 23 | $rootPath = __DIR__.'/../../../../..'; 24 | 25 | $filename = sprintf(self::FILENAME, $rootPath); 26 | $lines = file($filename, FILE_IGNORE_NEW_LINES); 27 | 28 | $text->getLines()->willReturn($lines); 29 | } 30 | 31 | function it_is_a_search_strategy() 32 | { 33 | $this->shouldImplement('Gnugat\Redaktilo\Search\SearchStrategy'); 34 | } 35 | 36 | function it_supports_lines_regex() 37 | { 38 | $regexp = '#\.{3}Dickus\?#'; 39 | $line = 'Sir Bedevere: Good. Now, why do witches burn?'; 40 | $rawLine = $line."\n"; 41 | $lineNumber = 42; 42 | 43 | $this->supports($regexp)->shouldBe(true); 44 | $this->supports($line)->shouldBe(false); 45 | $this->supports($rawLine)->shouldBe(false); 46 | $this->supports($lineNumber)->shouldBe(false); 47 | } 48 | 49 | function it_finds_above_occurences(Text $text) 50 | { 51 | $aboveLineRegex = '/\[A \w+ sniggers\]/'; 52 | $aboveLineNumber = 1; 53 | $immediateAboveLineRegex = '/^Pontius Pilate: \'.../'; 54 | $immediateAboveLineNumber = 2; 55 | $currentLineRegex = '/More sniggering/'; 56 | $currentLineNumber = 3; 57 | $belowLineRegex = '/\[Sniggering\]/'; 58 | 59 | $this->findAbove($text, $belowLineRegex, $currentLineNumber)->shouldBe(false); 60 | $this->findAbove($text, $currentLineRegex, $currentLineNumber)->shouldBe(false); 61 | $this->findAbove($text, $immediateAboveLineRegex, $currentLineNumber)->shouldBe($immediateAboveLineNumber); 62 | $this->findAbove($text, $aboveLineRegex, $currentLineNumber)->shouldBe($aboveLineNumber); 63 | 64 | $text->getCurrentLineNumber()->willReturn($currentLineNumber); 65 | 66 | $this->findAbove($text, $belowLineRegex)->shouldBe(false); 67 | $this->findAbove($text, $currentLineRegex)->shouldBe(false); 68 | $this->findAbove($text, $immediateAboveLineRegex)->shouldBe($immediateAboveLineNumber); 69 | $this->findAbove($text, $aboveLineRegex)->shouldBe($aboveLineNumber); 70 | } 71 | 72 | function it_finds_below_occurences(Text $text) 73 | { 74 | $aboveLineRegex = '/\[A \w+ sniggers\]/'; 75 | $currentLineRegex = '/More sniggering/'; 76 | $currentLineNumber = 3; 77 | $immediateLineBelowRegex = '/^Pontius Pilate: \'What/'; 78 | $immediateLineBelowNumber = 4; 79 | $belowLineRegex = '/\[Sniggering\]/'; 80 | $belowLineNumber = 5; 81 | 82 | $this->findBelow($text, $aboveLineRegex, $currentLineNumber)->shouldBe(false); 83 | $this->findBelow($text, $currentLineRegex, $currentLineNumber)->shouldBe(false); 84 | $this->findBelow($text, $immediateLineBelowRegex, $currentLineNumber)->shouldBe($immediateLineBelowNumber); 85 | $this->findBelow($text, $belowLineRegex, $currentLineNumber)->shouldBe($belowLineNumber); 86 | 87 | $text->getCurrentLineNumber()->willReturn($currentLineNumber); 88 | 89 | $this->findBelow($text, $aboveLineRegex)->shouldBe(false); 90 | $this->findBelow($text, $currentLineRegex)->shouldBe(false); 91 | $this->findBelow($text, $immediateLineBelowRegex)->shouldBe($immediateLineBelowNumber); 92 | $this->findBelow($text, $belowLineRegex)->shouldBe($belowLineNumber); 93 | } 94 | 95 | function it_finds_relatively_to_the_first_line(Text $text) 96 | { 97 | $pattern = '/\[Sniggering\]/'; 98 | $lineNumber = 5; 99 | $text->getCurrentLineNumber()->shouldNotBeCalled(); 100 | 101 | $this->findAbove($text, $pattern, 0)->shouldBe(false); 102 | $this->findBelow($text, $pattern, 0)->shouldBe($lineNumber); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/Search/SameSearchStrategySpec.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 | namespace spec\Gnugat\Redaktilo\Search; 13 | 14 | use Gnugat\Redaktilo\Text; 15 | use PhpSpec\ObjectBehavior; 16 | 17 | class SameSearchStrategySpec extends ObjectBehavior 18 | { 19 | const FILENAME = '%s/tests/fixtures/sources/life-of-brian.txt'; 20 | 21 | function let(Text $text) 22 | { 23 | $rootPath = __DIR__.'/../../../../..'; 24 | 25 | $filename = sprintf(self::FILENAME, $rootPath); 26 | $lines = file($filename, FILE_IGNORE_NEW_LINES); 27 | 28 | $text->getLines()->willReturn($lines); 29 | } 30 | 31 | function it_is_a_search_strategy() 32 | { 33 | $this->shouldImplement('Gnugat\Redaktilo\Search\SearchStrategy'); 34 | } 35 | 36 | function it_supports_lines() 37 | { 38 | $line = 'Sir Bedevere: Good. Now, why do witches burn?'; 39 | $rawLine = $line."\n"; 40 | $lineNumber = 42; 41 | 42 | $this->supports($line)->shouldBe(true); 43 | $this->supports($rawLine)->shouldBe(false); 44 | $this->supports($lineNumber)->shouldBe(false); 45 | } 46 | 47 | function it_finds_above_occurences(Text $text) 48 | { 49 | $aboveLine = '[A guard sniggers]'; 50 | $aboveLineNumber = 1; 51 | $immediateAboveLine = 'Pontius Pilate: \'...Dickus?\''; 52 | $immediateAboveLineNumber = 2; 53 | $currentLine = '[More sniggering]'; 54 | $currentLineNumber = 3; 55 | $belowLine = '[Sniggering]'; 56 | 57 | $this->findAbove($text, $belowLine, $currentLineNumber)->shouldBe(false); 58 | $this->findAbove($text, $currentLine, $currentLineNumber)->shouldBe(false); 59 | $this->findAbove($text, $immediateAboveLine, $currentLineNumber)->shouldBe($immediateAboveLineNumber); 60 | $this->findAbove($text, $aboveLine, $currentLineNumber)->shouldBe($aboveLineNumber); 61 | 62 | $text->getCurrentLineNumber()->willReturn($currentLineNumber); 63 | 64 | $this->findAbove($text, $belowLine)->shouldBe(false); 65 | $this->findAbove($text, $currentLine)->shouldBe(false); 66 | $this->findAbove($text, $immediateAboveLine)->shouldBe($immediateAboveLineNumber); 67 | $this->findAbove($text, $aboveLine)->shouldBe($aboveLineNumber); 68 | } 69 | 70 | function it_finds_below_occurences(Text $text) 71 | { 72 | $aboveLine = '[A guard sniggers]'; 73 | $currentLine = '[More sniggering]'; 74 | $currentLineNumber = 3; 75 | $belowLine = '[Sniggering]'; 76 | $belowLineNumber = 5; 77 | 78 | $this->findBelow($text, $aboveLine, $currentLineNumber)->shouldBe(false); 79 | $this->findBelow($text, $currentLine, $currentLineNumber)->shouldBe(false); 80 | $this->findBelow($text, $belowLine, $currentLineNumber)->shouldBe($belowLineNumber); 81 | 82 | $text->getCurrentLineNumber()->willReturn($currentLineNumber); 83 | 84 | $this->findBelow($text, $aboveLine)->shouldBe(false); 85 | $this->findBelow($text, $currentLine)->shouldBe(false); 86 | $this->findBelow($text, $belowLine)->shouldBe($belowLineNumber); 87 | } 88 | 89 | function it_finds_relatively_to_the_first_line(Text $text) 90 | { 91 | $pattern = '[Sniggering]'; 92 | $lineNumber = 5; 93 | $text->getCurrentLineNumber()->shouldNotBeCalled(); 94 | 95 | $this->findAbove($text, $pattern, 0)->shouldBe(false); 96 | $this->findBelow($text, $pattern, 0)->shouldBe($lineNumber); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/Search/SearchEngineSpec.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 | namespace spec\Gnugat\Redaktilo\Search; 13 | 14 | use Gnugat\Redaktilo\Search\SearchStrategy; 15 | use PhpSpec\ObjectBehavior; 16 | 17 | class SearchEngineSpec extends ObjectBehavior 18 | { 19 | function let(SearchStrategy $searchStrategy) 20 | { 21 | $this->registerStrategy($searchStrategy); 22 | } 23 | 24 | function it_sort_registered_strategies( 25 | SearchStrategy $searchStrategy1, 26 | SearchStrategy $searchStrategy2, 27 | SearchStrategy $searchStrategy3 28 | ) { 29 | $pattern = '/ac/'; 30 | 31 | $searchStrategy1->supports($pattern)->willReturn(true); 32 | $searchStrategy2->supports($pattern)->willReturn(true); 33 | $searchStrategy3->supports($pattern)->willReturn(true); 34 | 35 | $this->registerStrategy($searchStrategy1, 10); 36 | $this->registerStrategy($searchStrategy2, 0); 37 | $this->registerStrategy($searchStrategy3, 20); 38 | 39 | $this->resolve($pattern)->shouldReturn($searchStrategy3); 40 | } 41 | 42 | function it_resolves_registered_strategies(SearchStrategy $searchStrategy) 43 | { 44 | $pattern = 'We are now no longer the Knights who say Ni.'; 45 | 46 | $searchStrategy->supports($pattern)->willReturn(true); 47 | 48 | $this->resolve($pattern)->shouldBe($searchStrategy); 49 | } 50 | 51 | function it_fails_when_the_strategy_is_not_supported(SearchStrategy $searchStrategy) 52 | { 53 | $pattern = 'We are now no longer the Knights who say Ni.'; 54 | $exception = '\Gnugat\Redaktilo\Exception\NotSupportedException'; 55 | 56 | $searchStrategy->supports($pattern)->willReturn(false); 57 | 58 | $this->shouldThrow($exception)->duringResolve($pattern); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/Service/ContentFactorySpec.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 | namespace spec\Gnugat\Redaktilo\Service; 13 | 14 | use Gnugat\Redaktilo\EditorFactory; 15 | use PhpSpec\ObjectBehavior; 16 | 17 | class ContentFactorySpec extends ObjectBehavior 18 | { 19 | private $editor; 20 | 21 | function let() 22 | { 23 | $this->editor = EditorFactory::createEditor(); 24 | } 25 | 26 | function it_creates_content_from_text() 27 | { 28 | $filename = __DIR__.'/../../../../fixtures/sources/life-of-brian.txt'; 29 | $expectedContent = file_get_contents($filename); 30 | $text = $this->editor->open($filename); 31 | 32 | $this->make($text)->shouldBe($expectedContent); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/Service/EditorBuilderSpec.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 | namespace spec\Gnugat\Redaktilo\Service; 13 | 14 | use Gnugat\Redaktilo\Command\Command; 15 | use Gnugat\Redaktilo\Command\CommandInvoker; 16 | use Gnugat\Redaktilo\Search\SearchEngine; 17 | use Gnugat\Redaktilo\Search\SearchStrategy; 18 | use PhpSpec\ObjectBehavior; 19 | use PHPUnit\Framework\Constraint\IsEqual; 20 | 21 | class EditorBuilderSpec extends ObjectBehavior 22 | { 23 | function it_can_build_the_default_editor() 24 | { 25 | $editor = $this->getEditor(); 26 | 27 | $editor->shouldBeAnInstanceOf('Gnugat\Redaktilo\Editor'); 28 | 29 | $editor->shouldHaveSearchStrategies([ 30 | 'Gnugat\Redaktilo\Search\PhpSearchStrategy', 31 | 'Gnugat\Redaktilo\Search\LineNumberSearchStrategy', 32 | 'Gnugat\Redaktilo\Search\LineRegexSearchStrategy', 33 | 'Gnugat\Redaktilo\Search\SameSearchStrategy', 34 | ]); 35 | 36 | $editor->shouldHaveCommands([ 37 | 'insert_above' => 'Gnugat\Redaktilo\Command\LineInsertAboveCommand', 38 | 'insert_below' => 'Gnugat\Redaktilo\Command\LineInsertBelowCommand', 39 | 'replace' => 'Gnugat\Redaktilo\Command\LineReplaceCommand', 40 | 'replace_all' => 'Gnugat\Redaktilo\Command\LineReplaceAllCommand', 41 | 'remove' => 'Gnugat\Redaktilo\Command\LineRemoveCommand', 42 | ]); 43 | } 44 | 45 | function it_can_have_custom_search_strategies(SearchStrategy $searchStrategy) 46 | { 47 | $editor = $this 48 | ->addSearchStrategy($searchStrategy) 49 | ->getEditor(); 50 | 51 | $editor->shouldBeAnInstanceOf('Gnugat\Redaktilo\Editor'); 52 | $editor->shouldHaveSearchStrategiesCount(5); 53 | } 54 | 55 | function it_can_have_a_custom_search_engine(SearchEngine $searchEngine) 56 | { 57 | $editor = $this 58 | ->setSearchEngine($searchEngine) 59 | ->getEditor(); 60 | 61 | $editor->shouldBeAnInstanceOf('Gnugat\Redaktilo\Editor'); 62 | expect(static::readProperty($editor->getWrappedObject(), 'searchEngine'))->toBe($searchEngine); 63 | } 64 | 65 | function it_can_have_custom_commands(Command $command) 66 | { 67 | $editor = $this 68 | ->addCommand($command) 69 | ->getEditor(); 70 | 71 | $editor->shouldBeAnInstanceOf('Gnugat\Redaktilo\Editor'); 72 | $editor->shouldHaveCommandCount(6); 73 | } 74 | 75 | function it_can_have_a_custom_command_invoker(CommandInvoker $commandInvoker) 76 | { 77 | $editor = $this 78 | ->setCommandInvoker($commandInvoker) 79 | ->getEditor(); 80 | 81 | $editor->shouldBeAnInstanceOf('Gnugat\Redaktilo\Editor'); 82 | expect(static::readProperty($editor->getWrappedObject(), 'commandInvoker'))->toBe($commandInvoker); 83 | } 84 | 85 | function getMatchers(): array 86 | { 87 | $readProperty = function ($object, $propertyName) { 88 | return EditorBuilderSpec::readProperty($object, $propertyName); 89 | }; 90 | 91 | return [ 92 | 'haveSearchStrategies' => function ($subject, $expected) use ($readProperty) { 93 | $engine = $readProperty($subject, 'searchEngine'); 94 | $strategies = array_map( 95 | 'get_class', 96 | array_filter( 97 | call_user_func_array( 98 | 'array_merge', 99 | $readProperty($engine, 'searchStrategies') 100 | ) 101 | ) 102 | ); 103 | 104 | $constraint = new IsEqual($expected); 105 | 106 | return $constraint->evaluate($strategies, '', true); 107 | }, 108 | 'haveCommands' => function ($subject, $expected) use ($readProperty) { 109 | $commandInvoker = $readProperty($subject, 'commandInvoker'); 110 | $commands = array_map('get_class', $readProperty($commandInvoker, 'commands')); 111 | 112 | $constraint = new IsEqual($expected); 113 | 114 | return $constraint->evaluate($commands, '', true); 115 | }, 116 | 'haveSearchStrategiesCount' => function ($subject, $expected) use ($readProperty) { 117 | $engine = $readProperty($subject, 'searchEngine'); 118 | $count = array_reduce( 119 | $readProperty($engine, 'searchStrategies'), 120 | function ($carry, $item) { 121 | return $carry + count($item); 122 | } 123 | ); 124 | 125 | return $expected == $count; 126 | }, 127 | 'haveCommandCount' => function ($subject, $expected) use ($readProperty) { 128 | $commandInvoker = $readProperty($subject, 'commandInvoker'); 129 | $commands = $readProperty($commandInvoker, 'commands'); 130 | 131 | return $expected == count($commands); 132 | }, 133 | ]; 134 | } 135 | 136 | static function readProperty($object, $propertyName) 137 | { 138 | $reflectedClass = new \ReflectionObject($object); 139 | $property = $reflectedClass->getProperty($propertyName); 140 | $property->setAccessible(true); 141 | 142 | return $property->getValue($object); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/Service/FilesystemSpec.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 | namespace spec\Gnugat\Redaktilo\Service; 13 | 14 | use Gnugat\Redaktilo\File; 15 | use Gnugat\Redaktilo\Service\ContentFactory; 16 | use PhpSpec\ObjectBehavior; 17 | use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem; 18 | 19 | class FilesystemSpec extends ObjectBehavior 20 | { 21 | private $sourceFilename; 22 | private $copyFilename; 23 | 24 | private $fileCopier; 25 | 26 | function let(SymfonyFilesystem $symfonyFilesystem) 27 | { 28 | $this->sourceFilename = __DIR__.'/../../../../fixtures/sources/copy-me.txt'; 29 | $this->copyFilename = __DIR__.'/../../../../fixtures/copies/edit-me.txt'; 30 | 31 | $this->fileCopier = new SymfonyFilesystem(); 32 | $contentFactory = new ContentFactory(); 33 | 34 | $this->beConstructedWith($symfonyFilesystem, $contentFactory); 35 | } 36 | 37 | function it_opens_existing_files() 38 | { 39 | $this->fileCopier->copy($this->sourceFilename, $this->copyFilename, true); 40 | 41 | $file = $this->open($this->copyFilename); 42 | 43 | $file->shouldHaveType('Gnugat\Redaktilo\File'); 44 | } 45 | 46 | function it_cannot_open_new_files() 47 | { 48 | @unlink($this->copyFilename); 49 | 50 | $exception = '\Gnugat\Redaktilo\Exception\FileNotFoundException'; 51 | $this->shouldThrow($exception)->duringOpen($this->copyFilename); 52 | } 53 | 54 | function it_creates_new_files() 55 | { 56 | @unlink($this->copyFilename); 57 | 58 | $file = $this->create($this->copyFilename); 59 | 60 | $file->shouldHaveType('Gnugat\Redaktilo\File'); 61 | } 62 | 63 | function it_cannot_create_existing_files() 64 | { 65 | $this->fileCopier->copy($this->sourceFilename, $this->copyFilename, true); 66 | 67 | $exception = '\Gnugat\Redaktilo\Exception\IOException'; 68 | $this->shouldThrow($exception)->duringCreate($this->copyFilename); 69 | } 70 | 71 | function it_detects_if_file_exists() 72 | { 73 | @unlink($this->copyFilename); 74 | 75 | $this->exists($this->copyFilename)->shouldBe(false); 76 | 77 | $this->fileCopier->copy($this->sourceFilename, $this->copyFilename, true); 78 | 79 | $this->exists($this->copyFilename)->shouldBe(true); 80 | } 81 | 82 | function it_writes_files(SymfonyFilesystem $symfonyFilesystem) 83 | { 84 | $this->fileCopier->copy($this->sourceFilename, $this->copyFilename, true); 85 | $content = file_get_contents($this->copyFilename); 86 | $lines = file($this->copyFilename); 87 | $file = File::fromArray($lines); 88 | $file->setFilename($this->copyFilename); 89 | 90 | $symfonyFilesystem->dumpFile($this->copyFilename, $content, null)->shouldBeCalled(); 91 | 92 | $this->write($file); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/Service/TextToPhpConverterSpec.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 | namespace spec\Gnugat\Redaktilo\Service; 13 | 14 | use Gnugat\Redaktilo\Search\Php\TokenBuilder; 15 | use Gnugat\Redaktilo\Text; 16 | use Gnugat\Redaktilo\Util\StringUtil; 17 | use PhpSpec\ObjectBehavior; 18 | 19 | class TextToPhpConverterSpec extends ObjectBehavior 20 | { 21 | const FILENAME = '%s/tests/fixtures/sources/php-sample.php'; 22 | 23 | function let(TokenBuilder $tokenBuilder) 24 | { 25 | $this->beConstructedWith($tokenBuilder); 26 | } 27 | 28 | function it_converts_file_content_into_php_tokens(TokenBuilder $tokenBuilder, Text $text) 29 | { 30 | $rootPath = __DIR__.'/../../../../../'; 31 | $filename = sprintf(self::FILENAME, $rootPath); 32 | $content = file_get_contents($filename); 33 | $lineBreak = StringUtil::detectLineBreak($content); 34 | $lines = explode($lineBreak, $content); 35 | $rawTokens = token_get_all($content); 36 | $text->getLineBreak()->willReturn($lineBreak); 37 | $text->getLines()->willReturn($lines); 38 | 39 | $tokenBuilder->buildFromRaw($rawTokens)->willReturn([]); 40 | $this->from($text); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/spec/Gnugat/Redaktilo/TextSpec.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 | namespace spec\Gnugat\Redaktilo; 13 | 14 | use Gnugat\Redaktilo\Util\StringUtil; 15 | use PhpSpec\ObjectBehavior; 16 | 17 | class TextSpec extends ObjectBehavior 18 | { 19 | private $content; 20 | private $lines; 21 | 22 | function let() 23 | { 24 | $rootPath = __DIR__.'/../../../../'; 25 | $filename = '%s/tests/fixtures/sources/life-of-brian.txt'; 26 | $this->content = file_get_contents(sprintf($filename, $rootPath)); 27 | 28 | $this->lines = preg_split('/\R/', $this->content); 29 | 30 | $this->beConstructedThrough('fromString', [$this->content]); 31 | } 32 | 33 | function it_has_lines() 34 | { 35 | $newContent = [ 36 | 'This', 37 | 'is an EX parrot!', 38 | ]; 39 | 40 | $this->getLines()->shouldBe($this->lines); 41 | 42 | $this->setLines($newContent); 43 | $this->getLines()->shouldBe($newContent); 44 | } 45 | 46 | function it_has_a_length() 47 | { 48 | $newContent = [ 49 | 'YOU', 50 | 'SHOULD NOT', 51 | 'PASS', 52 | ]; 53 | 54 | $this->getLength()->shouldBe(count($this->lines)); 55 | 56 | $this->setLines($newContent); 57 | $this->getLength()->shouldBe(3); 58 | } 59 | 60 | function it_has_a_current_line_number() 61 | { 62 | $this->getCurrentLineNumber()->shouldBe(0); 63 | 64 | $middleLine = intval(count($this->lines) / 2); 65 | 66 | $this->setCurrentLineNumber($middleLine); 67 | $this->getCurrentLineNumber()->shouldBe($middleLine); 68 | } 69 | 70 | function it_fails_when_the_line_number_is_invalid() 71 | { 72 | $exception = '\Gnugat\Redaktilo\Exception\InvalidLineNumberException'; 73 | 74 | $this->shouldThrow($exception)->duringSetCurrentLineNumber('toto'); 75 | $this->shouldThrow($exception)->duringSetCurrentLineNumber(-1); 76 | $this->shouldThrow($exception)->duringSetCurrentLineNumber(9); 77 | } 78 | 79 | function it_has_a_line_break() 80 | { 81 | $newLineBreak = "\r\n"; 82 | 83 | $this->getLineBreak()->shouldBe(StringUtil::detectLineBreak($this->content)); 84 | 85 | $this->setLineBreak($newLineBreak); 86 | $this->getLineBreak()->shouldBe($newLineBreak); 87 | } 88 | 89 | function it_manipulates_the_current_line() 90 | { 91 | $line = '[A guard struggles not to snigger]'; 92 | $this->setCurrentLineNumber(1); 93 | 94 | $this->getLine()->shouldBe('[A guard sniggers]'); 95 | $this->setLine($line); 96 | $this->getLine()->shouldBe($line); 97 | } 98 | 99 | function it_manipulates_the_given_line() 100 | { 101 | $lineNumber = 5; 102 | $line = '[Even more sniggering]'; 103 | 104 | $this->getLine($lineNumber)->shouldBe('[Sniggering]'); 105 | $this->setLine($line, $lineNumber); 106 | $this->getLine($lineNumber)->shouldBe($line); 107 | } 108 | 109 | function it_cannot_manipulate_an_invalid_line() 110 | { 111 | $exception = '\Gnugat\Redaktilo\Exception\InvalidLineNumberException'; 112 | $line = 'I came here to learn how to fly an aeroplane'; 113 | 114 | $this->shouldThrow($exception)->duringSetLine($line, 'toto'); 115 | $this->shouldThrow($exception)->duringSetLine($line, -1); 116 | $this->shouldThrow($exception)->duringSetLine($line, 9); 117 | 118 | $this->shouldThrow($exception)->duringGetLine('toto'); 119 | $this->shouldThrow($exception)->duringGetLine(-1); 120 | $this->shouldThrow($exception)->duringGetLine(9); 121 | } 122 | 123 | function it_increments_current_line_number() 124 | { 125 | $this->setCurrentLineNumber(0); 126 | $this->incrementCurrentLineNumber(2); 127 | 128 | $this->getCurrentLineNumber()->shouldBe(2); 129 | } 130 | 131 | function it_cannot_increment_current_line_number_with_invalid_number() 132 | { 133 | $exception = '\Gnugat\Redaktilo\Exception\InvalidLineNumberException'; 134 | 135 | $this->setCurrentLineNumber(1); 136 | $lastLineNumber = count($this->lines) - 1; 137 | 138 | $this->shouldThrow($exception)->duringIncrementCurrentLineNumber('toto'); 139 | $this->shouldThrow($exception)->duringIncrementCurrentLineNumber(-1); 140 | $this->shouldThrow($exception)->duringIncrementCurrentLineNumber(4423); 141 | $this->shouldThrow($exception)->duringIncrementCurrentLineNumber($lastLineNumber); 142 | } 143 | 144 | function it_decrements_current_line_number() 145 | { 146 | $this->setCurrentLineNumber(3); 147 | $this->decrementCurrentLineNumber(2); 148 | 149 | $this->getCurrentLineNumber()->shouldBe(1); 150 | } 151 | 152 | function it_cannot_decrement_current_line_number_with_invalid_number() 153 | { 154 | $exception = '\Gnugat\Redaktilo\Exception\InvalidLineNumberException'; 155 | 156 | $lastLineNumber = count($this->lines) - 1; 157 | $this->setCurrentLineNumber($lastLineNumber - 1); 158 | 159 | $this->shouldThrow($exception)->duringDecrementCurrentLineNumber('toto'); 160 | $this->shouldThrow($exception)->duringDecrementCurrentLineNumber(-1); 161 | $this->shouldThrow($exception)->duringDecrementCurrentLineNumber(4423); 162 | $this->shouldThrow($exception)->duringDecrementCurrentLineNumber($lastLineNumber); 163 | } 164 | 165 | function it_can_loop_over_lines() 166 | { 167 | $i = 0; 168 | $lines = $this->lines; 169 | reset($lines); 170 | 171 | $this->map(function ($line, $lineNumber, $text) use (&$i, &$lines) { 172 | expect($line)->toBe(current($lines)); 173 | expect($lineNumber)->toBe($i++); 174 | expect($text)->toHaveType('Gnugat\Redaktilo\Text'); 175 | 176 | next($lines); 177 | }); 178 | 179 | expect($i)->toBe(9); // 8 lines + 1 last line feed character 180 | 181 | $this->getCurrentLineNumber()->shouldBe(8); 182 | } 183 | } 184 | --------------------------------------------------------------------------------