1
0
mirror of https://github.com/pattern-lab/patternlab-php.git synced 2025-01-17 22:29:12 +01:00

adding Scan\KSS

This commit is contained in:
Dave Olsen 2014-05-20 14:11:36 -04:00
parent b8caf67390
commit 01576e89e8
17 changed files with 2080 additions and 0 deletions

View File

@ -0,0 +1,13 @@
; http://EditorConfig.org
root = true
[*]
indent_style = space
indent_size = 4
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true

3
core/lib/scan/kss-php/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
vendor/
build/
phpunit.xml

View File

@ -0,0 +1,23 @@
Contents of this project are a php conversion of the original ruby version found
at http://github.com/kneath/kss which included the following license:
Copyright (c) 2011 Tom Preston-Werner, Kyle Neath
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
Software), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,41 @@
# Code Modifications
## Guidelines
All code modifications must following [PSR-0][], [PSR-1][], and [PSR-2][] as
outlined on the [PHP Framework Interop Group][php-fig].
An .editorconfig file is included to help setup your environment if your IDE supports
[EditorConfig][].
## Procedure
* Fork the repository and create a topic branch from where you want to base your work.
* This is usually the master branch.
* To quickly create a topic branch based on master; `git branch
my_contribution master` then checkout the new branch with `git
checkout my_contribution`. Please avoid working directly on the
`master` branch.
* Make commits of logical units.
* Check for unnecessary whitespace with `git diff --check` before committing.
* Make sure your commit messages are in the proper format.
* If your commit messages do not follow this format, please do a
`git rebase -i master` to reword your commit messages.
````
Subject Line Describing Your Changes
The body of your commit message should describe the behavior without your
changes, why this is a problem, and how your changes fix the problem when
applied.
````
* Make sure you have added the necessary tests for your changes.
* Run all the tests to assure nothing else was accidentally broken.
[PSR-0]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
[php-fig]: http://www.php-fig.org
[EditorConfig]: http://editorconfig.org/

View File

@ -0,0 +1,20 @@
Copyright (c) 2013 Scan, Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
Software), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,125 @@
# Knyle Style Sheets
This is a PHP implementation of [Knyle Style Sheets](http://warpspire.com/kss) (KSS).
KSS attempts to provide a methodology for writing maintainable, documented CSS
within a team. Specifically, KSS is a documentation specification and styleguide
format. It is **not** a preprocessor, CSS framework, naming convention, or
specificity guideline.
* **[The Spec (What KSS is)](https://github.com/kneath/kss/blob/master/SPEC.md)**
* **[Example living styleguide](https://github.com/scaninc/kss-php/tree/master/example)**
## KSS in a nutshell
The methodology and ideas behind Knyle Style Sheets are contained in [SPEC.md](https://github.com/kneath/kss/blob/master/SPEC.md)
of the origin [ruby version](https://github.com/kneath/kss) of KSS. At its core,
KSS is a documenting syntax for CSS.
```css
/*
# Star Button
A button suitable for giving stars to someone.
Markup: <a class="button star $modifierClass">Button</a>
:hover - Subtle hover highlight.
.stars--given - A highlight indicating you've already given a star.
.stars--given:hover - Subtle hover highlight on top of stars-given styling.
.stars--disabled - Dims the button to indicate it cannot be used.
Styleguide 2.1.3.
*/
a.button.star {
...
}
a.button.star:hover {
...
}
a.button.stars--given {
...
}
a.button.stars--given:hover {
...
}
a.button.stars--disabled {
...
}
```
## PHP Library
This repository includes a php library suitable for parsing SASS, SCSS, and CSS
documented with KSS guidelines. To use the library, include it in your project as
a composer dependency (see below). Then, create a parser and explore your KSS.
```php
<?php
require_once('../vendors/autoload.php');
$styleguide = new \Scan\Kss\Parser('public/stylesheets')
$section = $styleguide->getSection('2.1.1');
// Returns a \Scan\Kss\Section object
echo $section->getTitle();
// Echoes "Star Button"
echo $section->getDescription();
// echoes "A button suitable for giving stars to someone."
echo $section->getMarkup();
// echoes "<a class="button star $modifierClass">Button</a>"
$modifier = current($section->getModifiers());
// Returns a \Scan\Kss\Modifier object
echo $modifier->getName();
// echoes ':hover'
echo $modifier->getClassName();
// echoes 'psuedo-class-hover'
echo $modifier->getDescription();
// echoes 'Subtle hover highlight'
echo $modifier->getExampleHtml();
// echoes <a class="button stars stars-given">Button</a> for the .stars-given modifier
```
## Generating styleguides
The documenting syntax and php library are intended to generate styleguides automatically.
To do this, you'll need to leverage a small javascript library that generates
class styles for pseudo-class styles (`:hover`, `:disabled`, etc).
* [kss.coffee](https://github.com/scaninc/kss-php/blob/master/lib/Scan/kss.coffee)
* [kss.js](https://github.com/scaninc/kss-php/blob/master/example/public/js/kss.js) (compiled js)
For an example of how to generate a styleguide, check out the [`example`](https://github.com/scaninc/kss-php/tree/master/example)
php pages.
## Dependencies
The PHP version of KSS has dependencies managed by Composer. If you did not install
kss-php using composer, you must install these dependencies manually before using
the library by running the following commands:
```
$ composer install
```
If you do not yet have Composer, download it following the instructions on
http://getcomposer.org or run the following commands to install it globally on
your system:
```
$ curl -s https://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/local/bin/composer
```
## Symfony2 Bundle
If your project uses [symfony2](http://symfony.com/), consider using the [KSS Bundle]
(https://github.com/scaninc/ScanKssBundle) as well. The KSS Bundle uses Twig templates
to make the styleguide block easier to customize and include in your views.

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by PHP Project Wizard (PPW) 1.0.4 on Mon Jan 28 10:08:26 MST 2013 -->
<project name="kss-php" default="build" basedir=".">
<property name="source" value="lib"/>
<target name="clean" description="Clean up and create artifact directories">
<delete dir="${basedir}/build/api"/>
<delete dir="${basedir}/build/code-browser"/>
<delete dir="${basedir}/build/coverage"/>
<delete dir="${basedir}/build/logs"/>
<delete dir="${basedir}/build/pdepend"/>
<mkdir dir="${basedir}/build/api"/>
<mkdir dir="${basedir}/build/code-browser"/>
<mkdir dir="${basedir}/build/coverage"/>
<mkdir dir="${basedir}/build/logs"/>
<mkdir dir="${basedir}/build/pdepend"/>
</target>
<target name="phpunit" description="Run unit tests using PHPUnit and generates junit.xml and clover.xml">
<exec executable="phpunit" failonerror="true"/>
</target>
<target name="parallelTasks" description="Run the pdepend, phpmd, phpcpd, phpcs, phpdoc and phploc tasks in parallel using a maximum of 2 threads.">
<parallel threadCount="2">
<sequential>
<antcall target="pdepend"/>
<antcall target="phpmd"/>
</sequential>
<antcall target="phpcpd"/>
<antcall target="phpcs"/>
<antcall target="phpdoc"/>
<antcall target="phploc"/>
</parallel>
</target>
<target name="pdepend" description="Generate jdepend.xml and software metrics charts using PHP_Depend">
<exec executable="pdepend">
<arg line="--jdepend-xml=${basedir}/build/logs/jdepend.xml
--jdepend-chart=${basedir}/build/pdepend/dependencies.svg
--overview-pyramid=${basedir}/build/pdepend/overview-pyramid.svg
${source}" />
</exec>
</target>
<target name="phpmd" description="Generate pmd.xml using PHPMD">
<exec executable="phpmd">
<arg line="${source}
xml
codesize,design,naming,unusedcode
--reportfile ${basedir}/build/logs/pmd.xml" />
</exec>
</target>
<target name="phpcpd" description="Generate pmd-cpd.xml using PHPCPD">
<exec executable="phpcpd">
<arg line="--log-pmd ${basedir}/build/logs/pmd-cpd.xml ${source}" />
</exec>
</target>
<target name="phploc" description="Generate phploc.csv">
<exec executable="phploc">
<arg line="--log-csv ${basedir}/build/logs/phploc.csv ${source}" />
</exec>
</target>
<target name="phpcs" description="Generate checkstyle.xml using PHP_CodeSniffer">
<exec executable="phpcs" output="/dev/null">
<arg line="--report=checkstyle
--report-file=${basedir}/build/logs/checkstyle.xml
--standard=PSR0
${source}" />
</exec>
</target>
<target name="phpdoc" description="Generate API documentation using PHPDocumentor">
<exec executable="phpdoc">
<arg line="-d ${source} -t ${basedir}/build/api" />
</exec>
</target>
<target name="phpcb" description="Aggregate tool output with PHP_CodeBrowser">
<exec executable="phpcb">
<arg line="--log ${basedir}/build/logs
--source ${source}
--output ${basedir}/build/code-browser" />
</exec>
</target>
<target name="build" depends="clean,parallelTasks,phpunit,phpcb"/>
</project>

View File

@ -0,0 +1,29 @@
{
"name": "scan/kss-php",
"description": "A PHP implementation of KSS: a methodology for documenting CSS and generating styleguides",
"keywords": [
"kss",
"styleguide",
"css documentation"
],
"license": "MIT",
"require": {
"php": ">=5.3.3",
"symfony/finder": "~2.1"
},
"require-dev": {
"phpunit/phpunit": "3.7.*"
},
"authors": [
{
"name": "Russell Ahlstrom",
"email": "russell.ahlstrom@gmail.com",
"homepage": "http://russell.ahlstromology.com"
}
],
"autoload": {
"psr-0": {
"Scan\\Kss": "lib/"
}
}
}

475
core/lib/scan/kss-php/composer.lock generated Normal file
View File

@ -0,0 +1,475 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
],
"hash": "52293773fb81531edc12e7533e95963a",
"packages": [
{
"name": "symfony/finder",
"version": "v2.3.0",
"target-dir": "Symfony/Component/Finder",
"source": {
"type": "git",
"url": "https://github.com/symfony/Finder.git",
"reference": "v2.3.0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Finder/zipball/v2.3.0",
"reference": "v2.3.0",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Finder\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony Finder Component",
"homepage": "http://symfony.com",
"time": "2013-06-02 12:05:51"
}
],
"packages-dev": [
{
"name": "phpunit/php-code-coverage",
"version": "1.2.11",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "1.2.11"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1.2.11",
"reference": "1.2.11",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"phpunit/php-file-iterator": ">=1.3.0@stable",
"phpunit/php-text-template": ">=1.1.1@stable",
"phpunit/php-token-stream": ">=1.1.3@stable"
},
"require-dev": {
"phpunit/phpunit": "3.7.*"
},
"suggest": {
"ext-dom": "*",
"ext-xdebug": ">=2.0.5"
},
"type": "library",
"autoload": {
"classmap": [
"PHP/"
]
},
"notification-url": "https://packagist.org/downloads/",
"include-path": [
""
],
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
"homepage": "https://github.com/sebastianbergmann/php-code-coverage",
"keywords": [
"coverage",
"testing",
"xunit"
],
"time": "2013-05-23 18:23:24"
},
{
"name": "phpunit/php-file-iterator",
"version": "1.3.3",
"source": {
"type": "git",
"url": "git://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "1.3.3"
},
"dist": {
"type": "zip",
"url": "https://github.com/sebastianbergmann/php-file-iterator/zipball/1.3.3",
"reference": "1.3.3",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"autoload": {
"classmap": [
"File/"
]
},
"notification-url": "https://packagist.org/downloads/",
"include-path": [
""
],
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "FilterIterator implementation that filters files based on a list of suffixes.",
"homepage": "http://www.phpunit.de/",
"keywords": [
"filesystem",
"iterator"
],
"time": "2012-10-11 04:44:38"
},
{
"name": "phpunit/php-text-template",
"version": "1.1.4",
"source": {
"type": "git",
"url": "git://github.com/sebastianbergmann/php-text-template.git",
"reference": "1.1.4"
},
"dist": {
"type": "zip",
"url": "https://github.com/sebastianbergmann/php-text-template/zipball/1.1.4",
"reference": "1.1.4",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"autoload": {
"classmap": [
"Text/"
]
},
"notification-url": "https://packagist.org/downloads/",
"include-path": [
""
],
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Simple template engine.",
"homepage": "https://github.com/sebastianbergmann/php-text-template/",
"keywords": [
"template"
],
"time": "2012-10-31 11:15:28"
},
{
"name": "phpunit/php-timer",
"version": "1.0.4",
"source": {
"type": "git",
"url": "git://github.com/sebastianbergmann/php-timer.git",
"reference": "1.0.4"
},
"dist": {
"type": "zip",
"url": "https://github.com/sebastianbergmann/php-timer/zipball/1.0.4",
"reference": "1.0.4",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"autoload": {
"classmap": [
"PHP/"
]
},
"notification-url": "https://packagist.org/downloads/",
"include-path": [
""
],
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Utility class for timing",
"homepage": "http://www.phpunit.de/",
"keywords": [
"timer"
],
"time": "2012-10-11 04:45:58"
},
{
"name": "phpunit/php-token-stream",
"version": "1.1.5",
"source": {
"type": "git",
"url": "git://github.com/sebastianbergmann/php-token-stream.git",
"reference": "1.1.5"
},
"dist": {
"type": "zip",
"url": "https://github.com/sebastianbergmann/php-token-stream/zipball/1.1.5",
"reference": "1.1.5",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": ">=5.3.3"
},
"type": "library",
"autoload": {
"classmap": [
"PHP/"
]
},
"notification-url": "https://packagist.org/downloads/",
"include-path": [
""
],
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Wrapper around PHP's tokenizer extension.",
"homepage": "http://www.phpunit.de/",
"keywords": [
"tokenizer"
],
"time": "2012-10-11 04:47:14"
},
{
"name": "phpunit/phpunit",
"version": "3.7.21",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "3.7.21"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3.7.21",
"reference": "3.7.21",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-pcre": "*",
"ext-reflection": "*",
"ext-spl": "*",
"php": ">=5.3.3",
"phpunit/php-code-coverage": ">=1.2.1,<1.3.0",
"phpunit/php-file-iterator": ">=1.3.1",
"phpunit/php-text-template": ">=1.1.1",
"phpunit/php-timer": ">=1.0.2,<1.1.0",
"phpunit/phpunit-mock-objects": ">=1.2.0,<1.3.0",
"symfony/yaml": ">=2.0,<3.0"
},
"require-dev": {
"pear-pear/pear": "1.9.4"
},
"suggest": {
"ext-json": "*",
"ext-simplexml": "*",
"ext-tokenizer": "*",
"phpunit/php-invoker": ">=1.1.0,<1.2.0"
},
"bin": [
"composer/bin/phpunit"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.7.x-dev"
}
},
"autoload": {
"classmap": [
"PHPUnit/"
]
},
"notification-url": "https://packagist.org/downloads/",
"include-path": [
"",
"../../symfony/yaml/"
],
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "The PHP Unit Testing framework.",
"homepage": "http://www.phpunit.de/",
"keywords": [
"phpunit",
"testing",
"xunit"
],
"time": "2013-05-23 18:54:29"
},
{
"name": "phpunit/phpunit-mock-objects",
"version": "1.2.3",
"source": {
"type": "git",
"url": "git://github.com/sebastianbergmann/phpunit-mock-objects.git",
"reference": "1.2.3"
},
"dist": {
"type": "zip",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects/archive/1.2.3.zip",
"reference": "1.2.3",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"phpunit/php-text-template": ">=1.1.1@stable"
},
"suggest": {
"ext-soap": "*"
},
"type": "library",
"autoload": {
"classmap": [
"PHPUnit/"
]
},
"notification-url": "https://packagist.org/downloads/",
"include-path": [
""
],
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Mock Object library for PHPUnit",
"homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
"keywords": [
"mock",
"xunit"
],
"time": "2013-01-13 10:24:48"
},
{
"name": "symfony/yaml",
"version": "v2.2.2",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "v2.2.2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/v2.2.2",
"reference": "v2.2.2",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2-dev"
}
},
"autoload": {
"psr-0": {
"Symfony\\Component\\Yaml\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "http://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com",
"time": "2013-05-10 18:08:31"
}
],
"aliases": [
],
"minimum-stability": "stable",
"stability-flags": [
],
"platform": {
"php": ">=5.3.3"
},
"platform-dev": [
]
}

View File

@ -0,0 +1,226 @@
<?php
/**
* CommentParser
*
* Searches a file for all single line and multi-line comments and stores them
* in the object for later use.
*/
namespace Scan\Kss;
class CommentParser
{
/**
* File being parsed
*
* @var \SplFileObject
*/
protected $file = null;
/**
* Options use to control the parser
*
* @var array
*/
protected $options = array();
/**
* Storage for comment blocks
*
* @var array
*/
protected $blocks = array();
/**
* Flag for whether the file has been parsed for comments yet
*
* @var boolean
*/
protected $parsed = false;
/**
* Sets up the parser with the file needed and any options to use when parsing
*
* @param \SplFileObject $file
* @param array $options
*/
public function __construct(\SplFileObject $file, array $options = array())
{
$this->file = $file;
$this->options = $options;
}
/**
* Returns the parsed comment blocks or if object is not yet parsed, parses
* first and then returns the result
*
* @return array
*/
public function getBlocks()
{
if (!$this->parsed) {
$this->parseBlocks();
}
return $this->blocks;
}
/**
* Parses each line of the file looking for single or multi-line comments
*
* @return array
*/
protected function parseBlocks()
{
$this->blocks = array();
$currentBlock = '';
// Do we need insideSingleLineBlock? It doesn't seem to be used anywhere
// Original Ruby version of KSS had it but I'm not seeing a purpose to it
$insideSingleLineBlock = false;
$insideMultiLineBlock = false;
foreach ($this->file as $line) {
$isSingleLineComment = self::isSingleLineComment($line);
$isStartMultiLineComment = self::isStartMultiLineComment($line);
$isEndMultiLineComment = self::isEndMultiLineComment($line);
if ($isSingleLineComment) {
$parsed = self::parseSingleLineComment($line);
if ($insideSingleLineBlock) {
$currentBlock .= "\n";
} else {
$insideSingleLineBlock = true;
}
$currentBlock .= $parsed;
}
if ($isStartMultiLineComment || $insideMultiLineBlock) {
$parsed = self::parseMultiLineComment($line);
if ($insideMultiLineBlock) {
$currentBlock .= "\n";
} else {
$insideMultiLineBlock = true;
}
$currentBlock .= $parsed;
}
if ($isEndMultiLineComment) {
$insideMultiLineBlock = false;
}
// If we're not in a comment then end the current block and go to
// the next one
if (!$isSingleLineComment && !$insideMultiLineBlock) {
if (!empty($currentBlock)) {
$this->blocks[] = $this->normalize($currentBlock);
$insideSingleLineBlock = false;
$currentBlock = '';
}
}
}
$this->parsed = true;
return $this->blocks;
}
/**
* Makes all the white space consistent among the lines in a comment block.
* That is if the first and second line had 10 spaces but the third line was
* indented to 15 spaces, we'd normalize it so the first and second line have
* no spaces and the third line has 5 spaces.
*
* @param string $block
*
* @return string
*/
protected function normalize($block)
{
// Remove any [whitespace]*'s from the start of each line
$normalizedBlock = preg_replace('-^\s*\*+-m', '', $block);
$indentSize = null;
$blockLines = explode("\n", $normalizedBlock);
$normalizedLines = array();
foreach ($blockLines as $line) {
preg_match('/^\s*/', $line, $matches);
$precedingWhitespace = strlen($matches[0]);
if ($indentSize === null) {
$indentSize = $precedingWhitespace;
}
if ($indentSize <= $precedingWhitespace && $indentSize > 0) {
$line = substr($line, $indentSize);
}
$normalizedLines[] = $line;
}
return trim(implode("\n", $normalizedLines));
}
/**
* Checks if the comment is a single line comment
*
* @param string $line
*
* @return boolean
*/
public static function isSingleLineComment($line)
{
return (bool) preg_match('-^\s*//-', $line);
}
/**
* Checks if the line is the start of a multi-line comment
*
* @param string $line
*
* @return boolean
*/
public static function isStartMultiLineComment($line)
{
return (bool) preg_match('-^\s*/\*-', $line);
}
/**
* Checks if the line is the end of a multi-line comment
*
* @param string $line
*
* @return boolean
*/
public static function isEndMultiLineComment($line)
{
return (bool) preg_match('-.*\*/-', $line);
}
/**
* Removes the comment markers from a single line comment and trims the line
*
* @param string $line
*
* @return string
*/
public static function parseSingleLineComment($line)
{
return rtrim(preg_replace('-^\s*//-', '', $line));
}
/**
* Removes the comment markers from a multi line comment and trims the line
*
* @param string $line
*
* @return string
*/
public static function parseMultiLineComment($line)
{
$parsed = preg_replace('-^\s*/\*+-', '', $line);
$parsed = preg_replace('-\*/-', '', $parsed);
return rtrim($parsed);
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Scan\Kss\Exception;
interface ExceptionInterface
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Scan\Kss\Exception;
class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface
{
}

View File

@ -0,0 +1,236 @@
<?php
/**
* Modifier
*
* Object to represent the modifiers section of a KSS Comment Block
*/
namespace Scan\Kss;
class Modifier
{
/**
* Name of the modifier
*
* @var string
*/
protected $name = '';
/**
* Description of the modifier
*
* @var string
*/
protected $description = '';
/**
* Optional markup for the modifier
*
* @var string
*/
protected $markup = null;
/**
* Class that this modifier extends from
*
* @var string
*/
protected $extendedClass = null;
/**
* Creates a new modifier by adding a name and a description
*
* @param string $name
* @param string $description
*/
public function __construct($name, $description = '')
{
$this->setName($name);
$this->setDescription($description);
}
/**
* Returns the name of the modifier
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Sets the name of the modifier
*
* @param string $name
*/
public function setName($name)
{
$name = $this->parseExtend($name);
$this->name = $name;
}
/**
* Returns the description of the modifier
*
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Sets the description of the modifier
*
* @param string $description
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* Returns the markup of the modifier
*
* @return string
*/
public function getMarkup()
{
return $this->markup;
}
/**
* Sets the markup of the modifier
*
* @param string $markup
*/
public function setMarkup($markup)
{
$this->markup = $markup;
}
/**
* Checks the name for any extend notations and parses that information
* off and stores it in the $this->extendedClass
*
* @param string $name
*
* @return $name
*/
protected function parseExtend($name)
{
$this->setExtendedClass(null);
$nameParts = explode('@extend', $name);
$name = trim($nameParts[0]);
if (count($nameParts) > 1) {
$this->setExtendedClass($nameParts[1]);
}
return $name;
}
/**
* Returns whether the modifier is applied by extension
*
* @return boolean
*/
public function isExtender()
{
return (bool) $this->getExtendedClass();
}
/**
* Returns the extended class
*
* @return string
*/
public function getExtendedClass()
{
return $this->extendedClass;
}
/**
* Sets the extended class. If the class name is empty, assuming null instead
* and stop further parsing
*
* @param string $class
*/
public function setExtendedClass($class)
{
if (empty($class)) {
$this->extenderClass = null;
return;
}
$this->extendedClass = trim($class);
}
/**
* Returns the class name for the extended class
*
* @return string
*/
public function getExtendedClassName()
{
if ($this->getExtendedClass() === null) {
return '';
}
$name = str_replace('%', ' ', $this->getExtendedClass());
$name = str_replace('.', ' ', $name);
$name = str_replace(':', ' pseudo-class-', $name);
return trim($name);
}
/**
* Returns the class name for the modifier
*
* @return string
*/
public function getClassName()
{
$name = str_replace('.', ' ', $this->name);
$name = str_replace(':', ' pseudo-class-', $name);
return trim($name);
}
/**
* Returns a string of specified html with inserted class names in the correct
* places for modifiers and extenders.
*
* @param string $html OPTIONAL
*
* @return string $html
*/
public function getExampleHtml($html = null)
{
if ($html === null) {
if ($this->getMarkup() === null) {
return '';
}
$html = $this->getMarkup();
}
if ($this->isExtender()) {
$html = str_replace('$modifierClass', '', $html);
// Use a positive lookbehind and lookahead to ensure we don't
// replace anything more than just the targeted class name
// for example an element name that is the same as the extended
// class name (e.g. button)
$pattern = sprintf('/(?<="| )%s(?="| )/', $this->getExtendedClassName());
$html = preg_replace(
$pattern,
$this->getClassName(),
$html
);
}
$html = str_replace('$modifierClass', $this->getClassName(), $html);
return $html;
}
}

View File

@ -0,0 +1,196 @@
<?php
/**
* Parser
*
* Accepts an array of directories and parses them stylesheet files present in
* them for KSS Comment Blocks
*/
namespace Scan\Kss;
use Symfony\Component\Finder\Finder;
use Scan\Kss\Exception\UnexpectedValueException;
class Parser
{
/**
* An array of the different KSS sections found in the parsed directories
*
* @var array
*/
protected $sections = array();
/**
* A flag on whether sections have been sorted
*
* @var boolean
*/
protected $sectionsSortedByReference = false;
/**
* Parses specified directories for KSS Comments and adds any valid KSS Sections
* found.
*
* @param string|array $paths A string or array of the paths to scan for KSS
* Comments
*/
public function __construct($paths)
{
$finder = new Finder();
// Only accept css, sass, scss, and less files.
$finder->files()->name('/\.(css|sass|scss|less)$/')->in($paths);
foreach ($finder as $fileInfo) {
$file = new \splFileObject($fileInfo);
$commentParser = new CommentParser($file);
foreach ($commentParser->getBlocks() as $commentBlock) {
if (self::isKssBlock($commentBlock)) {
$this->addSection($commentBlock, $file);
}
}
}
}
/**
* Adds a section to the Sections collection
*
* @param string $comment
* @param \splFileObject $file
*/
protected function addSection($comment, \splFileObject $file)
{
$section = new Section($comment, $file);
$this->sections[$section->getReference(true)] = $section;
$this->sectionsSortedByReference = false;
}
/**
* Returns a Section object matching the requested reference. If reference
* is not found, an empty Section object is returned instead
*
* @param string $reference
*
* @return Section
*
* @throws UnexepectedValueException if reference does not exist
*/
public function getSection($reference)
{
$reference = Section::trimReference($reference);
if (array_key_exists($reference, $this->sections)) {
return $this->sections[$reference];
}
return false;
}
/**
* Returns an array of all the sections
*
* @return array
*/
public function getSections()
{
$this->sortSections();
return $this->sections;
}
/**
* Returns only the top level sections (i.e. 1.0, 2.0, 3.0, etc.)
*
* @return array
*/
public function getTopLevelSections()
{
$this->sortSectionsByDepth();
$topLevelSections = array();
foreach ($this->sections as $section) {
if ($section->getDepth() != 0) {
break;
}
$topLevelSections[] = $section;
}
return $topLevelSections;
}
/**
* Returns an array of children for a specified section reference
*
* @param string $reference
* @param int $levelsDown OPTIONAL
*
* @return array
*/
public function getSectionChildren($reference, $levelsDown = null)
{
$this->sortSections();
$sectionKeys = array_keys($this->sections);
$sections = array();
$maxDepth = null;
if ($levelsDown !== null) {
$maxDepth = Section::calcDepth($reference) + $levelsDown;
}
$reference = Section::trimReference($reference);
$reference .= '.';
foreach ($sectionKeys as $sectionKey) {
// Only get sections within that level. Do not get the level itself
if (strpos($sectionKey . '.', $reference) === 0
&& $sectionKey . '.' != $reference
) {
$section = $this->sections[$sectionKey];
if ($maxDepth !== null && $section->getDepth() > $maxDepth) {
continue;
}
$sections[$sectionKey] = $section;
}
}
return $sections;
}
/**
* Method to only sort the sections if they need sorting
*
* @return void
*/
protected function sortSections()
{
if ($this->sectionsSortedByReference) {
return;
}
uasort($this->sections, '\Scan\Kss\Section::depthScoreSort');
$this->sectionsSortedByReference = true;
}
/**
* Method to sort the sections by depth
*
* @return void
*/
protected function sortSectionsByDepth()
{
uasort($this->sections, '\Scan\Kss\Section::depthSort');
$this->sectionsSortedByReference = false;
}
/**
* Checks to see if a comment block is a KSS Comment block
*
* @param string $comment
*
* @return boolean
*/
public static function isKssBlock($comment)
{
$commentLines = explode("\n\n", $comment);
$lastLine = end($commentLines);
return (bool) preg_match('/Pattern \S/i', $lastLine);
}
}

View File

@ -0,0 +1,518 @@
<?php
/**
* Section
*
* A KSS Comment Block that represents a single section containing a description,
* modifiers, and a section reference.
*/
namespace Scan\Kss;
class Section
{
/**
* The raw KSS Comment Block before it was chopped into pieces
*
* @var string
*/
protected $rawComment = '';
/**
* The sections of the KSS Comment Block
*
* @var array
*/
protected $commentSections = array();
/**
* The file where the KSS Comment Block came from
*
* @var \SplFileObject
*/
protected $file = null;
/**
* The parsed markup comment in the KSS Block
*
* @var string
*/
protected $markup = null;
/**
* The deprecation notice in the KSS Block
*
* @var string
*/
protected $deprecated = null;
/**
* The experimental notice in the KSS Block
*
* @var string
*/
protected $experimental = null;
/**
* The section reference identifier
*
* @var string
*/
protected $reference = null;
/**
* Creates a section with the KSS Comment Block and source file
*
* @param string $comment
* @param \SplFileObject $file
*/
public function __construct($comment = '', \SplFileObject $file = null)
{
$this->rawComment = $comment;
$this->file = $file;
}
/**
* Returns the source filename for where the comment block was located
*
* @return string
*/
public function getFilename()
{
if ($this->file === null) {
return '';
}
return $this->file->getFilename();
}
/**
* Returns the title of the section
*
* @return string
*/
public function getTitle()
{
$title = '';
$titleComment = $this->getTitleComment();
if (preg_match('/^\s*#+\s*(.+)/', $titleComment, $matches)) {
$title = $matches[1];
}
return $title;
}
/**
* Returns the description for the section
*
* @return string
*/
public function getDescription()
{
$descriptionSections = array();
foreach ($this->getCommentSections() as $commentSection) {
// Anything that is not the section comment or modifiers comment
// must be the description comment
if ($commentSection != $this->getReferenceComment()
&& $commentSection != $this->getTitleComment()
&& $commentSection != $this->getMarkupComment()
&& $commentSection != $this->getDeprecatedComment()
&& $commentSection != $this->getExperimentalComment()
&& $commentSection != $this->getModifiersComment()
) {
$descriptionSections[] = $commentSection;
}
}
return implode("\n\n", $descriptionSections);
}
/**
* Returns the markup defined in the section
*
* @return string
*/
public function getMarkup()
{
if ($this->markup === null) {
if ($markupComment = $this->getMarkupComment()) {
$this->markup = trim(preg_replace('/^\s*Markup:/i', '', $markupComment));
}
}
return $this->markup;
}
/**
* Returns the markup for the normal element (without modifierclass)
*
* @param string $replacement Replacement for $modifierClass variable
* @return void
*/
public function getMarkupNormal($replacement = '')
{
return str_replace('$modifierClass', $replacement, $this->getMarkup());
}
/**
* Returns the deprecation notice defined in the section
*
* @return string
*/
public function getDeprecated()
{
if ($this->deprecated === null) {
if ($deprecatedComment = $this->getDeprecatedComment()) {
$this->deprecated = trim(preg_replace('/^\s*Deprecated:/i', '', $deprecatedComment));
}
}
return $this->deprecated;
}
/**
* Returns the experimental notice defined in the section
*
* @return string
*/
public function getExperimental()
{
if ($this->experimental === null) {
if ($experimentalComment = $this->getExperimentalComment()) {
$this->experimental = trim(preg_replace('/^\s*Experimental:/i', '', $experimentalComment));
}
}
return $this->experimental;
}
/**
* Returns the modifiers used in the section
*
* @return array
*/
public function getModifiers()
{
$lastIndent = null;
$modifiers = array();
if ($modiferComment = $this->getModifiersComment()) {
$modifierLines = explode("\n", $modiferComment);
foreach ($modifierLines as $line) {
if (empty($line)) {
continue;
}
preg_match('/^\s*/', $line, $matches);
$indent = strlen($matches[0]);
if ($lastIndent && $indent > $lastIndent) {
$modifier = end($modifiers);
$modifier->setDescription($modifier->getDescription() + trim($line));
} else {
$lineParts = explode(' - ', $line);
$name = trim(array_shift($lineParts));
$description = '';
if (!empty($lineParts)) {
$description = trim(implode(' - ', $lineParts));
}
$modifier = new Modifier($name, $description);
// If the CSS has a markup, pass it to the modifier for the example HTML
if ($markup = $this->getMarkup()) {
$modifier->setMarkup($markup);
}
$modifiers[] = $modifier;
}
}
}
return $modifiers;
}
/**
* Returns the reference number for the section
*
* @return string
*
* @deprecated Method deprecated in v0.3.1
*/
public function getSection()
{
return $this->getReference();
}
/**
* Returns the reference number for the section
*
* @param boolean $trimmed OPTIONAL
*
* @return string
*/
public function getReference($trimmed = false)
{
if ($this->reference === null) {
$referenceComment = $this->getReferenceComment();
$referenceComment = preg_replace('/\.$/', '', $referenceComment);
if (preg_match('/Pattern (\S*)/', $referenceComment, $matches)) {
$this->reference = $matches[1];
}
}
return ($trimmed && $this->reference !== null)
? self::trimReference($this->reference)
: $this->reference;
}
/**
* Trims off all trailing zeros and periods on a reference
*
* @param string $reference
*
* @return string
*/
public static function trimReference($reference)
{
if (substr($reference, -1) == '.') {
$reference = substr($reference, 0, -1);
}
while (preg_match('/(\.0+)$/', $reference, $matches)) {
$reference = substr($reference, 0, strlen($matches[1]) * -1);
}
return $reference;
}
/**
* Checks to see if a section belongs to a specified reference
*
* @param string $reference
*
* @return boolean
*/
public function belongsToReference($reference)
{
$reference = self::trimReference($reference);
return strpos($this->getReference() . '.', $reference . '.') === 0;
}
/**
* Helper method for calculating the depth of the instantiated section
*
* @return int
*/
public function getDepth()
{
return self::calcDepth($this->getReference());
}
/**
* Calculates and returns the depth of a section reference
*
* @param string $reference
*
* @return int
*/
public static function calcDepth($reference)
{
$reference = self::trimReference($reference);
return substr_count($reference, '.');
}
/**
* Helper method for calculating the score of the instantiated section
*
* @return int
*/
public function getDepthScore()
{
return self::calcDepthScore($this->getReference());
}
/**
* Calculates and returns the depth score for the section. Useful for sorting
* sections correctly by their section reference numbers
*
* @return int
*/
public static function calcDepthScore($reference)
{
$reference = self::trimReference($reference);
$sectionParts = explode('.', $reference);
$score = 0;
foreach ($sectionParts as $level => $part) {
$score += $part * (1 / pow(10, $level));
}
return $score;
}
/**
* Function to help sort sections by depth and then depth score
*
* @param Section $a
* @param Section $b
*
* @return int
*/
public static function depthSort(Section $a, Section $b)
{
if ($a->getDepth() == $b->getDepth()) {
return self::depthScoreSort($a, $b);
}
return $a->getDepth() > $b->getDepth();
}
/**
* Function to help sort sections by their depth score
*
* @param Section $a
* @param Section $b
*
* @return int
*/
public static function depthScoreSort(Section $a, Section $b)
{
return $a->getDepthScore() > $b->getDepthScore();
}
/**
* Returns the comment block used when creating the section as an array of
* paragraphs within the comment block
*
* @return array
*/
protected function getCommentSections()
{
if (empty($this->commentSections) && $this->rawComment) {
$this->commentSections = explode("\n\n", $this->rawComment);
}
return $this->commentSections;
}
/**
* Gets the title part of the KSS Comment Block
*
* @return string
*/
protected function getTitleComment()
{
$titleComment = null;
foreach ($this->getCommentSections() as $commentSection) {
// Identify the title by the # markdown header syntax
if (preg_match('/^\s*#/i', $commentSection)) {
$titleComment = $commentSection;
break;
}
}
return $titleComment;
}
/**
* Returns the part of the KSS Comment Block that contains the markup
*
* @return string
*/
protected function getMarkupComment()
{
$markupComment = null;
foreach ($this->getCommentSections() as $commentSection) {
// Identify the markup comment by the Markup: marker
if (preg_match('/^\s*Markup:/i', $commentSection)) {
$markupComment = $commentSection;
break;
}
}
return $markupComment;
}
/**
* Returns the part of the KSS Comment Block that contains the deprecated
* notice
*
* @return string
*/
protected function getDeprecatedComment()
{
$deprecatedComment = null;
foreach ($this->getCommentSections() as $commentSection) {
// Identify the deprecation notice by the Deprecated: marker
if (preg_match('/^\s*Deprecated:/i', $commentSection)) {
$deprecatedComment = $commentSection;
break;
}
}
return $deprecatedComment;
}
/**
* Returns the part of the KSS Comment Block that contains the experimental
* notice
*
* @return string
*/
protected function getExperimentalComment()
{
$experimentalComment = null;
foreach ($this->getCommentSections() as $commentSection) {
// Identify the experimental notice by the Experimental: marker
if (preg_match('/^\s*Experimental:/i', $commentSection)) {
$experimentalComment = $commentSection;
break;
}
}
return $experimentalComment;
}
/**
* Gets the part of the KSS Comment Block that contains the section reference
*
* @return string
*/
protected function getReferenceComment()
{
$referenceComment = null;
foreach ($this->getCommentSections() as $commentSection) {
// Identify it by the Styleguide 1.2.3. pattern
if (preg_match('/Pattern \S/i', $commentSection)) {
$referenceComment = $commentSection;
break;
}
}
return $referenceComment;
}
/**
* Returns the part of the KSS Comment Block that contains the modifiers
*
* @return string
*/
protected function getModifiersComment()
{
$modifiersComment = null;
foreach ($this->getCommentSections() as $commentSection) {
// Assume that the modifiers section starts with either a class or a
// pseudo class
if (preg_match('/^\s*(?:\.|:)/', $commentSection)) {
$modifiersComment = $commentSection;
break;
}
}
return $modifiersComment;
}
}

View File

@ -0,0 +1,41 @@
# This class scans your stylesheets for pseudo classes, then inserts a new CSS
# rule with the same properties, but named 'psuedo-class-{{name}}'.
#
# Supported pseudo classes: hover, disabled, active, visited, focus.
#
# Example:
#
# a:hover{ color:blue; }
# => a.pseudo-class-hover{ color:blue; }
class KssStateGenerator
constructor: ->
pseudos = /(\:hover|\:disabled|\:active|\:visited|\:focus|\:target)/g
try
for stylesheet in document.styleSheets
if stylesheet.href.indexOf(document.domain) >= 0
idxs = []
for rule, idx in stylesheet.cssRules
if (rule.type == CSSRule.STYLE_RULE) && pseudos.test(rule.selectorText)
replaceRule = (matched, stuff) ->
return matched.replace(/\:/g, '.pseudo-class-')
@insertRule(rule.cssText.replace(pseudos, replaceRule))
pseudos.lastIndex = 0
# Takes a given style and attaches it to the current page.
#
# rule - A CSS rule String (ex: ".test{ display:none; }").
#
# Returns nothing.
insertRule: (rule) ->
headEl = document.getElementsByTagName('head')[0]
styleEl = document.createElement('style')
styleEl.type = 'text/css'
if styleEl.styleSheet
styleEl.styleSheet.cssText = rule
else
styleEl.appendChild(document.createTextNode(rule))
headEl.appendChild(styleEl)
new KssStateGenerator

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by PHP Project Wizard (PPW) 1.0.4 on Mon Jan 28 10:08:26 MST 2013 -->
<phpunit bootstrap="test/bootstrap.php"
backupGlobals="false"
backupStaticAttributes="false"
strict="true"
verbose="true">
<testsuites>
<testsuite name="kss-php">
<directory suffix="Test.php">test</directory>
</testsuite>
</testsuites>
<logging>
<log type="coverage-html" target="build/coverage" title="kss-php"
charset="UTF-8" yui="true" highlight="true"
lowUpperBound="35" highLowerBound="70"/>
<log type="coverage-clover" target="build/logs/clover.xml"/>
<log type="junit" target="build/logs/junit.xml" logIncompleteSkipped="false"/>
</logging>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">lib</directory>
</whitelist>
</filter>
</phpunit>