Loïc Faugeron Technical Blog

Redaktilo: because your code too needs an editor 09/05/2014

TL;DR: jump to the conclusion.

I've been working on a silly library lately: Redaktilo (it means editor in esperanto).

Redaktilo has been created to fulfill actual needs. In this article we'll see what it is all about, and why it won't stay silly for long.

Use case 1: YAML configuration edition

Incenteev\ParameterHandler is a good example: it updates a YAML configuration after each update of the dependencies using Composer.

It uses the Symfony2 Yaml component which converts a YAML string into a PHP array, and then converts it back. The problem with it is that it strips empty lines, custom formatting and comments...

Redaktilo only inserts a new line in the file, leaving it as it is.

Use case 2: JSON file edition

The composer.json file is really usefull and can be almost completly edited using the composer.phar CLI application.

Some part still need manual edition, like the script section. To automate this you could use json_decode and json_encode, but similarly to the previous use case you would lose empty lines and custom formatting...

Redaktilo aims at solving this problem, but isn't ready yet: inserting a line in JSON often means adding a comma at the end of the previous one.

Use case 3: PHP source code edition

To be fair this use case isn't limited to PHP source code: it can be useful for any plain text files (text, XML, java, python, anything).

GnugatWizardBundle automatically registers new bundles installed using Composer in your Symfony2 application.

To do so it uses SensioGeneratorBundle's KernelManipulator to insert a line in the app/AppKernel.php. However this class registers bundles for every environments, and doesn't take into account bundle which depend on the kernel.

If you take a look at the KernelManipulator source code you'll realise it has been a bit over engineered as it parses PHP tokens.

A new KernelManipulator could be written using Redaktilo as follow:

<?php

namespace Sensio\Bundle\GeneratorBundle\Manipulator;

use Gnugat\Redaktilo\Editor;

class KernelManipulator extends Manipulator
{
    protected $editor;
    protected $appKernelFilename;

    public function __construct(Editor $editor, $appKernelFilename)
    {
        $this->editor = $editor;
        $this->appKernelFilename = $appKernelFilename;
    }

    public function addBundle($bundle)
    {
        $file = $this->editor->open($this->appKernelFilename);
        $newLine = sprintf('            new %s(),', $bundle);

        $this->editor->jumpDownTo('    public function registerBundles()');
        $this->editor->jumpDownTo('        $bundles = array(');
        $this->editor->jumpDownTo('        );');

        $this->editor->addBefore($file, $newLine);

        $this->editor->save($file);

        return true;
    }
}

Usage

A great effort has been put to document the project, as you can see in the README.

Here's an overview!

You can install Redaktilo using Composer:

composer require "gnugat/redaktilo:~0.3@dev"

Then you need to create an instance of the Editor class:

<?php
require_once __DIR__.'/vendor/autoload.php';

use Gnugat\Redaktilo\Filesystem;
use Gnugat\Redaktilo\Editor;
use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;

$symfonyFilesystem = new SymfonyFilesystem();
$filesystem = new Filesystem($symfonyFilesystem);
$editor = new Editor($filesystem);

Editor is completly stateless, which means you can use the same instance everywhere in your scripts/applications/libraries.

Let's now have a look at the available classes and their responsibility.

File

The basic idea behind Redaktilo is to provide an object oriented way to represent files:

<?php

namespace Gnugat\Redaktilo;

class File
{
    public function getFilename();

    public function read();
    public function write($newContent);

    // ...
}

Once this domain model available, you can build services to manipulate it.

Filesystem

This is the first service available:

<?php

namespace Gnugat\Redaktilo;

class Filesystem
{
    public function open($filename); // Cannot open new files
    public function create($filename); // Cannot create existing files

    public function exists($filename);

    public function write(File $file);
}

It creates instances of File and write their content in the actual file.

Editor

Developers should only use the Editor class: it's a facade which provides the text edition metaphor:

<?php

namespace Gnugat\Redaktilo;

class Editor
{
    // Filesystem operations.
    public function open($filename, $force = false);
    public function save(File $file);

    // Line insertion.
    public function addBefore(File $file, $add);
    public function addAfter(File $file, $add);

    // Content navigation.
    public function jumpDownTo(File $file, $line);
    public function jumpUpTo(File $file, $line);
}

And that's it.It told you it was a small and simple library ;) . Now let's see what's planned for the next releases.

Version 0.4 should bring SearchEngine

There's still some search logic left in Editor.

To remove it, a whole system will be put in place: SearchEngineCollection will be called by Editor and will ask its SearchEngines if they support the pattern.

This should allow many search strategies:

You could then have an extending point!

Version 0.5 should bring ContentConverter

Some extra logic are also left in File, regarding the conversion of the content into an array of lines.

ContentConverter could take a file, and convert its content into anything:

This would allow new types of SearchEngine, and maybe the creation of ContentEditor which would bear the single responsibility of inserting, replacing or removing bits of it.

I need your humble opinion / help

I'd like to hear about more use cases: what would you do with Redaktilo? What would you like to do with it?

You can open issues to start discussions, just make sure to provide a real life use case ;) .

Conclusion

Redaktilo provides an Object Oriented way to manipulate files, through the editor metaphor: