diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..6e65161
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+/*.* export-ignore
+/tests export-ignore
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7d21911
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+/.phpcs-cache
+/composer.lock
+/phpcs.xml
+/phpunit.xml
+/vendor/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..0d7b95c
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,42 @@
+sudo: false
+
+language: php
+
+php:
+ - 7.2
+ - nightly
+
+before_install:
+ - phpenv config-rm xdebug.ini || true
+ - phpenv config-add .travis/php.ini
+
+install:
+ - travis_retry composer install --classmap-authoritative --no-suggest --prefer-dist
+
+script:
+ - vendor/bin/phpunit
+
+jobs:
+ include:
+
+ - stage: Test
+ env: DEPENDENCIES=low
+ php: 7.2
+ install:
+ - travis_retry composer update --classmap-authoritative --no-suggest --prefer-dist --prefer-lowest --prefer-stable
+
+ - stage: Code Quality
+ env: CODING_STANDARDS
+ script:
+ - vendor/bin/phpcs -p
+
+allow_failures:
+ - php: nightly
+
+cache:
+ directories:
+ - $HOME/.composer/cache/files
+
+branches:
+ only:
+ - master
diff --git a/.travis/php.ini b/.travis/php.ini
new file mode 100644
index 0000000..c9ce25c
--- /dev/null
+++ b/.travis/php.ini
@@ -0,0 +1 @@
+short_open_tag = On
diff --git a/README.md b/README.md
index fca1257..bc7ee82 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,35 @@
Libero PHP coding standard
==========================
+
+[](https://travis-ci.com/libero/php-coding-standard)
+
+The Libero PHP coding standard is a set of [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer) rules applied to all Libero PHP projects. It is based on [PSR-1](https://www.php-fig.org/psr/psr-1/).
+
+Getting started
+---------------
+
+Using [Composer](https://getcomposer.org/) you can install the coding standard into your project:
+
+```
+composer require --dev libero/coding-standard
+```
+
+You can then find violations of the standard by running:
+
+```
+vendor/bin/phpcs --standard=Libero /path/to/some/file/to/sniff.php
+```
+
+Or automatically correct (at least some of) these violations by running:
+
+```
+vendor/bin/phpcbf --standard=Libero /path/to/some/file/to/sniff.php
+```
+
+See the [PHP_CodeSniffer documentation](https://github.com/squizlabs/PHP_CodeSniffer/wiki) for more details.
+
+Getting help
+------------
+
+- Report a bug or request a feature on [GitHub](https://github.com/libero/libero/issues/new/choose).
+- Ask a question on the [Libero Community Slack](https://libero-community.slack.com/).
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..97da514
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,30 @@
+{
+ "name": "libero/coding-standard",
+ "description": "Libero PHP coding standard",
+ "type": "phpcodesniffer-standard",
+ "license": "MIT",
+ "autoload-dev": {
+ "psr-4": {
+ "tests\\Libero\\CodingStandard\\": "tests/"
+ }
+ },
+ "require": {
+ "php": "^7.2",
+ "dealerdirect/phpcodesniffer-composer-installer": "^0.4",
+ "squizlabs/php_codesniffer": "^3.3"
+ },
+ "require-dev": {
+ "ext-tokenizer": "*",
+ "lstrojny/functional-php": "^1.8",
+ "phpunit/phpunit": "^7.3",
+ "symfony/finder": "^4.1"
+ },
+ "config": {
+ "sort-packages": true
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ }
+}
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
new file mode 100644
index 0000000..73de171
--- /dev/null
+++ b/phpcs.xml.dist
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ src/
+ tests/
+
+
+ */tests/*
+
+
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..edbd815
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+ tests
+
+
+
+
+
+ src
+
+
+
+
diff --git a/src/Libero/ruleset.xml b/src/Libero/ruleset.xml
new file mode 100644
index 0000000..5bc3697
--- /dev/null
+++ b/src/Libero/ruleset.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ The Libero coding standard.
+
+
+
+
+
+
diff --git a/tests/RulesetTests.php b/tests/RulesetTests.php
new file mode 100644
index 0000000..7999f20
--- /dev/null
+++ b/tests/RulesetTests.php
@@ -0,0 +1,142 @@
+config = new Config(['--standard=Libero']);
+ self::$codeSniffer->init();
+ }
+
+ /**
+ * @test
+ * @dataProvider cases
+ */
+ public function it_finds_and_fixes_violations(
+ string $filename,
+ string $contents,
+ string $fixed,
+ array $messages,
+ ?string $description
+ ) : void {
+ $file = $this->createFile($filename, $contents);
+ $actual = flatten($this->getMessages($file));
+
+ sort($actual);
+ sort($messages);
+
+ $this->assertSame($messages, $actual, $description);
+ $this->assertSame($fixed, $file->fixer->getContents());
+ }
+
+ public function cases() : iterable
+ {
+ $files = Finder::create()->files()->in(__DIR__.'/cases');
+
+ foreach ($files as $file) {
+ preg_match_all('~(?:---)?([A-Z]+)---\s+([\s\S]+?)\n---~', $file->getContents(), $matches);
+
+ $parts = array_combine(array_map('strtolower', $matches[1]), $matches[2]);
+
+ if (isset($parts['messages'])) {
+ $parts['messages'] = array_filter(explode("\n", $parts['messages']));
+ }
+
+ if (empty($parts['contents'])) {
+ throw new LogicException("Couldn't find contents in {$file->getRelativePathname()}");
+ } elseif (empty($parts['fixed']) && empty($parts['messages'])) {
+ throw new LogicException("Expected one of fixed or messages in {$file->getRelativePathname()}");
+ }
+
+ try {
+ token_get_all($parts['contents'], TOKEN_PARSE);
+ } catch (ParseError $exception) {
+ $message = "Failed to parse content in {$file->getRelativePathname()}: {$exception->getMessage()}";
+ throw new LogicException($message, 0, $exception);
+ }
+
+ if (!empty($parts['fixed'])) {
+ try {
+ token_get_all($parts['fixed'], TOKEN_PARSE);
+ } catch (ParseError $exception) {
+ $message = "Failed to parse fixed in {$file->getRelativePathname()}: {$exception->getMessage()}";
+ throw new LogicException($message, 0, $exception);
+ }
+ }
+
+ yield $file->getRelativePathname() => [
+ $parts['filename'] ?? 'test.php',
+ $parts['contents'],
+ $parts['fixed'] ?? $parts['contents'],
+ $parts['messages'] ?? [],
+ $parts['description'] ?? null,
+ ];
+ }
+ }
+
+ private function createFile(string $filename, string $content) : File
+ {
+ if (!ini_get('short_open_tag') && false === strpos($content, 'markTestSkipped('short_open_tag option is disabled');
+ }
+
+ $file = new DummyFile(
+ "phpcs_input_file:${filename}\n{$content}",
+ self::$codeSniffer->ruleset,
+ self::$codeSniffer->config
+ );
+
+ $file->process();
+
+ $file->fixer->fixFile();
+
+ $file = new DummyFile(
+ "phpcs_input_file:${filename}\n{$file->fixer->getContents()}",
+ self::$codeSniffer->ruleset,
+ self::$codeSniffer->config
+ );
+
+ $file->process();
+
+ return $file;
+ }
+
+ private function getMessages(File $file) : iterable
+ {
+ foreach ([$file->getErrors(), $file->getWarnings()] as $messages) {
+ foreach ($messages as $line => $lineMessages) {
+ foreach ($lineMessages as $column => $columnMessages) {
+ foreach ($columnMessages as $data) {
+ yield "{$line}:{$column} {$data['source']}";
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..007ba25
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,6 @@
+