Initial commit of Markdown Resume
This includes several vendor libraries: Assetic, LessPHP, Mustache, SmartyPants, Markdown
This commit is contained in:
commit
f4c52ac4c9
|
@ -0,0 +1,3 @@
|
|||
# Markdown Resume Styles
|
||||
|
||||
Turn a simple Markdown document into an elegant resume.
|
|
@ -0,0 +1,502 @@
|
|||
/*! normalize.css 2012-02-07T12:37 UTC - http://github.com/necolas/normalize.css */
|
||||
|
||||
/* =============================================================================
|
||||
HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Corrects block display not defined in IE6/7/8/9 & FF3
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects inline-block display not defined in IE6/7/8/9 & FF3
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
video {
|
||||
display: inline-block;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevents modern browsers from displaying 'audio' without controls
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling for 'hidden' attribute not present in IE7/8/9, FF3, S4
|
||||
* Known issue: no IE6 support
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Base
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* 1. Corrects text resizing oddly in IE6/7 when body font-size is set using em units
|
||||
* http://clagnut.com/blog/348/#c790
|
||||
* 2. Prevents iOS text size adjust after orientation change, without disabling user zoom
|
||||
* www.456bereastreet.com/archive/201012/controlling_text_size_in_safari_for_ios_without_disabling_user_zoom/
|
||||
*/
|
||||
|
||||
html {
|
||||
font-size: 100%; /* 1 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses font-family inconsistency between 'textarea' and other form elements.
|
||||
*/
|
||||
|
||||
html,
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses margins handled incorrectly in IE6/7
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Links
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses outline displayed oddly in Chrome
|
||||
*/
|
||||
|
||||
a:focus {
|
||||
outline: thin dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Improves readability when focused and also mouse hovered in all browsers
|
||||
* people.opera.com/patrickl/experiments/keyboard/test
|
||||
*/
|
||||
|
||||
a:hover,
|
||||
a:active {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Typography
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses font sizes and margins set differently in IE6/7
|
||||
* Addresses font sizes within 'section' and 'article' in FF4+, Chrome, S5
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
margin: 0.83em 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.17em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1em;
|
||||
margin: 1.33em 0;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 0.83em;
|
||||
margin: 1.67em 0;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.75em;
|
||||
margin: 2.33em 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in IE7/8/9, S5, Chrome
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses style set to 'bolder' in FF3+, S4/5, Chrome
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in S5, Chrome
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses styling not present in IE6/7/8/9
|
||||
*/
|
||||
|
||||
mark {
|
||||
background: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses margins set differently in IE6/7
|
||||
*/
|
||||
|
||||
p,
|
||||
pre {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects font family set oddly in IE6, S4/5, Chrome
|
||||
* en.wikipedia.org/wiki/User:Davidgothberg/Test59
|
||||
*/
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, serif;
|
||||
_font-family: 'courier new', monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/*
|
||||
* Improves readability of pre-formatted text in all browsers
|
||||
*/
|
||||
|
||||
pre {
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses CSS quotes not supported in IE6/7
|
||||
* 2. Addresses quote property not supported in S4
|
||||
*/
|
||||
|
||||
/* 1 */
|
||||
|
||||
q {
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
/* 2 */
|
||||
|
||||
q:before,
|
||||
q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prevents sub and sup affecting line-height in all browsers
|
||||
* gist.github.com/413930
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Lists
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses margins set differently in IE6/7
|
||||
*/
|
||||
|
||||
dl,
|
||||
menu,
|
||||
ol,
|
||||
ul {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0 0 0 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses paddings set differently in IE6/7
|
||||
*/
|
||||
|
||||
menu,
|
||||
ol,
|
||||
ul {
|
||||
padding: 0 0 0 40px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects list images handled incorrectly in IE7
|
||||
*/
|
||||
|
||||
nav ul,
|
||||
nav ol {
|
||||
list-style: none;
|
||||
list-style-image: none;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* 1. Removes border when inside 'a' element in IE6/7/8/9, FF3
|
||||
* 2. Improves image quality when scaled in IE7
|
||||
* code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0; /* 1 */
|
||||
-ms-interpolation-mode: bicubic; /* 2 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Corrects overflow displayed oddly in IE9
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Figures
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Addresses margin not present in IE6/7/8/9, S5, O11
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Forms
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Corrects margin displayed oddly in IE6/7
|
||||
*/
|
||||
|
||||
form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Define consistent border, margin, and padding
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Corrects color not being inherited in IE6/7/8/9
|
||||
* 2. Corrects text not wrapping in FF3
|
||||
* 3. Corrects alignment displayed oddly in IE6/7
|
||||
*/
|
||||
|
||||
legend {
|
||||
border: 0; /* 1 */
|
||||
padding: 0;
|
||||
white-space: normal; /* 2 */
|
||||
*margin-left: -7px; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Corrects font size not being inherited in all browsers
|
||||
* 2. Addresses margins set differently in IE6/7, FF3+, S5, Chrome
|
||||
* 3. Improves appearance and consistency in all browsers
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font-size: 100%; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
vertical-align: baseline; /* 3 */
|
||||
*vertical-align: middle; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Addresses FF3/4 setting line-height on 'input' using !important in the UA stylesheet
|
||||
*/
|
||||
|
||||
button,
|
||||
input {
|
||||
line-height: normal; /* 1 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Improves usability and consistency of cursor style between image-type 'input' and others
|
||||
* 2. Corrects inability to style clickable 'input' types in iOS
|
||||
* 3. Removes inner spacing in IE7 without affecting normal text inputs
|
||||
* Known issue: inner spacing remains in IE6
|
||||
*/
|
||||
|
||||
button,
|
||||
input[type="button"],
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
cursor: pointer; /* 1 */
|
||||
-webkit-appearance: button; /* 2 */
|
||||
*overflow: visible; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-set default cursor for disabled elements
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses box sizing set to content-box in IE8/9
|
||||
* 2. Removes excess padding in IE8/9
|
||||
* 3. Removes excess padding in IE7
|
||||
Known issue: excess padding remains in IE6
|
||||
*/
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
*height: 13px; /* 3 */
|
||||
*width: 13px; /* 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Addresses appearance set to searchfield in S5, Chrome
|
||||
* 2. Addresses box-sizing set to border-box in S5, Chrome (include -moz to future-proof)
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes inner padding and search cancel button in S5, Chrome on OS X
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-decoration,
|
||||
input[type="search"]::-webkit-search-cancel-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes inner padding and border in FF3+
|
||||
* www.sitepen.com/blog/2008/05/14/the-devils-in-the-details-fixing-dojos-toolbar-buttons/
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 1. Removes default vertical scrollbar in IE6/7/8/9
|
||||
* 2. Improves readability and alignment in all browsers
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto; /* 1 */
|
||||
vertical-align: top; /* 2 */
|
||||
}
|
||||
|
||||
|
||||
/* =============================================================================
|
||||
Tables
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Remove most spacing between table cells
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
@import url("css/normalize.css");
|
||||
@import url("css/style.css");
|
|
@ -0,0 +1 @@
|
|||
body { font-family: Georgia; color: #444; }
|
|
@ -0,0 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
{{#reload}}
|
||||
<meta http-equiv="refresh" content="2">
|
||||
{{/reload}}
|
||||
|
||||
<title>{{title}}</title>
|
||||
<style type="text/css">
|
||||
{{{style}}}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
{{{resume}}}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
define('APPLICATION_BASE_PATH', realpath(__DIR__ . '/..'));
|
||||
|
||||
spl_autoload_register(function ($className) {
|
||||
$namespaces = explode('\\', $className);
|
||||
if (count($namespaces) > 1) {
|
||||
$classPath
|
||||
= APPLICATION_BASE_PATH
|
||||
. '/vendor/'
|
||||
. implode('/', $namespaces)
|
||||
. '.php';
|
||||
if (file_exists($classPath)) {
|
||||
require_once($classPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
include_once APPLICATION_BASE_PATH . '/vendor/Mustache/Mustache.php';
|
||||
include_once APPLICATION_BASE_PATH . '/vendor/smartypants/smartypants.php';
|
||||
include_once APPLICATION_BASE_PATH . '/vendor/markdown-extra/markdown.php';
|
||||
include_once APPLICATION_BASE_PATH . '/vendor/lessphp/lessc.inc.php';
|
||||
|
||||
use Assetic\Asset\AssetCollection;
|
||||
use Assetic\Asset\FileAsset;
|
||||
use Assetic\Asset\GlobAsset;
|
||||
use Assetic\Filter;
|
||||
|
||||
$shortopts = "";
|
||||
$shortopts .= "r";
|
||||
|
||||
$longopts = array(
|
||||
"refresh"
|
||||
);
|
||||
$options = getopt($shortopts, $longopts);
|
||||
|
||||
$refresh_dev = isset($options['r']) || isset($options['refresh']);
|
||||
|
||||
|
||||
$css = new AssetCollection(
|
||||
array(
|
||||
//new FileAsset('/path/to/src/styles.less', array(new LessFilter())),
|
||||
new GlobAsset(APPLICATION_BASE_PATH . '/assets/css/*.css')
|
||||
),
|
||||
array(
|
||||
new Filter\LessphpFilter(),
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
|
||||
// the code is merged when the asset is dumped
|
||||
$style = $css->dump();
|
||||
|
||||
|
||||
$template = file_get_contents(APPLICATION_BASE_PATH . '/assets/templates/default.html');
|
||||
$resume = file_get_contents(APPLICATION_BASE_PATH . '/resume/resume.md');
|
||||
|
||||
|
||||
$resume = Markdown($resume);
|
||||
$resume = SmartyPants($resume);
|
||||
|
||||
$m = new Mustache;
|
||||
$rendered = $m->render(
|
||||
$template,
|
||||
array(
|
||||
'title' => 'TITLE',
|
||||
'style' => $style,
|
||||
'resume' => $resume,
|
||||
'reload' => $refresh_dev
|
||||
)
|
||||
);
|
||||
|
||||
file_put_contents(
|
||||
APPLICATION_BASE_PATH . '/resume/resume.html',
|
||||
$rendered
|
||||
);
|
||||
|
||||
|
||||
/* End of file build.php */
|
|
@ -0,0 +1,171 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="refresh" content="2">
|
||||
|
||||
<title>TITLE</title>
|
||||
<style type="text/css">
|
||||
article, aside, details, figcaption, figure, footer, header, hgroup, nav, section, summary { display:block; }
|
||||
audio, canvas, video {
|
||||
display:inline-block;
|
||||
*display:inline;
|
||||
*zoom:1;
|
||||
}
|
||||
audio:not([controls]) { display:none; }
|
||||
[hidden] { display:none; }
|
||||
html {
|
||||
font-size:100%;
|
||||
-webkit-text-size-adjust:100%;
|
||||
-ms-text-size-adjust:100%;
|
||||
}
|
||||
html, button, input, select, textarea { font-family:sans-serif; }
|
||||
body { margin:0; }
|
||||
a:focus { outline:thin dotted; }
|
||||
a:hover, a:active { outline:0; }
|
||||
h1 {
|
||||
font-size:2em;
|
||||
margin:0.67em 0;
|
||||
}
|
||||
h2 {
|
||||
font-size:1.5em;
|
||||
margin:0.83em 0;
|
||||
}
|
||||
h3 {
|
||||
font-size:1.17em;
|
||||
margin:1em 0;
|
||||
}
|
||||
h4 {
|
||||
font-size:1em;
|
||||
margin:1.33em 0;
|
||||
}
|
||||
h5 {
|
||||
font-size:0.83em;
|
||||
margin:1.67em 0;
|
||||
}
|
||||
h6 {
|
||||
font-size:0.75em;
|
||||
margin:2.33em 0;
|
||||
}
|
||||
abbr[title] { border-bottom:1px dotted; }
|
||||
b, strong { font-weight:bold; }
|
||||
blockquote { margin:1em 40px; }
|
||||
dfn { font-style:italic; }
|
||||
mark {
|
||||
background:#ffff00;
|
||||
color:#000000;
|
||||
}
|
||||
p, pre { margin:1em 0; }
|
||||
pre, code, kbd, samp {
|
||||
font-family:monospace, serif;
|
||||
_font-family:'courier new', monospace;
|
||||
font-size:1em;
|
||||
}
|
||||
pre {
|
||||
white-space:pre;
|
||||
white-space:pre-wrap;
|
||||
word-wrap:break-word;
|
||||
}
|
||||
q { quotes:none; }
|
||||
q:before, q:after {
|
||||
content:'';
|
||||
content:none;
|
||||
}
|
||||
small { font-size:75%; }
|
||||
sub, sup {
|
||||
font-size:75%;
|
||||
line-height:0;
|
||||
position:relative;
|
||||
vertical-align:baseline;
|
||||
}
|
||||
sup { top:-0.5em; }
|
||||
sub { bottom:-0.25em; }
|
||||
dl, menu, ol, ul { margin:1em 0; }
|
||||
dd { margin:0 0 0 40px; }
|
||||
menu, ol, ul { padding:0 0 0 40px; }
|
||||
nav ul, nav ol {
|
||||
list-style:none;
|
||||
list-style-image:none;
|
||||
}
|
||||
img {
|
||||
border:0;
|
||||
-ms-interpolation-mode:bicubic;
|
||||
}
|
||||
svg:not(:root) { overflow:hidden; }
|
||||
figure { margin:0; }
|
||||
form { margin:0; }
|
||||
fieldset {
|
||||
border:1px solid #c0c0c0;
|
||||
margin:0 2px;
|
||||
padding:0.35em 0.625em 0.75em;
|
||||
}
|
||||
legend {
|
||||
border:0;
|
||||
padding:0;
|
||||
white-space:normal;
|
||||
*margin-left:-7px;
|
||||
}
|
||||
button, input, select, textarea {
|
||||
font-size:100%;
|
||||
margin:0;
|
||||
vertical-align:baseline;
|
||||
*vertical-align:middle;
|
||||
}
|
||||
button, input { line-height:normal; }
|
||||
button, input[type="button"], input[type="reset"], input[type="submit"] {
|
||||
cursor:pointer;
|
||||
-webkit-appearance:button;
|
||||
*overflow:visible;
|
||||
}
|
||||
button[disabled], input[disabled] { cursor:default; }
|
||||
input[type="checkbox"], input[type="radio"] {
|
||||
box-sizing:border-box;
|
||||
padding:0;
|
||||
*height:13px;
|
||||
*width:13px;
|
||||
}
|
||||
input[type="search"] {
|
||||
-webkit-appearance:textfield;
|
||||
-moz-box-sizing:content-box;
|
||||
-webkit-box-sizing:content-box;
|
||||
box-sizing:content-box;
|
||||
}
|
||||
input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance:none; }
|
||||
button::-moz-focus-inner, input::-moz-focus-inner {
|
||||
border:0;
|
||||
padding:0;
|
||||
}
|
||||
textarea {
|
||||
overflow:auto;
|
||||
vertical-align:top;
|
||||
}
|
||||
table {
|
||||
border-collapse:collapse;
|
||||
border-spacing:0;
|
||||
}
|
||||
|
||||
@import url("css/normalize.css");
|
||||
@import url("css/style.css");
|
||||
|
||||
body {
|
||||
font-family:Georgia;
|
||||
color:#444444;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<h1>Craig Davis</h1>
|
||||
|
||||
<h2>Senior PHP Developer</h2>
|
||||
|
||||
<p>craig@there4development.com
|
||||
(704) 724-3235</p>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,5 @@
|
|||
# Craig Davis
|
||||
## Senior PHP Developer
|
||||
|
||||
craig@there4development.com
|
||||
(704) 724-3235
|
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Asset;
|
||||
|
||||
use Assetic\Cache\CacheInterface;
|
||||
use Assetic\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* Caches an asset to avoid the cost of loading and dumping.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class AssetCache implements AssetInterface
|
||||
{
|
||||
private $asset;
|
||||
private $cache;
|
||||
|
||||
public function __construct(AssetInterface $asset, CacheInterface $cache)
|
||||
{
|
||||
$this->asset = $asset;
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
public function ensureFilter(FilterInterface $filter)
|
||||
{
|
||||
$this->asset->ensureFilter($filter);
|
||||
}
|
||||
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->asset->getFilters();
|
||||
}
|
||||
|
||||
public function clearFilters()
|
||||
{
|
||||
$this->asset->clearFilters();
|
||||
}
|
||||
|
||||
public function load(FilterInterface $additionalFilter = null)
|
||||
{
|
||||
$cacheKey = self::getCacheKey($this->asset, $additionalFilter, 'load');
|
||||
if ($this->cache->has($cacheKey)) {
|
||||
$this->asset->setContent($this->cache->get($cacheKey));
|
||||
return;
|
||||
}
|
||||
|
||||
$this->asset->load($additionalFilter);
|
||||
$this->cache->set($cacheKey, $this->asset->getContent());
|
||||
}
|
||||
|
||||
public function dump(FilterInterface $additionalFilter = null)
|
||||
{
|
||||
$cacheKey = self::getCacheKey($this->asset, $additionalFilter, 'dump');
|
||||
if ($this->cache->has($cacheKey)) {
|
||||
return $this->cache->get($cacheKey);
|
||||
}
|
||||
|
||||
$content = $this->asset->dump($additionalFilter);
|
||||
$this->cache->set($cacheKey, $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function getContent()
|
||||
{
|
||||
return $this->asset->getContent();
|
||||
}
|
||||
|
||||
public function setContent($content)
|
||||
{
|
||||
$this->asset->setContent($content);
|
||||
}
|
||||
|
||||
public function getSourceRoot()
|
||||
{
|
||||
return $this->asset->getSourceRoot();
|
||||
}
|
||||
|
||||
public function getSourcePath()
|
||||
{
|
||||
return $this->asset->getSourcePath();
|
||||
}
|
||||
|
||||
public function getTargetPath()
|
||||
{
|
||||
return $this->asset->getTargetPath();
|
||||
}
|
||||
|
||||
public function setTargetPath($targetPath)
|
||||
{
|
||||
$this->asset->setTargetPath($targetPath);
|
||||
}
|
||||
|
||||
public function getLastModified()
|
||||
{
|
||||
return $this->asset->getLastModified();
|
||||
}
|
||||
|
||||
public function getVars()
|
||||
{
|
||||
return $this->asset->getVars();
|
||||
}
|
||||
|
||||
public function setValues(array $values)
|
||||
{
|
||||
$this->asset->setValues($values);
|
||||
}
|
||||
|
||||
public function getValues()
|
||||
{
|
||||
return $this->asset->getValues();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cache key for the current asset.
|
||||
*
|
||||
* The key is composed of everything but an asset's content:
|
||||
*
|
||||
* * source root
|
||||
* * source path
|
||||
* * target url
|
||||
* * last modified
|
||||
* * filters
|
||||
*
|
||||
* @param AssetInterface $asset The asset
|
||||
* @param FilterInterface $additionalFilter Any additional filter being applied
|
||||
* @param string $salt Salt for the key
|
||||
*
|
||||
* @return string A key for identifying the current asset
|
||||
*/
|
||||
static private function getCacheKey(AssetInterface $asset, FilterInterface $additionalFilter = null, $salt = '')
|
||||
{
|
||||
if ($additionalFilter) {
|
||||
$asset = clone $asset;
|
||||
$asset->ensureFilter($additionalFilter);
|
||||
}
|
||||
|
||||
$cacheKey = $asset->getSourceRoot();
|
||||
$cacheKey .= $asset->getSourcePath();
|
||||
$cacheKey .= $asset->getTargetPath();
|
||||
$cacheKey .= $asset->getLastModified();
|
||||
|
||||
foreach ($asset->getFilters() as $filter) {
|
||||
$cacheKey .= serialize($filter);
|
||||
}
|
||||
|
||||
if ($values = $asset->getValues()) {
|
||||
asort($values);
|
||||
$cacheKey .= serialize($values);
|
||||
}
|
||||
|
||||
return md5($cacheKey.$salt);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Asset;
|
||||
|
||||
use Assetic\Asset\Iterator\AssetCollectionFilterIterator;
|
||||
use Assetic\Asset\Iterator\AssetCollectionIterator;
|
||||
use Assetic\Filter\FilterCollection;
|
||||
use Assetic\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* A collection of assets.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class AssetCollection implements \IteratorAggregate, AssetCollectionInterface
|
||||
{
|
||||
private $assets;
|
||||
private $filters;
|
||||
private $sourceRoot;
|
||||
private $targetPath;
|
||||
private $content;
|
||||
private $clones;
|
||||
private $vars;
|
||||
private $values;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $assets Assets for the current collection
|
||||
* @param array $filters Filters for the current collection
|
||||
* @param string $sourceRoot The root directory
|
||||
*/
|
||||
public function __construct($assets = array(), $filters = array(), $sourceRoot = null, array $vars = array())
|
||||
{
|
||||
$this->assets = array();
|
||||
foreach ($assets as $asset) {
|
||||
$this->add($asset);
|
||||
}
|
||||
|
||||
$this->filters = new FilterCollection($filters);
|
||||
$this->sourceRoot = $sourceRoot;
|
||||
$this->clones = new \SplObjectStorage();
|
||||
$this->vars = $vars;
|
||||
$this->values = array();
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
return $this->assets;
|
||||
}
|
||||
|
||||
public function add(AssetInterface $asset)
|
||||
{
|
||||
$this->assets[] = $asset;
|
||||
}
|
||||
|
||||
public function removeLeaf(AssetInterface $needle, $graceful = false)
|
||||
{
|
||||
foreach ($this->assets as $i => $asset) {
|
||||
$clone = isset($this->clones[$asset]) ? $this->clones[$asset] : null;
|
||||
if (in_array($needle, array($asset, $clone), true)) {
|
||||
unset($this->clones[$asset], $this->assets[$i]);
|
||||
return true;
|
||||
} elseif ($asset instanceof AssetCollectionInterface && $asset->removeLeaf($needle, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($graceful) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Leaf not found.');
|
||||
}
|
||||
|
||||
public function replaceLeaf(AssetInterface $needle, AssetInterface $replacement, $graceful = false)
|
||||
{
|
||||
foreach ($this->assets as $i => $asset) {
|
||||
$clone = isset($this->clones[$asset]) ? $this->clones[$asset] : null;
|
||||
if (in_array($needle, array($asset, $clone), true)) {
|
||||
unset($this->clones[$asset]);
|
||||
$this->assets[$i] = $replacement;
|
||||
return true;
|
||||
} elseif ($asset instanceof AssetCollectionInterface && $asset->replaceLeaf($needle, $replacement, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($graceful) {
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Leaf not found.');
|
||||
}
|
||||
|
||||
public function ensureFilter(FilterInterface $filter)
|
||||
{
|
||||
$this->filters->ensure($filter);
|
||||
}
|
||||
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->filters->all();
|
||||
}
|
||||
|
||||
public function clearFilters()
|
||||
{
|
||||
$this->filters->clear();
|
||||
}
|
||||
|
||||
public function load(FilterInterface $additionalFilter = null)
|
||||
{
|
||||
// loop through leaves and load each asset
|
||||
$parts = array();
|
||||
foreach ($this as $asset) {
|
||||
$asset->load($additionalFilter);
|
||||
$parts[] = $asset->getContent();
|
||||
}
|
||||
|
||||
$this->content = implode("\n", $parts);
|
||||
}
|
||||
|
||||
public function dump(FilterInterface $additionalFilter = null)
|
||||
{
|
||||
// loop through leaves and dump each asset
|
||||
$parts = array();
|
||||
foreach ($this as $asset) {
|
||||
$parts[] = $asset->dump($additionalFilter);
|
||||
}
|
||||
|
||||
return implode("\n", $parts);
|
||||
}
|
||||
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function setContent($content)
|
||||
{
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
public function getSourceRoot()
|
||||
{
|
||||
return $this->sourceRoot;
|
||||
}
|
||||
|
||||
public function getSourcePath()
|
||||
{
|
||||
}
|
||||
|
||||
public function getTargetPath()
|
||||
{
|
||||
return $this->targetPath;
|
||||
}
|
||||
|
||||
public function setTargetPath($targetPath)
|
||||
{
|
||||
$this->targetPath = $targetPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the highest last-modified value of all assets in the current collection.
|
||||
*
|
||||
* @return integer|null A UNIX timestamp
|
||||
*/
|
||||
public function getLastModified()
|
||||
{
|
||||
if (!count($this->assets)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mapper = function (AssetInterface $asset)
|
||||
{
|
||||
return $asset->getLastModified();
|
||||
};
|
||||
|
||||
return max(array_map($mapper, $this->assets));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator for looping recursively over unique leaves.
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new \RecursiveIteratorIterator(new AssetCollectionFilterIterator(new AssetCollectionIterator($this, $this->clones)));
|
||||
}
|
||||
|
||||
public function getVars()
|
||||
{
|
||||
return $this->vars;
|
||||
}
|
||||
|
||||
public function setValues(array $values)
|
||||
{
|
||||
$this->values = $values;
|
||||
|
||||
foreach ($this as $asset) {
|
||||
$asset->setValues(array_intersect_key($values, array_flip($asset->getVars())));
|
||||
}
|
||||
}
|
||||
|
||||
public function getValues()
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Asset;
|
||||
|
||||
/**
|
||||
* An asset collection.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
interface AssetCollectionInterface extends AssetInterface, \Traversable
|
||||
{
|
||||
/**
|
||||
* Returns all child assets.
|
||||
*
|
||||
* @return array An array of AssetInterface objects
|
||||
*/
|
||||
function all();
|
||||
|
||||
/**
|
||||
* Adds an asset to the current collection.
|
||||
*
|
||||
* @param AssetInterface $asset An asset
|
||||
*/
|
||||
function add(AssetInterface $asset);
|
||||
|
||||
/**
|
||||
* Removes a leaf.
|
||||
*
|
||||
* @param AssetInterface $needle The leaf to remove
|
||||
*
|
||||
* @throws InvalidArgumentException If the asset cannot be found
|
||||
*/
|
||||
function removeLeaf(AssetInterface $leaf);
|
||||
|
||||
/**
|
||||
* Replaces an existing leaf with a new one.
|
||||
*
|
||||
* @param AssetInterface $needle The current asset to replace
|
||||
* @param AssetInterface $replacement The new asset
|
||||
*
|
||||
* @throws InvalidArgumentException If the asset cannot be found
|
||||
*/
|
||||
function replaceLeaf(AssetInterface $needle, AssetInterface $replacement);
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Asset;
|
||||
|
||||
use Assetic\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* An asset has a mutable URL and content and can be loaded and dumped.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
interface AssetInterface
|
||||
{
|
||||
/**
|
||||
* Ensures the current asset includes the supplied filter.
|
||||
*
|
||||
* @param FilterInterface $filter A filter
|
||||
*/
|
||||
function ensureFilter(FilterInterface $filter);
|
||||
|
||||
/**
|
||||
* Returns an array of filters currently applied.
|
||||
*
|
||||
* @return array An array of filters
|
||||
*/
|
||||
function getFilters();
|
||||
|
||||
/**
|
||||
* Clears all filters from the current asset.
|
||||
*/
|
||||
function clearFilters();
|
||||
|
||||
/**
|
||||
* Loads the asset into memory and applies load filters.
|
||||
*
|
||||
* You may provide an additional filter to apply during load.
|
||||
*
|
||||
* @param FilterInterface $additionalFilter An additional filter
|
||||
*/
|
||||
function load(FilterInterface $additionalFilter = null);
|
||||
|
||||
/**
|
||||
* Applies dump filters and returns the asset as a string.
|
||||
*
|
||||
* You may provide an additional filter to apply during dump.
|
||||
*
|
||||
* Dumping an asset should not change its state.
|
||||
*
|
||||
* If the current asset has not been loaded yet, it should be
|
||||
* automatically loaded at this time.
|
||||
*
|
||||
* @param FilterInterface $additionalFilter An additional filter
|
||||
*
|
||||
* @return string The filtered content of the current asset
|
||||
*/
|
||||
function dump(FilterInterface $additionalFilter = null);
|
||||
|
||||
/**
|
||||
* Returns the loaded content of the current asset.
|
||||
*
|
||||
* @return string The content
|
||||
*/
|
||||
function getContent();
|
||||
|
||||
/**
|
||||
* Sets the content of the current asset.
|
||||
*
|
||||
* Filters can use this method to change the content of the asset.
|
||||
*
|
||||
* @param string $content The asset content
|
||||
*/
|
||||
function setContent($content);
|
||||
|
||||
/**
|
||||
* Returns an absolute path or URL to the source asset's root directory.
|
||||
*
|
||||
* This value should be an absolute path to a directory in the filesystem,
|
||||
* an absolute URL with no path, or null.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* * '/path/to/web'
|
||||
* * 'http://example.com'
|
||||
* * null
|
||||
*
|
||||
* @return string|null The asset's root
|
||||
*/
|
||||
function getSourceRoot();
|
||||
|
||||
/**
|
||||
* Returns the relative path for the source asset.
|
||||
*
|
||||
* This value can be combined with the asset's source root (if both are
|
||||
* non-null) to get something compatible with file_get_contents().
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* * 'js/main.js'
|
||||
* * 'main.js'
|
||||
* * null
|
||||
*
|
||||
* @return string|null The source asset path
|
||||
*/
|
||||
function getSourcePath();
|
||||
|
||||
/**
|
||||
* Returns the URL for the current asset.
|
||||
*
|
||||
* @return string|null A web URL where the asset will be dumped
|
||||
*/
|
||||
function getTargetPath();
|
||||
|
||||
/**
|
||||
* Sets the URL for the current asset.
|
||||
*
|
||||
* @param string $targetPath A web URL where the asset will be dumped
|
||||
*/
|
||||
function setTargetPath($targetPath);
|
||||
|
||||
/**
|
||||
* Returns the time the current asset was last modified.
|
||||
*
|
||||
* @return integer|null A UNIX timestamp
|
||||
*/
|
||||
function getLastModified();
|
||||
|
||||
/**
|
||||
* Returns an array of variable names for this asset.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function getVars();
|
||||
|
||||
/**
|
||||
* Sets the values for the asset's variables.
|
||||
*
|
||||
* @param array $values
|
||||
*/
|
||||
function setValues(array $values);
|
||||
|
||||
/**
|
||||
* Returns the current values for this asset.
|
||||
*
|
||||
* @return array an array of strings
|
||||
*/
|
||||
function getValues();
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Asset;
|
||||
|
||||
use Assetic\AssetManager;
|
||||
use Assetic\Filter\FilterCollection;
|
||||
use Assetic\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* A reference to an asset in the asset manager.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class AssetReference implements AssetInterface
|
||||
{
|
||||
private $am;
|
||||
private $name;
|
||||
private $filters = array();
|
||||
|
||||
public function __construct(AssetManager $am, $name)
|
||||
{
|
||||
$this->am = $am;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function ensureFilter(FilterInterface $filter)
|
||||
{
|
||||
$this->filters[] = $filter;
|
||||
}
|
||||
|
||||
public function getFilters()
|
||||
{
|
||||
$this->flushFilters();
|
||||
|
||||
return $this->callAsset(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function clearFilters()
|
||||
{
|
||||
$this->filters = array();
|
||||
$this->callAsset(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function load(FilterInterface $additionalFilter = null)
|
||||
{
|
||||
$this->flushFilters();
|
||||
|
||||
return $this->callAsset(__FUNCTION__, array($additionalFilter));
|
||||
}
|
||||
|
||||
public function dump(FilterInterface $additionalFilter = null)
|
||||
{
|
||||
$this->flushFilters();
|
||||
|
||||
return $this->callAsset(__FUNCTION__, array($additionalFilter));
|
||||
}
|
||||
|
||||
public function getContent()
|
||||
{
|
||||
return $this->callAsset(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function setContent($content)
|
||||
{
|
||||
$this->callAsset(__FUNCTION__, array($content));
|
||||
}
|
||||
|
||||
public function getSourceRoot()
|
||||
{
|
||||
return $this->callAsset(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getSourcePath()
|
||||
{
|
||||
return $this->callAsset(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getTargetPath()
|
||||
{
|
||||
return $this->callAsset(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function setTargetPath($targetPath)
|
||||
{
|
||||
$this->callAsset(__FUNCTION__, array($targetPath));
|
||||
}
|
||||
|
||||
public function getLastModified()
|
||||
{
|
||||
return $this->callAsset(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getVars()
|
||||
{
|
||||
return $this->callAsset(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function getValues()
|
||||
{
|
||||
return $this->callAsset(__FUNCTION__);
|
||||
}
|
||||
|
||||
public function setValues(array $values)
|
||||
{
|
||||
$this->callAsset(__FUNCTION__, array($values));
|
||||
}
|
||||
|
||||
// private
|
||||
|
||||
private function callAsset($method, $arguments = array())
|
||||
{
|
||||
$asset = $this->am->get($this->name);
|
||||
|
||||
return call_user_func_array(array($asset, $method), $arguments);
|
||||
}
|
||||
|
||||
private function flushFilters()
|
||||
{
|
||||
$asset = $this->am->get($this->name);
|
||||
|
||||
while ($filter = array_shift($this->filters)) {
|
||||
$asset->ensureFilter($filter);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Asset;
|
||||
|
||||
use Assetic\Filter\FilterCollection;
|
||||
use Assetic\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* A base abstract asset.
|
||||
*
|
||||
* The methods load() and getLastModified() are left undefined, although a
|
||||
* reusable doLoad() method is available to child classes.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
abstract class BaseAsset implements AssetInterface
|
||||
{
|
||||
private $filters;
|
||||
private $sourceRoot;
|
||||
private $sourcePath;
|
||||
private $targetPath;
|
||||
private $content;
|
||||
private $loaded;
|
||||
private $vars;
|
||||
private $values;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $filters Filters for the asset
|
||||
*/
|
||||
public function __construct($filters = array(), $sourceRoot = null, $sourcePath = null, array $vars = array())
|
||||
{
|
||||
$this->filters = new FilterCollection($filters);
|
||||
$this->sourceRoot = $sourceRoot;
|
||||
$this->sourcePath = $sourcePath;
|
||||
$this->vars = $vars;
|
||||
$this->values = array();
|
||||
$this->loaded = false;
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->filters = clone $this->filters;
|
||||
}
|
||||
|
||||
public function ensureFilter(FilterInterface $filter)
|
||||
{
|
||||
$this->filters->ensure($filter);
|
||||
}
|
||||
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->filters->all();
|
||||
}
|
||||
|
||||
public function clearFilters()
|
||||
{
|
||||
$this->filters->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates asset loading logic.
|
||||
*
|
||||
* @param string $content The asset content
|
||||
* @param FilterInterface $additionalFilter An additional filter
|
||||
*/
|
||||
protected function doLoad($content, FilterInterface $additionalFilter = null)
|
||||
{
|
||||
$filter = clone $this->filters;
|
||||
if ($additionalFilter) {
|
||||
$filter->ensure($additionalFilter);
|
||||
}
|
||||
|
||||
$asset = clone $this;
|
||||
$asset->setContent($content);
|
||||
|
||||
$filter->filterLoad($asset);
|
||||
$this->content = $asset->getContent();
|
||||
|
||||
$this->loaded = true;
|
||||
}
|
||||
|
||||
public function dump(FilterInterface $additionalFilter = null)
|
||||
{
|
||||
if (!$this->loaded) {
|
||||
$this->load();
|
||||
}
|
||||
|
||||
$filter = clone $this->filters;
|
||||
if ($additionalFilter) {
|
||||
$filter->ensure($additionalFilter);
|
||||
}
|
||||
|
||||
$asset = clone $this;
|
||||
$filter->filterDump($asset);
|
||||
|
||||
return $asset->getContent();
|
||||
}
|
||||
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
public function setContent($content)
|
||||
{
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
public function getSourceRoot()
|
||||
{
|
||||
return $this->sourceRoot;
|
||||
}
|
||||
|
||||
public function getSourcePath()
|
||||
{
|
||||
return $this->sourcePath;
|
||||
}
|
||||
|
||||
public function getTargetPath()
|
||||
{
|
||||
return $this->targetPath;
|
||||
}
|
||||
|
||||
public function setTargetPath($targetPath)
|
||||
{
|
||||
if ($this->vars) {
|
||||
foreach ($this->vars as $var) {
|
||||
if (false === strpos($targetPath, $var)) {
|
||||
throw new \RuntimeException(sprintf('The asset target path "%s" must contain the variable "{%s}".', $targetPath, $var));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->targetPath = $targetPath;
|
||||
}
|
||||
|
||||
public function getVars()
|
||||
{
|
||||
return $this->vars;
|
||||
}
|
||||
|
||||
public function setValues(array $values)
|
||||
{
|
||||
foreach ($values as $var => $v) {
|
||||
if (!in_array($var, $this->vars, true)) {
|
||||
throw new \InvalidArgumentException(sprintf('The asset with source path "%s" has no variable named "%s".', $this->sourcePath, $var));
|
||||
}
|
||||
}
|
||||
|
||||
$this->values = $values;
|
||||
$this->loaded = false;
|
||||
}
|
||||
|
||||
public function getValues()
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Asset;
|
||||
|
||||
use Assetic\Util\PathUtils;
|
||||
use Assetic\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* Represents an asset loaded from a file.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class FileAsset extends BaseAsset
|
||||
{
|
||||
private $source;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $source An absolute path
|
||||
* @param array $filters An array of filters
|
||||
* @param string $sourceRoot The source asset root directory
|
||||
* @param string $sourcePath The source asset path
|
||||
*
|
||||
* @throws InvalidArgumentException If the supplied root doesn't match the source when guessing the path
|
||||
*/
|
||||
public function __construct($source, $filters = array(), $sourceRoot = null, $sourcePath = null, array $vars = array())
|
||||
{
|
||||
if (null === $sourceRoot) {
|
||||
$sourceRoot = dirname($source);
|
||||
if (null === $sourcePath) {
|
||||
$sourcePath = basename($source);
|
||||
}
|
||||
} elseif (null === $sourcePath) {
|
||||
if (0 !== strpos($source, $sourceRoot)) {
|
||||
throw new \InvalidArgumentException(sprintf('The source "%s" is not in the root directory "%s"', $source, $sourceRoot));
|
||||
}
|
||||
|
||||
$sourcePath = substr($source, strlen($sourceRoot) + 1);
|
||||
}
|
||||
|
||||
$this->source = $source;
|
||||
|
||||
parent::__construct($filters, $sourceRoot, $sourcePath, $vars);
|
||||
}
|
||||
|
||||
public function load(FilterInterface $additionalFilter = null)
|
||||
{
|
||||
$source = PathUtils::resolvePath($this->source, $this->getVars(),
|
||||
$this->getValues());
|
||||
|
||||
if (!is_file($source)) {
|
||||
throw new \RuntimeException(sprintf('The source file "%s" does not exist.', $source));
|
||||
}
|
||||
|
||||
$this->doLoad(file_get_contents($source), $additionalFilter);
|
||||
}
|
||||
|
||||
public function getLastModified()
|
||||
{
|
||||
$source = PathUtils::resolvePath($this->source, $this->getVars(),
|
||||
$this->getValues());
|
||||
|
||||
if (!is_file($source)) {
|
||||
throw new \RuntimeException(sprintf('The source file "%s" does not exist.', $source));
|
||||
}
|
||||
return filemtime($source);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Asset;
|
||||
|
||||
use Assetic\Util\PathUtils;
|
||||
|
||||
use Assetic\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* A collection of assets loaded by glob.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class GlobAsset extends AssetCollection
|
||||
{
|
||||
private $globs;
|
||||
private $initialized;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string|array $globs A single glob path or array of paths
|
||||
* @param array $filters An array of filters
|
||||
* @param string $root The root directory
|
||||
*/
|
||||
public function __construct($globs, $filters = array(), $root = null, array $vars = array())
|
||||
{
|
||||
$this->globs = (array) $globs;
|
||||
$this->initialized = false;
|
||||
|
||||
parent::__construct(array(), $filters, $root, $vars);
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
if (!$this->initialized) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return parent::all();
|
||||
}
|
||||
|
||||
public function load(FilterInterface $additionalFilter = null)
|
||||
{
|
||||
if (!$this->initialized) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
parent::load($additionalFilter);
|
||||
}
|
||||
|
||||
public function dump(FilterInterface $additionalFilter = null)
|
||||
{
|
||||
if (!$this->initialized) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return parent::dump($additionalFilter);
|
||||
}
|
||||
|
||||
public function getLastModified()
|
||||
{
|
||||
if (!$this->initialized) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return parent::getLastModified();
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
if (!$this->initialized) {
|
||||
$this->initialize();
|
||||
}
|
||||
|
||||
return parent::getIterator();
|
||||
}
|
||||
|
||||
public function setValues(array $values)
|
||||
{
|
||||
parent::setValues($values);
|
||||
$this->initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the collection based on the glob(s) passed in.
|
||||
*/
|
||||
private function initialize()
|
||||
{
|
||||
foreach ($this->globs as $glob) {
|
||||
$glob = PathUtils::resolvePath($glob, $this->getVars(), $this->getValues());
|
||||
|
||||
if (false !== $paths = glob($glob)) {
|
||||
foreach ($paths as $path) {
|
||||
$this->add(new FileAsset($path, array(), $this->getSourceRoot()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->initialized = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Asset;
|
||||
|
||||
use Assetic\Util\PathUtils;
|
||||
|
||||
use Assetic\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* Represents an asset loaded via an HTTP request.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class HttpAsset extends BaseAsset
|
||||
{
|
||||
private $sourceUrl;
|
||||
private $ignoreErrors;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $sourceUrl The source URL
|
||||
* @param array $filters An array of filters
|
||||
*
|
||||
* @throws InvalidArgumentException If the first argument is not an URL
|
||||
*/
|
||||
public function __construct($sourceUrl, $filters = array(), $ignoreErrors = false, array $vars = array())
|
||||
{
|
||||
if (0 === strpos($sourceUrl, '//')) {
|
||||
$sourceUrl = 'http:'.$sourceUrl;
|
||||
} elseif (false === strpos($sourceUrl, '://')) {
|
||||
throw new \InvalidArgumentException(sprintf('"%s" is not a valid URL.', $sourceUrl));
|
||||
}
|
||||
|
||||
$this->sourceUrl = $sourceUrl;
|
||||
$this->ignoreErrors = $ignoreErrors;
|
||||
|
||||
list($scheme, $url) = explode('://', $sourceUrl, 2);
|
||||
list($host, $path) = explode('/', $url, 2);
|
||||
|
||||
parent::__construct($filters, $scheme.'://'.$host, $path, $vars);
|
||||
}
|
||||
|
||||
public function load(FilterInterface $additionalFilter = null)
|
||||
{
|
||||
if (false === $content = @file_get_contents(PathUtils::resolvePath(
|
||||
$this->sourceUrl, $this->getVars(), $this->getValues()))) {
|
||||
if ($this->ignoreErrors) {
|
||||
return;
|
||||
} else {
|
||||
throw new \RuntimeException(sprintf('Unable to load asset from URL "%s"', $this->sourceUrl));
|
||||
}
|
||||
}
|
||||
|
||||
$this->doLoad($content, $additionalFilter);
|
||||
}
|
||||
|
||||
public function getLastModified()
|
||||
{
|
||||
if (false !== @file_get_contents($this->sourceUrl, false, stream_context_create(array('http' => array('method' => 'HEAD'))))) {
|
||||
foreach ($http_response_header as $header) {
|
||||
if (0 === stripos($header, 'Last-Modified: ')) {
|
||||
list(, $mtime) = explode(':', $header, 2);
|
||||
|
||||
return strtotime(trim($mtime));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Asset\Iterator;
|
||||
|
||||
/**
|
||||
* Asset collection filter iterator.
|
||||
*
|
||||
* The filter iterator is responsible for de-duplication of leaf assets based
|
||||
* on both strict equality and source URL.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class AssetCollectionFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
private $visited;
|
||||
private $sources;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param AssetCollectionIterator $iterator The inner iterator
|
||||
* @param array $visited An array of visited asset objects
|
||||
* @param array $sources An array of visited source strings
|
||||
*/
|
||||
public function __construct(AssetCollectionIterator $iterator, array $visited = array(), array $sources = array())
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
|
||||
$this->visited = $visited;
|
||||
$this->sources = $sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the current asset is a duplicate.
|
||||
*
|
||||
* De-duplication is performed based on either strict equality or by
|
||||
* matching sources.
|
||||
*
|
||||
* @return Boolean Returns true if we have not seen this asset yet
|
||||
*/
|
||||
public function accept()
|
||||
{
|
||||
$asset = $this->getInnerIterator()->current(true);
|
||||
$duplicate = false;
|
||||
|
||||
// check strict equality
|
||||
if (in_array($asset, $this->visited, true)) {
|
||||
$duplicate = true;
|
||||
} else {
|
||||
$this->visited[] = $asset;
|
||||
}
|
||||
|
||||
// check source
|
||||
$sourceRoot = $asset->getSourceRoot();
|
||||
$sourcePath = $asset->getSourcePath();
|
||||
if ($sourceRoot && $sourcePath) {
|
||||
$source = $sourceRoot.'/'.$sourcePath;
|
||||
if (in_array($source, $this->sources)) {
|
||||
$duplicate = true;
|
||||
} else {
|
||||
$this->sources[] = $source;
|
||||
}
|
||||
}
|
||||
|
||||
return !$duplicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes visited objects and source URLs to the child iterator.
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
return new self($this->getInnerIterator()->getChildren(), $this->visited, $this->sources);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Asset\Iterator;
|
||||
|
||||
use Assetic\Asset\AssetCollectionInterface;
|
||||
|
||||
/**
|
||||
* Iterates over an asset collection.
|
||||
*
|
||||
* The iterator is responsible for cascading filters and target URL patterns
|
||||
* from parent to child assets.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class AssetCollectionIterator implements \RecursiveIterator
|
||||
{
|
||||
private $assets;
|
||||
private $filters;
|
||||
private $output;
|
||||
private $clones;
|
||||
|
||||
public function __construct(AssetCollectionInterface $coll, \SplObjectStorage $clones)
|
||||
{
|
||||
$this->assets = $coll->all();
|
||||
$this->filters = $coll->getFilters();
|
||||
$this->output = $coll->getTargetPath();
|
||||
$this->clones = $clones;
|
||||
|
||||
if (false === $pos = strpos($this->output, '.')) {
|
||||
$this->output .= '_*';
|
||||
} else {
|
||||
$this->output = substr($this->output, 0, $pos).'_*'.substr($this->output, $pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the current asset with filters and a target URL applied.
|
||||
*
|
||||
* @param Boolean $raw Returns the unmodified asset if true
|
||||
*/
|
||||
public function current($raw = false)
|
||||
{
|
||||
$asset = current($this->assets);
|
||||
|
||||
if ($raw) {
|
||||
return $asset;
|
||||
}
|
||||
|
||||
// clone once
|
||||
if (!isset($this->clones[$asset])) {
|
||||
$clone = $this->clones[$asset] = clone $asset;
|
||||
|
||||
// generate a target path based on asset name
|
||||
$name = sprintf('%s_%d', pathinfo($asset->getSourcePath(), PATHINFO_FILENAME) ?: 'part', $this->key() + 1);
|
||||
$clone->setTargetPath(str_replace('*', $name, $this->output));
|
||||
} else {
|
||||
$clone = $this->clones[$asset];
|
||||
}
|
||||
|
||||
// cascade filters
|
||||
foreach ($this->filters as $filter) {
|
||||
$clone->ensureFilter($filter);
|
||||
}
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function key()
|
||||
{
|
||||
return key($this->assets);
|
||||
}
|
||||
|
||||
public function next()
|
||||
{
|
||||
return next($this->assets);
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
return reset($this->assets);
|
||||
}
|
||||
|
||||
public function valid()
|
||||
{
|
||||
return false !== current($this->assets);
|
||||
}
|
||||
|
||||
public function hasChildren()
|
||||
{
|
||||
return current($this->assets) instanceof AssetCollectionInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* @uses current()
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
return new self($this->current(), $this->clones);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Asset;
|
||||
|
||||
use Assetic\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* Represents a string asset.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class StringAsset extends BaseAsset
|
||||
{
|
||||
private $content;
|
||||
private $lastModified;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $content The content of the asset
|
||||
* @param array $filters Filters for the asset
|
||||
* @param string $sourceRoot The source asset root directory
|
||||
* @param string $sourcePath The source asset path
|
||||
*/
|
||||
public function __construct($content, $filters = array(), $sourceRoot = null, $sourcePath = null)
|
||||
{
|
||||
$this->content = $content;
|
||||
|
||||
parent::__construct($filters, $sourceRoot, $sourcePath);
|
||||
}
|
||||
|
||||
public function load(FilterInterface $additionalFilter = null)
|
||||
{
|
||||
$this->doLoad($this->content, $additionalFilter);
|
||||
}
|
||||
|
||||
public function setLastModified($lastModified)
|
||||
{
|
||||
$this->lastModified = $lastModified;
|
||||
}
|
||||
|
||||
public function getLastModified()
|
||||
{
|
||||
return $this->lastModified;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
|
||||
/**
|
||||
* Manages assets.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class AssetManager
|
||||
{
|
||||
private $assets = array();
|
||||
|
||||
/**
|
||||
* Gets an asset by name.
|
||||
*
|
||||
* @param string $name The asset name
|
||||
*
|
||||
* @return AssetInterface The asset
|
||||
*
|
||||
* @throws InvalidArgumentException If there is no asset by that name
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
if (!isset($this->assets[$name])) {
|
||||
throw new \InvalidArgumentException(sprintf('There is no "%s" asset.', $name));
|
||||
}
|
||||
|
||||
return $this->assets[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current asset manager has a certain asset.
|
||||
*
|
||||
* @param string $name an asset name
|
||||
*
|
||||
* @return Boolean True if the asset has been set, false if not
|
||||
*/
|
||||
public function has($name)
|
||||
{
|
||||
return isset($this->assets[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an asset to the current asset manager.
|
||||
*
|
||||
* @param string $name The asset name
|
||||
* @param AssetInterface $asset The asset
|
||||
*/
|
||||
public function set($name, AssetInterface $asset)
|
||||
{
|
||||
if (!ctype_alnum(str_replace('_', '', $name))) {
|
||||
throw new \InvalidArgumentException(sprintf('The name "%s" is invalid.', $name));
|
||||
}
|
||||
|
||||
$this->assets[$name] = $asset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of asset names.
|
||||
*
|
||||
* @return array An array of asset names
|
||||
*/
|
||||
public function getNames()
|
||||
{
|
||||
return array_keys($this->assets);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic;
|
||||
|
||||
use Assetic\Util\PathUtils;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
|
||||
/**
|
||||
* Writes assets to the filesystem.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class AssetWriter
|
||||
{
|
||||
private $dir;
|
||||
private $varValues;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $dir The base web directory
|
||||
*/
|
||||
public function __construct($dir, array $varValues = array())
|
||||
{
|
||||
foreach ($varValues as $var => $values) {
|
||||
foreach ($values as $value) {
|
||||
if (!is_string($value)) {
|
||||
throw new \InvalidArgumentException(sprintf('All variable values must be strings, but got %s for variable "%s".', json_encode($value), $var));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->dir = $dir;
|
||||
$this->varValues = $varValues;
|
||||
}
|
||||
|
||||
public function writeManagerAssets(AssetManager $am)
|
||||
{
|
||||
foreach ($am->getNames() as $name) {
|
||||
$this->writeAsset($am->get($name));
|
||||
}
|
||||
}
|
||||
|
||||
public function writeAsset(AssetInterface $asset)
|
||||
{
|
||||
foreach ($this->getCombinations($asset->getVars()) as $combination) {
|
||||
$asset->setValues($combination);
|
||||
|
||||
static::write($this->dir.'/'.PathUtils::resolvePath(
|
||||
$asset->getTargetPath(), $asset->getVars(), $asset->getValues()),
|
||||
$asset->dump());
|
||||
}
|
||||
}
|
||||
|
||||
private function getCombinations(array $vars)
|
||||
{
|
||||
if (!$vars) {
|
||||
return array(array());
|
||||
}
|
||||
|
||||
$combinations = array();
|
||||
$nbValues = array();
|
||||
foreach ($this->varValues as $var => $values) {
|
||||
if (!in_array($var, $vars, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nbValues[$var] = count($values);
|
||||
}
|
||||
|
||||
for ($i=array_product($nbValues),$c=$i*2; $i<$c; $i++) {
|
||||
$k = $i;
|
||||
$combination = array();
|
||||
|
||||
foreach ($vars as $var) {
|
||||
$combination[$var] = $this->varValues[$var][$k % $nbValues[$var]];
|
||||
$k = intval($k/$nbValues[$var]);
|
||||
}
|
||||
|
||||
$combinations[] = $combination;
|
||||
}
|
||||
|
||||
return $combinations;
|
||||
}
|
||||
|
||||
static protected function write($path, $contents)
|
||||
{
|
||||
if (!is_dir($dir = dirname($path)) && false === @mkdir($dir, 0777, true)) {
|
||||
throw new \RuntimeException('Unable to create directory '.$dir);
|
||||
}
|
||||
|
||||
if (false === @file_put_contents($path, $contents)) {
|
||||
throw new \RuntimeException('Unable to write file '.$path);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Cache;
|
||||
|
||||
/**
|
||||
* Interface for a cache backend.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
interface CacheInterface
|
||||
{
|
||||
/**
|
||||
* Checks if the cache has a value for a key.
|
||||
*
|
||||
* @param string $key A unique key
|
||||
*
|
||||
* @return Boolean Whether the cache has a value for this key
|
||||
*/
|
||||
function has($key);
|
||||
|
||||
/**
|
||||
* Returns the value for a key.
|
||||
*
|
||||
* @param string $key A unique key
|
||||
*
|
||||
* @return string|null The value in the cache
|
||||
*/
|
||||
function get($key);
|
||||
|
||||
/**
|
||||
* Sets a value in the cache.
|
||||
*
|
||||
* @param string $key A unique key
|
||||
* @param string $value The value to cache
|
||||
*/
|
||||
function set($key, $value);
|
||||
|
||||
/**
|
||||
* Removes a value from the cache.
|
||||
*
|
||||
* @param string $key A unique key
|
||||
*/
|
||||
function remove($key);
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Cache;
|
||||
|
||||
/**
|
||||
* A config cache stores values using var_export() and include.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class ConfigCache
|
||||
{
|
||||
private $dir;
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param string $dir The cache directory
|
||||
*/
|
||||
public function __construct($dir)
|
||||
{
|
||||
$this->dir = $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks of the cache has a file.
|
||||
*
|
||||
* @param string $resource A cache key
|
||||
*
|
||||
* @return Boolean True if a file exists
|
||||
*/
|
||||
public function has($resource)
|
||||
{
|
||||
return file_exists($this->getSourcePath($resource));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a value to a file.
|
||||
*
|
||||
* @param string $resource A cache key
|
||||
* @param mixed $value A value to cache
|
||||
*/
|
||||
public function set($resource, $value)
|
||||
{
|
||||
$path = $this->getSourcePath($resource);
|
||||
|
||||
if (!is_dir($dir = dirname($path)) && false === @mkdir($dir, 0777, true)) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new \RuntimeException('Unable to create directory '.$dir);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
if (false === @file_put_contents($path, sprintf("<?php\n\n// $resource\nreturn %s;\n", var_export($value, true)))) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new \RuntimeException('Unable to write file '.$path);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and returns the value for the supplied cache key.
|
||||
*
|
||||
* @param string $resource A cache key
|
||||
*
|
||||
* @return mixed The cached value
|
||||
*/
|
||||
public function get($resource)
|
||||
{
|
||||
$path = $this->getSourcePath($resource);
|
||||
|
||||
if (!file_exists($path)) {
|
||||
throw new \RuntimeException('There is no cached value for '.$resource);
|
||||
}
|
||||
|
||||
return include $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a timestamp for when the cache was created.
|
||||
*
|
||||
* @param string $resource A cache key
|
||||
*
|
||||
* @return integer A UNIX timestamp
|
||||
*/
|
||||
public function getTimestamp($resource)
|
||||
{
|
||||
$path = $this->getSourcePath($resource);
|
||||
|
||||
if (!file_exists($path)) {
|
||||
throw new \RuntimeException('There is no cached value for '.$resource);
|
||||
}
|
||||
|
||||
if (false === $mtime = @filemtime($path)) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new \RuntimeException('Unable to determine file mtime for '.$path);
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
return $mtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path where the file corresponding to the supplied cache key can be included from.
|
||||
*
|
||||
* @param string $resource A cache key
|
||||
*
|
||||
* @return string A file path
|
||||
*/
|
||||
private function getSourcePath($resource)
|
||||
{
|
||||
$key = md5($resource);
|
||||
|
||||
return $this->dir.'/'.$key[0].'/'.$key.'.php';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Cache;
|
||||
|
||||
/**
|
||||
* Adds expiration to a cache backend.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class ExpiringCache implements CacheInterface
|
||||
{
|
||||
private $cache;
|
||||
private $lifetime;
|
||||
|
||||
public function __construct(CacheInterface $cache, $lifetime)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
$this->lifetime = $lifetime;
|
||||
}
|
||||
|
||||
public function has($key)
|
||||
{
|
||||
if ($this->cache->has($key)) {
|
||||
if (time() < $this->cache->get($key.'.expires')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->cache->remove($key.'.expires');
|
||||
$this->cache->remove($key);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function get($key)
|
||||
{
|
||||
return $this->cache->get($key);
|
||||
}
|
||||
|
||||
public function set($key, $value)
|
||||
{
|
||||
$this->cache->set($key.'.expires', time() + $this->lifetime);
|
||||
$this->cache->set($key, $value);
|
||||
}
|
||||
|
||||
public function remove($key)
|
||||
{
|
||||
$this->cache->remove($key.'.expires');
|
||||
$this->cache->remove($key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Cache;
|
||||
|
||||
/**
|
||||
* A simple filesystem cache.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class FilesystemCache implements CacheInterface
|
||||
{
|
||||
private $dir;
|
||||
|
||||
public function __construct($dir)
|
||||
{
|
||||
$this->dir = $dir;
|
||||
}
|
||||
|
||||
public function has($key)
|
||||
{
|
||||
return file_exists($this->dir.'/'.$key);
|
||||
}
|
||||
|
||||
public function get($key)
|
||||
{
|
||||
$path = $this->dir.'/'.$key;
|
||||
|
||||
if (!file_exists($path)) {
|
||||
throw new \RuntimeException('There is no cached value for '.$key);
|
||||
}
|
||||
|
||||
return file_get_contents($path);
|
||||
}
|
||||
|
||||
public function set($key, $value)
|
||||
{
|
||||
if (!is_dir($this->dir) && false === @mkdir($this->dir, 0777, true)) {
|
||||
throw new \RuntimeException('Unable to create directory '.$this->dir);
|
||||
}
|
||||
|
||||
$path = $this->dir.'/'.$key;
|
||||
|
||||
if (false === @file_put_contents($path, $value)) {
|
||||
throw new \RuntimeException('Unable to write file '.$path);
|
||||
}
|
||||
}
|
||||
|
||||
public function remove($key)
|
||||
{
|
||||
$path = $this->dir.'/'.$key;
|
||||
|
||||
if (file_exists($path) && false === @unlink($path)) {
|
||||
throw new \RuntimeException('Unable to remove file '.$path);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Exception;
|
||||
|
||||
/**
|
||||
* Marker.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
interface Exception
|
||||
{
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Exception;
|
||||
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
/**
|
||||
* Describes an exception that occurred within a filter.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
class FilterException extends \RuntimeException implements Exception
|
||||
{
|
||||
private $originalMessage;
|
||||
private $input;
|
||||
|
||||
public static function fromProcess(Process $proc)
|
||||
{
|
||||
$message = sprintf("An error occurred while running:\n%s", $proc->getCommandLine());
|
||||
|
||||
$errorOutput = $proc->getErrorOutput();
|
||||
if (!empty($errorOutput)) {
|
||||
$message .= "\n\nError Output:\n".str_replace("\r", '', $errorOutput);
|
||||
}
|
||||
|
||||
$output = $proc->getOutput();
|
||||
if (!empty($output)) {
|
||||
$message .= "\n\nOutput:\n".str_replace("\r", '', $output);
|
||||
}
|
||||
|
||||
return new self($message);
|
||||
}
|
||||
|
||||
public function __construct($message, $code = 0, \Exception $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
|
||||
$this->originalMessage = $message;
|
||||
}
|
||||
|
||||
public function setInput($input)
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->updateMessage();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getInput()
|
||||
{
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
private function updateMessage()
|
||||
{
|
||||
$message = $this->originalMessage;
|
||||
|
||||
if (!empty($this->input)) {
|
||||
$message .= "\n\nInput:\n".$this->input;
|
||||
}
|
||||
|
||||
$this->message = $message;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Extension\Twig;
|
||||
|
||||
use Assetic\ValueSupplierInterface;
|
||||
use Assetic\Factory\AssetFactory;
|
||||
|
||||
class AsseticExtension extends \Twig_Extension
|
||||
{
|
||||
protected $factory;
|
||||
protected $functions;
|
||||
protected $valueSupplier;
|
||||
|
||||
public function __construct(AssetFactory $factory, $functions = array(), ValueSupplierInterface $valueSupplier = null)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->functions = array();
|
||||
$this->valueSupplier = $valueSupplier;
|
||||
|
||||
foreach ($functions as $function => $options) {
|
||||
if (is_integer($function) && is_string($options)) {
|
||||
$this->functions[$options] = array('filter' => $options);
|
||||
} else {
|
||||
$this->functions[$function] = $options + array('filter' => $function);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getTokenParsers()
|
||||
{
|
||||
return array(
|
||||
new AsseticTokenParser($this->factory, 'javascripts', 'js/*.js'),
|
||||
new AsseticTokenParser($this->factory, 'stylesheets', 'css/*.css'),
|
||||
new AsseticTokenParser($this->factory, 'image', 'images/*', true),
|
||||
);
|
||||
}
|
||||
|
||||
public function getFunctions()
|
||||
{
|
||||
$functions = array();
|
||||
foreach ($this->functions as $function => $filter) {
|
||||
$functions[$function] = new AsseticFilterFunction($function);
|
||||
}
|
||||
|
||||
return $functions;
|
||||
}
|
||||
|
||||
public function getGlobals()
|
||||
{
|
||||
return array(
|
||||
'assetic' => array(
|
||||
'debug' => $this->factory->isDebug(),
|
||||
'vars' => null !== $this->valueSupplier ? $this->valueSupplier->getValues() : array(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function getFilterInvoker($function)
|
||||
{
|
||||
return new AsseticFilterInvoker($this->factory, $this->functions[$function]);
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return 'assetic';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Extension\Twig;
|
||||
|
||||
class AsseticFilterFunction extends \Twig_Function
|
||||
{
|
||||
private $filter;
|
||||
|
||||
public function __construct($filter, $options = array())
|
||||
{
|
||||
$this->filter = $filter;
|
||||
|
||||
parent::__construct($options);
|
||||
}
|
||||
|
||||
public function compile()
|
||||
{
|
||||
return sprintf('$this->env->getExtension(\'assetic\')->getFilterInvoker(\'%s\')->invoke', $this->filter);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Extension\Twig;
|
||||
|
||||
use Assetic\Factory\AssetFactory;
|
||||
|
||||
/**
|
||||
* Filters a single asset.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class AsseticFilterInvoker
|
||||
{
|
||||
private $factory;
|
||||
private $filters;
|
||||
private $options;
|
||||
|
||||
public function __construct($factory, $filter)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
|
||||
if (is_array($filter) && isset($filter['filter'])) {
|
||||
$this->filters = (array) $filter['filter'];
|
||||
$this->options = isset($filter['options']) ? (array) $filter['options'] : array();
|
||||
} else {
|
||||
$this->filters = (array) $filter;
|
||||
$this->options = array();
|
||||
}
|
||||
}
|
||||
|
||||
public function getFactory()
|
||||
{
|
||||
return $this->factory;
|
||||
}
|
||||
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
public function invoke($input, array $options = array())
|
||||
{
|
||||
$asset = $this->factory->createAsset($input, $this->filters, $options + $this->options);
|
||||
|
||||
return $asset->getTargetPath();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Extension\Twig;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
|
||||
class AsseticNode extends \Twig_Node
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Available attributes:
|
||||
*
|
||||
* * debug: The debug mode
|
||||
* * combine: Whether to combine assets
|
||||
* * var_name: The name of the variable to expose to the body node
|
||||
*
|
||||
* @param AssetInterface $asset The asset
|
||||
* @param Twig_NodeInterface $body The body node
|
||||
* @param array $inputs An array of input strings
|
||||
* @param array $filters An array of filter strings
|
||||
* @param string $name The name of the asset
|
||||
* @param array $attributes An array of attributes
|
||||
* @param integer $lineno The line number
|
||||
* @param string $tag The tag name
|
||||
*/
|
||||
public function __construct(AssetInterface $asset, \Twig_NodeInterface $body, array $inputs, array $filters, $name, array $attributes = array(), $lineno = 0, $tag = null)
|
||||
{
|
||||
$nodes = array('body' => $body);
|
||||
|
||||
$attributes = array_replace(
|
||||
array('debug' => null, 'combine' => null, 'var_name' => 'asset_url'),
|
||||
$attributes,
|
||||
array('asset' => $asset, 'inputs' => $inputs, 'filters' => $filters, 'name' => $name)
|
||||
);
|
||||
|
||||
parent::__construct($nodes, $attributes, $lineno, $tag);
|
||||
}
|
||||
|
||||
public function compile(\Twig_Compiler $compiler)
|
||||
{
|
||||
$compiler->addDebugInfo($this);
|
||||
|
||||
$combine = $this->getAttribute('combine');
|
||||
$debug = $this->getAttribute('debug');
|
||||
|
||||
if (null === $combine && null !== $debug) {
|
||||
$combine = !$debug;
|
||||
}
|
||||
|
||||
if (null === $combine) {
|
||||
$compiler
|
||||
->write("if (isset(\$context['assetic']['debug']) && \$context['assetic']['debug']) {\n")
|
||||
->indent()
|
||||
;
|
||||
|
||||
$this->compileDebug($compiler);
|
||||
|
||||
$compiler
|
||||
->outdent()
|
||||
->write("} else {\n")
|
||||
->indent()
|
||||
;
|
||||
|
||||
$this->compileAsset($compiler, $this->getAttribute('asset'), $this->getAttribute('name'));
|
||||
|
||||
$compiler
|
||||
->outdent()
|
||||
->write("}\n")
|
||||
;
|
||||
} elseif ($combine) {
|
||||
$this->compileAsset($compiler, $this->getAttribute('asset'), $this->getAttribute('name'));
|
||||
} else {
|
||||
$this->compileDebug($compiler);
|
||||
}
|
||||
|
||||
$compiler
|
||||
->write('unset($context[')
|
||||
->repr($this->getAttribute('var_name'))
|
||||
->raw("]);\n")
|
||||
;
|
||||
}
|
||||
|
||||
protected function compileDebug(\Twig_Compiler $compiler)
|
||||
{
|
||||
$i = 0;
|
||||
foreach ($this->getAttribute('asset') as $leaf) {
|
||||
$leafName = $this->getAttribute('name').'_'.$i++;
|
||||
$this->compileAsset($compiler, $leaf, $leafName);
|
||||
}
|
||||
}
|
||||
|
||||
protected function compileAsset(\Twig_Compiler $compiler, AssetInterface $asset, $name)
|
||||
{
|
||||
if ($vars = $asset->getVars()) {
|
||||
$compiler->write("// check variable conditions\n");
|
||||
|
||||
foreach ($vars as $var) {
|
||||
$compiler
|
||||
->write("if (!isset(\$context['assetic']['vars']['$var'])) {\n")
|
||||
->indent()
|
||||
->write("throw new \RuntimeException(sprintf('The asset \"".$name."\" expected variable \"".$var."\" to be set, but got only these vars: %s. Did you set-up a value supplier?', isset(\$context['assetic']['vars']) && \$context['assetic']['vars'] ? implode(', ', \$context['assetic']['vars']) : '# none #'));\n")
|
||||
->outdent()
|
||||
->write("}\n")
|
||||
;
|
||||
}
|
||||
|
||||
$compiler->raw("\n");
|
||||
}
|
||||
|
||||
$compiler
|
||||
->write("// asset \"$name\"\n")
|
||||
->write('$context[')
|
||||
->repr($this->getAttribute('var_name'))
|
||||
->raw('] = ')
|
||||
;
|
||||
|
||||
$this->compileAssetUrl($compiler, $asset, $name);
|
||||
|
||||
$compiler
|
||||
->raw(";\n")
|
||||
->subcompile($this->getNode('body'))
|
||||
;
|
||||
}
|
||||
|
||||
protected function compileAssetUrl(\Twig_Compiler $compiler, AssetInterface $asset, $name)
|
||||
{
|
||||
if (!$vars = $asset->getVars()) {
|
||||
$compiler->repr($asset->getTargetPath());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw("strtr(")
|
||||
->string($asset->getTargetPath())
|
||||
->raw(", array(");
|
||||
;
|
||||
|
||||
$first = true;
|
||||
foreach ($vars as $var) {
|
||||
if (!$first) {
|
||||
$compiler->raw(", ");
|
||||
}
|
||||
$first = false;
|
||||
|
||||
$compiler
|
||||
->string("{".$var."}")
|
||||
->raw(" => \$context['assetic']['vars']['$var']")
|
||||
;
|
||||
}
|
||||
|
||||
$compiler
|
||||
->raw("))")
|
||||
;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Extension\Twig;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Factory\AssetFactory;
|
||||
|
||||
class AsseticTokenParser extends \Twig_TokenParser
|
||||
{
|
||||
private $factory;
|
||||
private $tag;
|
||||
private $output;
|
||||
private $single;
|
||||
private $extensions;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Attributes can be added to the tag by passing names as the options
|
||||
* array. These values, if found, will be passed to the factory and node.
|
||||
*
|
||||
* @param AssetFactory $factory The asset factory
|
||||
* @param string $tag The tag name
|
||||
* @param string $output The default output string
|
||||
* @param Boolean $single Whether to force a single asset
|
||||
* @param array $extensions Additional attribute names to look for
|
||||
*/
|
||||
public function __construct(AssetFactory $factory, $tag, $output, $single = false, array $extensions = array())
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->tag = $tag;
|
||||
$this->output = $output;
|
||||
$this->single = $single;
|
||||
$this->extensions = $extensions;
|
||||
}
|
||||
|
||||
public function parse(\Twig_Token $token)
|
||||
{
|
||||
$inputs = array();
|
||||
$filters = array();
|
||||
$name = null;
|
||||
$attributes = array(
|
||||
'output' => $this->output,
|
||||
'var_name' => 'asset_url',
|
||||
'vars' => array(),
|
||||
);
|
||||
|
||||
$stream = $this->parser->getStream();
|
||||
while (!$stream->test(\Twig_Token::BLOCK_END_TYPE)) {
|
||||
if ($stream->test(\Twig_Token::STRING_TYPE)) {
|
||||
// '@jquery', 'js/src/core/*', 'js/src/extra.js'
|
||||
$inputs[] = $stream->next()->getValue();
|
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'filter')) {
|
||||
// filter='yui_js'
|
||||
$stream->next();
|
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
|
||||
$filters = array_merge($filters, array_filter(array_map('trim', explode(',', $stream->expect(\Twig_Token::STRING_TYPE)->getValue()))));
|
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'output')) {
|
||||
// output='js/packed/*.js' OR output='js/core.js'
|
||||
$stream->next();
|
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
|
||||
$attributes['output'] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue();
|
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'name')) {
|
||||
// name='core_js'
|
||||
$stream->next();
|
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
|
||||
$name = $stream->expect(\Twig_Token::STRING_TYPE)->getValue();
|
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'as')) {
|
||||
// as='the_url'
|
||||
$stream->next();
|
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
|
||||
$attributes['var_name'] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue();
|
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'debug')) {
|
||||
// debug=true
|
||||
$stream->next();
|
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
|
||||
$attributes['debug'] = 'true' == $stream->expect(\Twig_Token::NAME_TYPE, array('true', 'false'))->getValue();
|
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'combine')) {
|
||||
// combine=true
|
||||
$stream->next();
|
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
|
||||
$attributes['combine'] = 'true' == $stream->expect(\Twig_Token::NAME_TYPE, array('true', 'false'))->getValue();
|
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, 'vars')) {
|
||||
// vars=['locale','browser']
|
||||
$stream->next();
|
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
|
||||
$stream->expect(\Twig_Token::PUNCTUATION_TYPE, '[');
|
||||
|
||||
while ($stream->test(\Twig_Token::STRING_TYPE)) {
|
||||
$attributes['vars'][] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue();
|
||||
|
||||
if (!$stream->test(\Twig_Token::PUNCTUATION_TYPE, ',')) {
|
||||
break;
|
||||
}
|
||||
|
||||
$stream->next();
|
||||
}
|
||||
|
||||
$stream->expect(\Twig_Token::PUNCTUATION_TYPE, ']');
|
||||
} elseif ($stream->test(\Twig_Token::NAME_TYPE, $this->extensions)) {
|
||||
// an arbitrary configured attribute
|
||||
$key = $stream->next()->getValue();
|
||||
$stream->expect(\Twig_Token::OPERATOR_TYPE, '=');
|
||||
$attributes[$key] = $stream->expect(\Twig_Token::STRING_TYPE)->getValue();
|
||||
} else {
|
||||
$token = $stream->getCurrent();
|
||||
throw new \Twig_Error_Syntax(sprintf('Unexpected token "%s" of value "%s"', \Twig_Token::typeToEnglish($token->getType(), $token->getLine()), $token->getValue()), $token->getLine());
|
||||
}
|
||||
}
|
||||
|
||||
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
|
||||
$endtag = 'end'.$this->getTag();
|
||||
$test = function(\Twig_Token $token) use($endtag) { return $token->test($endtag); };
|
||||
$body = $this->parser->subparse($test, true);
|
||||
|
||||
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
|
||||
if ($this->single && 1 < count($inputs)) {
|
||||
$inputs = array_slice($inputs, -1);
|
||||
}
|
||||
|
||||
if (!$name) {
|
||||
$name = $this->factory->generateAssetName($inputs, $filters, $attributes);
|
||||
}
|
||||
|
||||
$asset = $this->factory->createAsset($inputs, $filters, $attributes + array('name' => $name));
|
||||
|
||||
return $this->createNode($asset, $body, $inputs, $filters, $name, $attributes, $token->getLine(), $this->getTag());
|
||||
}
|
||||
|
||||
public function getTag()
|
||||
{
|
||||
return $this->tag;
|
||||
}
|
||||
|
||||
protected function createNode(AssetInterface $asset, \Twig_NodeInterface $body, array $inputs, array $filters, $name, array $attributes = array(), $lineno = 0, $tag = null)
|
||||
{
|
||||
return new AsseticNode($asset, $body, $inputs, $filters, $name, $attributes, $lineno, $tag);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Extension\Twig;
|
||||
|
||||
use Assetic\Factory\Loader\FormulaLoaderInterface;
|
||||
use Assetic\Factory\Resource\ResourceInterface;
|
||||
|
||||
/**
|
||||
* Loads asset formulae from Twig templates.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class TwigFormulaLoader implements FormulaLoaderInterface
|
||||
{
|
||||
private $twig;
|
||||
|
||||
public function __construct(\Twig_Environment $twig)
|
||||
{
|
||||
$this->twig = $twig;
|
||||
}
|
||||
|
||||
public function load(ResourceInterface $resource)
|
||||
{
|
||||
try {
|
||||
$tokens = $this->twig->tokenize($resource->getContent(), (string) $resource);
|
||||
$nodes = $this->twig->parse($tokens);
|
||||
} catch (\Exception $e) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $this->loadNode($nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads assets from the supplied node.
|
||||
*
|
||||
* @return array An array of asset formulae indexed by name
|
||||
*/
|
||||
private function loadNode(\Twig_Node $node)
|
||||
{
|
||||
$formulae = array();
|
||||
|
||||
if ($node instanceof AsseticNode) {
|
||||
$formulae[$node->getAttribute('name')] = array(
|
||||
$node->getAttribute('inputs'),
|
||||
$node->getAttribute('filters'),
|
||||
array(
|
||||
'output' => $node->getAttribute('asset')->getTargetPath(),
|
||||
'name' => $node->getAttribute('name'),
|
||||
'debug' => $node->getAttribute('debug'),
|
||||
'combine' => $node->getAttribute('combine'),
|
||||
'vars' => $node->getAttribute('vars'),
|
||||
),
|
||||
);
|
||||
} elseif ($node instanceof \Twig_Node_Expression_Function) {
|
||||
$name = version_compare(\Twig_Environment::VERSION, '1.2.0-DEV', '<')
|
||||
? $node->getNode('name')->getAttribute('name')
|
||||
: $node->getAttribute('name');
|
||||
|
||||
if ($this->twig->getFunction($name) instanceof AsseticFilterFunction) {
|
||||
$arguments = array();
|
||||
foreach ($node->getNode('arguments') as $argument) {
|
||||
$arguments[] = eval('return '.$this->twig->compile($argument).';');
|
||||
}
|
||||
|
||||
$invoker = $this->twig->getExtension('assetic')->getFilterInvoker($name);
|
||||
|
||||
$inputs = isset($arguments[0]) ? (array) $arguments[0] : array();
|
||||
$filters = $invoker->getFilters();
|
||||
$options = array_replace($invoker->getOptions(), isset($arguments[1]) ? $arguments[1] : array());
|
||||
|
||||
if (!isset($options['name'])) {
|
||||
$options['name'] = $invoker->getFactory()->generateAssetName($inputs, $filters, $options);
|
||||
}
|
||||
|
||||
$formulae[$options['name']] = array($inputs, $filters, $options);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($node as $child) {
|
||||
if ($child instanceof \Twig_Node) {
|
||||
$formulae += $this->loadNode($child);
|
||||
}
|
||||
}
|
||||
|
||||
return $formulae;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Extension\Twig;
|
||||
|
||||
use Assetic\Factory\Resource\ResourceInterface;
|
||||
|
||||
/**
|
||||
* A Twig template resource.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class TwigResource implements ResourceInterface
|
||||
{
|
||||
private $loader;
|
||||
private $name;
|
||||
|
||||
public function __construct(\Twig_LoaderInterface $loader, $name)
|
||||
{
|
||||
$this->loader = $loader;
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getContent()
|
||||
{
|
||||
try {
|
||||
return $this->loader->getSource($this->name);
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
public function isFresh($timestamp)
|
||||
{
|
||||
try {
|
||||
return $this->loader->isFresh($this->name, $timestamp);
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,386 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Factory;
|
||||
|
||||
use Assetic\Asset\AssetCollection;
|
||||
use Assetic\Asset\AssetCollectionInterface;
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Asset\AssetReference;
|
||||
use Assetic\Asset\FileAsset;
|
||||
use Assetic\Asset\GlobAsset;
|
||||
use Assetic\Asset\HttpAsset;
|
||||
use Assetic\AssetManager;
|
||||
use Assetic\Factory\Worker\WorkerInterface;
|
||||
use Assetic\FilterManager;
|
||||
|
||||
/**
|
||||
* The asset factory creates asset objects.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class AssetFactory
|
||||
{
|
||||
private $root;
|
||||
private $debug;
|
||||
private $output;
|
||||
private $workers;
|
||||
private $am;
|
||||
private $fm;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $root The default root directory
|
||||
* @param string $output The default output string
|
||||
* @param Boolean $debug Filters prefixed with a "?" will be omitted in debug mode
|
||||
*/
|
||||
public function __construct($root, $debug = false)
|
||||
{
|
||||
$this->root = rtrim($root, '/');
|
||||
$this->debug = $debug;
|
||||
$this->output = 'assetic/*';
|
||||
$this->workers = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets debug mode for the current factory.
|
||||
*
|
||||
* @param Boolean $debug Debug mode
|
||||
*/
|
||||
public function setDebug($debug)
|
||||
{
|
||||
$this->debug = $debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the factory is in debug mode.
|
||||
*
|
||||
* @return Boolean Debug mode
|
||||
*/
|
||||
public function isDebug()
|
||||
{
|
||||
return $this->debug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default output string.
|
||||
*
|
||||
* @param string $output The default output string
|
||||
*/
|
||||
public function setDefaultOutput($output)
|
||||
{
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a factory worker.
|
||||
*
|
||||
* @param WorkerInterface $worker A worker
|
||||
*/
|
||||
public function addWorker(WorkerInterface $worker)
|
||||
{
|
||||
$this->workers[] = $worker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current asset manager.
|
||||
*
|
||||
* @return AssetManager|null The asset manager
|
||||
*/
|
||||
public function getAssetManager()
|
||||
{
|
||||
return $this->am;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the asset manager to use when creating asset references.
|
||||
*
|
||||
* @param AssetManager $am The asset manager
|
||||
*/
|
||||
public function setAssetManager(AssetManager $am)
|
||||
{
|
||||
$this->am = $am;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current filter manager.
|
||||
*
|
||||
* @return FilterManager|null The filter manager
|
||||
*/
|
||||
public function getFilterManager()
|
||||
{
|
||||
return $this->fm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filter manager to use when adding filters.
|
||||
*
|
||||
* @param FilterManager $fm The filter manager
|
||||
*/
|
||||
public function setFilterManager(FilterManager $fm)
|
||||
{
|
||||
$this->fm = $fm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new asset.
|
||||
*
|
||||
* Prefixing a filter name with a question mark will cause it to be
|
||||
* omitted when the factory is in debug mode.
|
||||
*
|
||||
* Available options:
|
||||
*
|
||||
* * output: An output string
|
||||
* * name: An asset name for interpolation in output patterns
|
||||
* * debug: Forces debug mode on or off for this asset
|
||||
* * root: An array or string of more root directories
|
||||
*
|
||||
* @param array|string $inputs An array of input strings
|
||||
* @param array|string $filters An array of filter names
|
||||
* @param array $options An array of options
|
||||
*
|
||||
* @return AssetCollection An asset collection
|
||||
*/
|
||||
public function createAsset($inputs = array(), $filters = array(), array $options = array())
|
||||
{
|
||||
if (!is_array($inputs)) {
|
||||
$inputs = array($inputs);
|
||||
}
|
||||
|
||||
if (!is_array($filters)) {
|
||||
$filters = array($filters);
|
||||
}
|
||||
|
||||
if (!isset($options['output'])) {
|
||||
$options['output'] = $this->output;
|
||||
}
|
||||
|
||||
if (!isset($options['vars'])) {
|
||||
$options['vars'] = array();
|
||||
}
|
||||
|
||||
if (!isset($options['debug'])) {
|
||||
$options['debug'] = $this->debug;
|
||||
}
|
||||
|
||||
if (!isset($options['root'])) {
|
||||
$options['root'] = array($this->root);
|
||||
} else {
|
||||
if (!is_array($options['root'])) {
|
||||
$options['root'] = array($options['root']);
|
||||
}
|
||||
|
||||
$options['root'][] = $this->root;
|
||||
}
|
||||
|
||||
if (!isset($options['name'])) {
|
||||
$options['name'] = $this->generateAssetName($inputs, $filters, $options);
|
||||
}
|
||||
|
||||
$asset = $this->createAssetCollection(array(), $options);
|
||||
$extensions = array();
|
||||
|
||||
// inner assets
|
||||
foreach ($inputs as $input) {
|
||||
if (is_array($input)) {
|
||||
// nested formula
|
||||
$asset->add(call_user_func_array(array($this, 'createAsset'), $input));
|
||||
} else {
|
||||
$asset->add($this->parseInput($input, $options));
|
||||
$extensions[pathinfo($input, PATHINFO_EXTENSION)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// filters
|
||||
foreach ($filters as $filter) {
|
||||
if ('?' != $filter[0]) {
|
||||
$asset->ensureFilter($this->getFilter($filter));
|
||||
} elseif (!$options['debug']) {
|
||||
$asset->ensureFilter($this->getFilter(substr($filter, 1)));
|
||||
}
|
||||
}
|
||||
|
||||
// append variables
|
||||
if (!empty($options['vars'])) {
|
||||
$toAdd = array();
|
||||
foreach ($options['vars'] as $var) {
|
||||
if (false !== strpos($options['output'], '{'.$var.'}')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$toAdd[] = '{'.$var.'}';
|
||||
}
|
||||
|
||||
if ($toAdd) {
|
||||
$options['output'] = str_replace('*', '*.'.implode('.', $toAdd), $options['output']);
|
||||
}
|
||||
}
|
||||
|
||||
// append consensus extension if missing
|
||||
if (1 == count($extensions) && !pathinfo($options['output'], PATHINFO_EXTENSION) && $extension = key($extensions)) {
|
||||
$options['output'] .= '.'.$extension;
|
||||
}
|
||||
|
||||
// output --> target url
|
||||
$asset->setTargetPath(str_replace('*', $options['name'], $options['output']));
|
||||
|
||||
// apply workers and return
|
||||
return $this->applyWorkers($asset);
|
||||
}
|
||||
|
||||
public function generateAssetName($inputs, $filters, $options = array())
|
||||
{
|
||||
foreach (array_diff(array_keys($options), array('output', 'debug', 'root')) as $key) {
|
||||
unset($options[$key]);
|
||||
}
|
||||
|
||||
ksort($options);
|
||||
|
||||
return substr(sha1(serialize($inputs).serialize($filters).serialize($options)), 0, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an input string string into an asset.
|
||||
*
|
||||
* The input string can be one of the following:
|
||||
*
|
||||
* * A reference: If the string starts with an "at" sign it will be interpreted as a reference to an asset in the asset manager
|
||||
* * An absolute URL: If the string contains "://" or starts with "//" it will be interpreted as an HTTP asset
|
||||
* * A glob: If the string contains a "*" it will be interpreted as a glob
|
||||
* * A path: Otherwise the string is interpreted as a filesystem path
|
||||
*
|
||||
* Both globs and paths will be absolutized using the current root directory.
|
||||
*
|
||||
* @param string $input An input string
|
||||
* @param array $options An array of options
|
||||
*
|
||||
* @return AssetInterface An asset
|
||||
*/
|
||||
protected function parseInput($input, array $options = array())
|
||||
{
|
||||
if ('@' == $input[0]) {
|
||||
return $this->createAssetReference(substr($input, 1));
|
||||
}
|
||||
|
||||
if (false !== strpos($input, '://') || 0 === strpos($input, '//')) {
|
||||
return $this->createHttpAsset($input, $options['vars']);
|
||||
}
|
||||
|
||||
if (self::isAbsolutePath($input)) {
|
||||
if ($root = self::findRootDir($input, $options['root'])) {
|
||||
$path = ltrim(substr($input, strlen($root)), '/');
|
||||
} else {
|
||||
$path = null;
|
||||
}
|
||||
} else {
|
||||
$root = $this->root;
|
||||
$path = $input;
|
||||
$input = $this->root.'/'.$path;
|
||||
}
|
||||
if (false !== strpos($input, '*')) {
|
||||
return $this->createGlobAsset($input, $root, $options['vars']);
|
||||
} else {
|
||||
return $this->createFileAsset($input, $root, $path, $options['vars']);
|
||||
}
|
||||
}
|
||||
|
||||
protected function createAssetCollection(array $assets = array(), array $options = array())
|
||||
{
|
||||
return new AssetCollection($assets, array(), null, isset($options['vars']) ? $options['vars'] : array());
|
||||
}
|
||||
|
||||
protected function createAssetReference($name)
|
||||
{
|
||||
if (!$this->am) {
|
||||
throw new \LogicException('There is no asset manager.');
|
||||
}
|
||||
|
||||
return new AssetReference($this->am, $name);
|
||||
}
|
||||
|
||||
protected function createHttpAsset($sourceUrl, $vars)
|
||||
{
|
||||
return new HttpAsset($sourceUrl, array(), false, $vars);
|
||||
}
|
||||
|
||||
protected function createGlobAsset($glob, $root = null, $vars)
|
||||
{
|
||||
return new GlobAsset($glob, array(), $root, $vars);
|
||||
}
|
||||
|
||||
protected function createFileAsset($source, $root = null, $path = null, $vars)
|
||||
{
|
||||
return new FileAsset($source, array(), $root, $path, $vars);
|
||||
}
|
||||
|
||||
protected function getFilter($name)
|
||||
{
|
||||
if (!$this->fm) {
|
||||
throw new \LogicException('There is no filter manager.');
|
||||
}
|
||||
|
||||
return $this->fm->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters an asset collection through the factory workers.
|
||||
*
|
||||
* Each leaf asset will be processed first, followed by the asset
|
||||
* collection itself.
|
||||
*
|
||||
* @param AssetCollectionInterface $asset An asset collection
|
||||
*/
|
||||
private function applyWorkers(AssetCollectionInterface $asset)
|
||||
{
|
||||
foreach ($asset as $leaf) {
|
||||
foreach ($this->workers as $worker) {
|
||||
$retval = $worker->process($leaf);
|
||||
|
||||
if ($retval instanceof AssetInterface && $leaf !== $retval) {
|
||||
$asset->replaceLeaf($leaf, $retval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->workers as $worker) {
|
||||
$retval = $worker->process($asset);
|
||||
|
||||
if ($retval instanceof AssetInterface) {
|
||||
$asset = $retval;
|
||||
}
|
||||
}
|
||||
|
||||
return $asset instanceof AssetCollectionInterface ? $asset : $this->createAssetCollection(array($asset));
|
||||
}
|
||||
|
||||
static private function isAbsolutePath($path)
|
||||
{
|
||||
return '/' == $path[0] || '\\' == $path[0] || (3 < strlen($path) && ctype_alpha($path[0]) && $path[1] == ':' && ('\\' == $path[2] || '/' == $path[2]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through the root directories and returns the first match.
|
||||
*
|
||||
* @param string $path An absolute path
|
||||
* @param array $roots An array of root directories
|
||||
*
|
||||
* @return string|null The matching root directory, if found
|
||||
*/
|
||||
static private function findRootDir($path, array $roots)
|
||||
{
|
||||
foreach ($roots as $root) {
|
||||
if (0 === strpos($path, $root)) {
|
||||
return $root;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Factory;
|
||||
|
||||
use Assetic\AssetManager;
|
||||
use Assetic\Factory\Loader\FormulaLoaderInterface;
|
||||
use Assetic\Factory\Resource\ResourceInterface;
|
||||
|
||||
/**
|
||||
* A lazy asset manager is a composition of a factory and many formula loaders.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class LazyAssetManager extends AssetManager
|
||||
{
|
||||
private $factory;
|
||||
private $loaders;
|
||||
private $resources;
|
||||
private $formulae;
|
||||
private $loaded;
|
||||
private $loading;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param AssetFactory $factory The asset factory
|
||||
* @param array $loaders An array of loaders indexed by alias
|
||||
*/
|
||||
public function __construct(AssetFactory $factory, $loaders = array())
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->loaders = array();
|
||||
$this->resources = array();
|
||||
$this->formulae = array();
|
||||
$this->loaded = false;
|
||||
$this->loading = false;
|
||||
|
||||
foreach ($loaders as $alias => $loader) {
|
||||
$this->setLoader($alias, $loader);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a loader to the asset manager.
|
||||
*
|
||||
* @param string $alias An alias for the loader
|
||||
* @param FormulaLoaderInterface $loader A loader
|
||||
*/
|
||||
public function setLoader($alias, FormulaLoaderInterface $loader)
|
||||
{
|
||||
$this->loaders[$alias] = $loader;
|
||||
$this->loaded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a resource to the asset manager.
|
||||
*
|
||||
* @param ResourceInterface $resource A resource
|
||||
* @param string $loader The loader alias for this resource
|
||||
*/
|
||||
public function addResource(ResourceInterface $resource, $loader)
|
||||
{
|
||||
$this->resources[$loader][] = $resource;
|
||||
$this->loaded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of resources.
|
||||
*
|
||||
* @return array An array of resources
|
||||
*/
|
||||
public function getResources()
|
||||
{
|
||||
$resources = array();
|
||||
foreach ($this->resources as $r) {
|
||||
$resources = array_merge($resources, $r);
|
||||
}
|
||||
|
||||
return $resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for an asset formula.
|
||||
*
|
||||
* @param string $name An asset name
|
||||
*
|
||||
* @return Boolean If there is a formula
|
||||
*/
|
||||
public function hasFormula($name)
|
||||
{
|
||||
if (!$this->loaded) {
|
||||
$this->load();
|
||||
}
|
||||
|
||||
return isset($this->formulae[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an asset's formula.
|
||||
*
|
||||
* @param string $name An asset name
|
||||
*
|
||||
* @return array The formula
|
||||
*
|
||||
* @throws InvalidArgumentException If there is no formula by that name
|
||||
*/
|
||||
public function getFormula($name)
|
||||
{
|
||||
if (!$this->loaded) {
|
||||
$this->load();
|
||||
}
|
||||
|
||||
if (!isset($this->formulae[$name])) {
|
||||
throw new \InvalidArgumentException(sprintf('There is no "%s" formula.', $name));
|
||||
}
|
||||
|
||||
return $this->formulae[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a formula on the asset manager.
|
||||
*
|
||||
* @param string $name An asset name
|
||||
* @param array $formula A formula
|
||||
*/
|
||||
public function setFormula($name, array $formula)
|
||||
{
|
||||
$this->formulae[$name] = $formula;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads formulae from resources.
|
||||
*
|
||||
* @throws LogicException If a resource has been added to an invalid loader
|
||||
*/
|
||||
public function load()
|
||||
{
|
||||
if ($this->loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($diff = array_diff(array_keys($this->resources), array_keys($this->loaders))) {
|
||||
throw new \LogicException('The following loader(s) are not registered: '.implode(', ', $diff));
|
||||
}
|
||||
|
||||
$this->loading = true;
|
||||
|
||||
foreach ($this->resources as $loader => $resources) {
|
||||
foreach ($resources as $resource) {
|
||||
$this->formulae = array_replace($this->formulae, $this->loaders[$loader]->load($resource));
|
||||
}
|
||||
}
|
||||
|
||||
$this->loaded = true;
|
||||
$this->loading = false;
|
||||
}
|
||||
|
||||
public function get($name)
|
||||
{
|
||||
if (!$this->loaded) {
|
||||
$this->load();
|
||||
}
|
||||
|
||||
if (!parent::has($name) && isset($this->formulae[$name])) {
|
||||
list($inputs, $filters, $options) = $this->formulae[$name];
|
||||
$options['name'] = $name;
|
||||
parent::set($name, $this->factory->createAsset($inputs, $filters, $options));
|
||||
}
|
||||
|
||||
return parent::get($name);
|
||||
}
|
||||
|
||||
public function has($name)
|
||||
{
|
||||
if (!$this->loaded) {
|
||||
$this->load();
|
||||
}
|
||||
|
||||
return isset($this->formulae[$name]) || parent::has($name);
|
||||
}
|
||||
|
||||
public function getNames()
|
||||
{
|
||||
if (!$this->loaded) {
|
||||
$this->load();
|
||||
}
|
||||
|
||||
return array_unique(array_merge(parent::getNames(), array_keys($this->formulae)));
|
||||
}
|
||||
|
||||
public function isDebug()
|
||||
{
|
||||
return $this->factory->isDebug();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Factory\Loader;
|
||||
|
||||
use Assetic\Factory\AssetFactory;
|
||||
use Assetic\Factory\Resource\ResourceInterface;
|
||||
|
||||
/**
|
||||
* Loads asset formulae from PHP files.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
abstract class BasePhpFormulaLoader implements FormulaLoaderInterface
|
||||
{
|
||||
protected $factory;
|
||||
protected $prototypes;
|
||||
|
||||
public function __construct(AssetFactory $factory)
|
||||
{
|
||||
$this->factory = $factory;
|
||||
$this->prototypes = array();
|
||||
|
||||
foreach ($this->registerPrototypes() as $prototype => $options) {
|
||||
$this->addPrototype($prototype, $options);
|
||||
}
|
||||
}
|
||||
|
||||
public function addPrototype($prototype, array $options = array())
|
||||
{
|
||||
$tokens = token_get_all('<?php '.$prototype);
|
||||
array_shift($tokens);
|
||||
|
||||
$this->prototypes[$prototype] = array($tokens, $options);
|
||||
}
|
||||
|
||||
public function load(ResourceInterface $resource)
|
||||
{
|
||||
if (!$nbProtos = count($this->prototypes)) {
|
||||
throw new \LogicException('There are no prototypes registered.');
|
||||
}
|
||||
|
||||
$buffers = array_fill(0, $nbProtos, '');
|
||||
$bufferLevels = array_fill(0, $nbProtos, 0);
|
||||
$buffersInWildcard = array();
|
||||
|
||||
$tokens = token_get_all($resource->getContent());
|
||||
$calls = array();
|
||||
|
||||
while ($token = array_shift($tokens)) {
|
||||
$current = self::tokenToString($token);
|
||||
// loop through each prototype (by reference)
|
||||
foreach (array_keys($this->prototypes) as $i) {
|
||||
$prototype =& $this->prototypes[$i][0];
|
||||
$options = $this->prototypes[$i][1];
|
||||
$buffer =& $buffers[$i];
|
||||
$level =& $bufferLevels[$i];
|
||||
|
||||
if (isset($buffersInWildcard[$i])) {
|
||||
switch ($current) {
|
||||
case '(': ++$level; break;
|
||||
case ')': --$level; break;
|
||||
}
|
||||
|
||||
$buffer .= $current;
|
||||
|
||||
if (!$level) {
|
||||
$calls[] = array($buffer.';', $options);
|
||||
$buffer = '';
|
||||
unset($buffersInWildcard[$i]);
|
||||
}
|
||||
} elseif ($current == self::tokenToString(current($prototype))) {
|
||||
$buffer .= $current;
|
||||
if ('*' == self::tokenToString(next($prototype))) {
|
||||
$buffersInWildcard[$i] = true;
|
||||
++$level;
|
||||
}
|
||||
} else {
|
||||
reset($prototype);
|
||||
unset($buffersInWildcard[$i]);
|
||||
$buffer = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$formulae = array();
|
||||
foreach ($calls as $call) {
|
||||
$formulae += call_user_func_array(array($this, 'processCall'), $call);
|
||||
}
|
||||
|
||||
return $formulae;
|
||||
}
|
||||
|
||||
private function processCall($call, array $protoOptions = array())
|
||||
{
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'assetic');
|
||||
file_put_contents($tmp, implode("\n", array(
|
||||
'<?php',
|
||||
$this->registerSetupCode(),
|
||||
$call,
|
||||
'echo serialize($_call);',
|
||||
)));
|
||||
$args = unserialize(shell_exec('php '.escapeshellarg($tmp)));
|
||||
unlink($tmp);
|
||||
|
||||
$inputs = isset($args[0]) ? self::argumentToArray($args[0]) : array();
|
||||
$filters = isset($args[1]) ? self::argumentToArray($args[1]) : array();
|
||||
$options = isset($args[2]) ? $args[2] : array();
|
||||
|
||||
if (!isset($options['debug'])) {
|
||||
$options['debug'] = $this->factory->isDebug();
|
||||
}
|
||||
|
||||
if (!is_array($options)) {
|
||||
throw new \RuntimeException('The third argument must be omitted, null or an array.');
|
||||
}
|
||||
|
||||
// apply the prototype options
|
||||
$options += $protoOptions;
|
||||
|
||||
if (!isset($options['name'])) {
|
||||
$options['name'] = $this->factory->generateAssetName($inputs, $filters, $options);
|
||||
}
|
||||
|
||||
return array($options['name'] => array($inputs, $filters, $options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of prototypical calls and options.
|
||||
*
|
||||
* @return array Prototypes and options
|
||||
*/
|
||||
abstract protected function registerPrototypes();
|
||||
|
||||
/**
|
||||
* Returns setup code for the reflection scriptlet.
|
||||
*
|
||||
* @return string Some PHP setup code
|
||||
*/
|
||||
abstract protected function registerSetupCode();
|
||||
|
||||
static protected function tokenToString($token)
|
||||
{
|
||||
return is_array($token) ? $token[1] : $token;
|
||||
}
|
||||
|
||||
static protected function argumentToArray($argument)
|
||||
{
|
||||
return is_array($argument) ? $argument : array_filter(array_map('trim', explode(',', $argument)));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Factory\Loader;
|
||||
|
||||
use Assetic\Cache\ConfigCache;
|
||||
use Assetic\Factory\Resource\IteratorResourceInterface;
|
||||
use Assetic\Factory\Resource\ResourceInterface;
|
||||
|
||||
/**
|
||||
* Adds a caching layer to a loader.
|
||||
*
|
||||
* A cached formula loader is a composition of a formula loader and a cache.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class CachedFormulaLoader implements FormulaLoaderInterface
|
||||
{
|
||||
private $loader;
|
||||
private $configCache;
|
||||
private $debug;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* When the loader is in debug mode it will ensure the cached formulae
|
||||
* are fresh before returning them.
|
||||
*
|
||||
* @param FormulaLoaderInterface $loader A formula loader
|
||||
* @param ConfigCache $configCache A config cache
|
||||
* @param Boolean $debug The debug mode
|
||||
*/
|
||||
public function __construct(FormulaLoaderInterface $loader, ConfigCache $configCache, $debug = false)
|
||||
{
|
||||
$this->loader = $loader;
|
||||
$this->configCache = $configCache;
|
||||
$this->debug = $debug;
|
||||
}
|
||||
|
||||
public function load(ResourceInterface $resources)
|
||||
{
|
||||
if (!$resources instanceof IteratorResourceInterface) {
|
||||
$resources = array($resources);
|
||||
}
|
||||
|
||||
$formulae = array();
|
||||
|
||||
foreach ($resources as $resource) {
|
||||
$id = (string) $resource;
|
||||
if (!$this->configCache->has($id) || ($this->debug && !$resource->isFresh($this->configCache->getTimestamp($id)))) {
|
||||
$formulae += $this->loader->load($resource);
|
||||
$this->configCache->set($id, $formulae);
|
||||
} else {
|
||||
$formulae += $this->configCache->get($id);
|
||||
}
|
||||
}
|
||||
|
||||
return $formulae;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Factory\Loader;
|
||||
|
||||
use Assetic\Factory\Resource\ResourceInterface;
|
||||
|
||||
/**
|
||||
* Loads formulae.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
interface FormulaLoaderInterface
|
||||
{
|
||||
/**
|
||||
* Loads formulae from a resource.
|
||||
*
|
||||
* Formulae should be loaded the same regardless of the current debug
|
||||
* mode. Debug considerations should happen downstream.
|
||||
*
|
||||
* @param ResourceInterface $resource A resource
|
||||
*
|
||||
* @return array An array of formulae
|
||||
*/
|
||||
function load(ResourceInterface $resource);
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Factory\Loader;
|
||||
|
||||
/**
|
||||
* Loads asset formulae from PHP files.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class FunctionCallsFormulaLoader extends BasePhpFormulaLoader
|
||||
{
|
||||
protected function registerPrototypes()
|
||||
{
|
||||
return array(
|
||||
'assetic_javascripts(*)' => array('output' => 'js/*.js'),
|
||||
'assetic_stylesheets(*)' => array('output' => 'css/*.css'),
|
||||
'assetic_image(*)' => array('output' => 'images/*'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function registerSetupCode()
|
||||
{
|
||||
return <<<'EOF'
|
||||
function assetic_javascripts()
|
||||
{
|
||||
global $_call;
|
||||
$_call = func_get_args();
|
||||
}
|
||||
|
||||
function assetic_stylesheets()
|
||||
{
|
||||
global $_call;
|
||||
$_call = func_get_args();
|
||||
}
|
||||
|
||||
function assetic_image()
|
||||
{
|
||||
global $_call;
|
||||
$_call = func_get_args();
|
||||
}
|
||||
|
||||
EOF;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Factory\Resource;
|
||||
|
||||
/**
|
||||
* Coalesces multiple directories together into one merged resource.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class CoalescingDirectoryResource implements IteratorResourceInterface
|
||||
{
|
||||
private $directories;
|
||||
|
||||
public function __construct($directories)
|
||||
{
|
||||
$this->directories = array();
|
||||
|
||||
foreach ($directories as $directory) {
|
||||
$this->addDirectory($directory);
|
||||
}
|
||||
}
|
||||
|
||||
public function addDirectory(IteratorResourceInterface $directory)
|
||||
{
|
||||
$this->directories[] = $directory;
|
||||
}
|
||||
|
||||
public function isFresh($timestamp)
|
||||
{
|
||||
foreach ($this->getFileResources() as $file) {
|
||||
if (!$file->isFresh($timestamp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getContent()
|
||||
{
|
||||
$parts = array();
|
||||
foreach ($this->getFileResources() as $file) {
|
||||
$parts[] = $file->getContent();
|
||||
}
|
||||
|
||||
return implode("\n", $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string to uniquely identify the current resource.
|
||||
*
|
||||
* @return string An identifying string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$parts = array();
|
||||
foreach ($this->directories as $directory) {
|
||||
$parts[] = (string) $directory;
|
||||
}
|
||||
|
||||
return implode(',', $parts);
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->getFileResources());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the relative version of a filename.
|
||||
*
|
||||
* @param ResourceInterface $file The file
|
||||
* @param ResourceInterface $directory The directory
|
||||
*
|
||||
* @return string The name to compare with files from other directories
|
||||
*/
|
||||
protected function getRelativeName(ResourceInterface $file, ResourceInterface $directory)
|
||||
{
|
||||
return substr((string) $file, strlen((string) $directory));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the coalesce.
|
||||
*
|
||||
* @return array An array of file resources
|
||||
*/
|
||||
private function getFileResources()
|
||||
{
|
||||
$paths = array();
|
||||
|
||||
foreach ($this->directories as $directory) {
|
||||
foreach ($directory as $file) {
|
||||
$relative = $this->getRelativeName($file, $directory);
|
||||
|
||||
if (!isset($paths[$relative])) {
|
||||
$paths[$relative] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($paths);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Factory\Resource;
|
||||
|
||||
/**
|
||||
* A resource is something formulae can be loaded from.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class DirectoryResource implements IteratorResourceInterface
|
||||
{
|
||||
private $path;
|
||||
private $pattern;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $path A directory path
|
||||
* @param string $pattern A filename pattern
|
||||
*/
|
||||
public function __construct($path, $pattern = null)
|
||||
{
|
||||
if (DIRECTORY_SEPARATOR != substr($path, -1)) {
|
||||
$path .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
$this->path = $path;
|
||||
$this->pattern = $pattern;
|
||||
}
|
||||
|
||||
public function isFresh($timestamp)
|
||||
{
|
||||
if (!is_dir($this->path) || filemtime($this->path) > $timestamp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this as $resource) {
|
||||
if (!$resource->isFresh($timestamp)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the combined content of all inner resources.
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
$content = array();
|
||||
foreach ($this as $resource) {
|
||||
$content[] = $resource->getContent();
|
||||
}
|
||||
|
||||
return implode("\n", $content);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
return is_dir($this->path)
|
||||
? new DirectoryResourceIterator($this->getInnerIterator())
|
||||
: new \EmptyIterator();
|
||||
}
|
||||
|
||||
protected function getInnerIterator()
|
||||
{
|
||||
return new DirectoryResourceFilterIterator(new \RecursiveDirectoryIterator($this->path), $this->pattern);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An iterator that converts file objects into file resources.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
* @access private
|
||||
*/
|
||||
class DirectoryResourceIterator extends \RecursiveIteratorIterator
|
||||
{
|
||||
public function current()
|
||||
{
|
||||
return new FileResource(parent::current()->getPathname());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters files by a basename pattern.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
* @access private
|
||||
*/
|
||||
class DirectoryResourceFilterIterator extends \RecursiveFilterIterator
|
||||
{
|
||||
protected $pattern;
|
||||
|
||||
public function __construct(\RecursiveDirectoryIterator $iterator, $pattern = null)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
|
||||
$this->pattern = $pattern;
|
||||
}
|
||||
|
||||
public function accept()
|
||||
{
|
||||
$file = $this->current();
|
||||
$name = $file->getBasename();
|
||||
|
||||
if ($file->isDir()) {
|
||||
return '.' != $name[0];
|
||||
} else {
|
||||
return null === $this->pattern || 0 < preg_match($this->pattern, $name);
|
||||
}
|
||||
}
|
||||
|
||||
public function getChildren()
|
||||
{
|
||||
return new self(new \RecursiveDirectoryIterator($this->current()->getPathname()), $this->pattern);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Factory\Resource;
|
||||
|
||||
/**
|
||||
* A resource is something formulae can be loaded from.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class FileResource implements ResourceInterface
|
||||
{
|
||||
private $path;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $path The path to a file
|
||||
*/
|
||||
public function __construct($path)
|
||||
{
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
public function isFresh($timestamp)
|
||||
{
|
||||
return file_exists($this->path) && filemtime($this->path) <= $timestamp;
|
||||
}
|
||||
|
||||
public function getContent()
|
||||
{
|
||||
return file_exists($this->path) ? file_get_contents($this->path) : '';
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Factory\Resource;
|
||||
|
||||
/**
|
||||
* A resource is something formulae can be loaded from.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
interface IteratorResourceInterface extends ResourceInterface, \IteratorAggregate
|
||||
{
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Factory\Resource;
|
||||
|
||||
/**
|
||||
* A resource is something formulae can be loaded from.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
interface ResourceInterface
|
||||
{
|
||||
/**
|
||||
* Checks if a timestamp represents the latest resource.
|
||||
*
|
||||
* @param integer $timestamp A UNIX timestamp
|
||||
*
|
||||
* @return Boolean True if the timestamp is up to date
|
||||
*/
|
||||
function isFresh($timestamp);
|
||||
|
||||
/**
|
||||
* Returns the content of the resource.
|
||||
*
|
||||
* @return string The content
|
||||
*/
|
||||
function getContent();
|
||||
|
||||
/**
|
||||
* Returns a unique string for the current resource.
|
||||
*
|
||||
* @return string A unique string to identity the current resource
|
||||
*/
|
||||
function __toString();
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Factory\Worker;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* Applies a filter to an asset based on a source and/or target path match.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
* @todo A better asset-matcher mechanism
|
||||
*/
|
||||
class EnsureFilterWorker implements WorkerInterface
|
||||
{
|
||||
const CHECK_SOURCE = 1;
|
||||
const CHECK_TARGET = 2;
|
||||
|
||||
private $pattern;
|
||||
private $filter;
|
||||
private $flags;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $pattern A regex for checking the asset's target URL
|
||||
* @param FilterInterface $filter A filter to apply if the regex matches
|
||||
* @param integer $flags Flags for what to check
|
||||
*/
|
||||
public function __construct($pattern, FilterInterface $filter, $flags = null)
|
||||
{
|
||||
if (null === $flags) {
|
||||
$flags = self::CHECK_SOURCE | self::CHECK_TARGET;
|
||||
}
|
||||
|
||||
$this->pattern = $pattern;
|
||||
$this->filter = $filter;
|
||||
$this->flags = $flags;
|
||||
}
|
||||
|
||||
public function process(AssetInterface $asset)
|
||||
{
|
||||
if (
|
||||
(self::CHECK_SOURCE === (self::CHECK_SOURCE & $this->flags) && preg_match($this->pattern, $asset->getSourcePath()))
|
||||
||
|
||||
(self::CHECK_TARGET === (self::CHECK_TARGET & $this->flags) && preg_match($this->pattern, $asset->getTargetPath()))
|
||||
) {
|
||||
$asset->ensureFilter($this->filter);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Factory\Worker;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
|
||||
/**
|
||||
* Assets are passed through factory workers before leaving the factory.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
interface WorkerInterface
|
||||
{
|
||||
/**
|
||||
* Processes an asset.
|
||||
*
|
||||
* @param AssetInterface $asset An asset
|
||||
*
|
||||
* @return AssetInterface|null May optionally return a replacement asset
|
||||
*/
|
||||
function process(AssetInterface $asset);
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
/**
|
||||
* An abstract filter for dealing with CSS.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
abstract class BaseCssFilter implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Filters all references -- url() and "@import" -- through a callable.
|
||||
*
|
||||
* @param string $content The CSS
|
||||
* @param mixed $callback A PHP callable
|
||||
*
|
||||
* @return string The filtered CSS
|
||||
*/
|
||||
protected function filterReferences($content, $callback, $limit = -1, & $count = 0)
|
||||
{
|
||||
$content = $this->filterUrls($content, $callback, $limit, $count);
|
||||
$content = $this->filterImports($content, $callback, $limit, $count, false);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters all CSS url()'s through a callable.
|
||||
*
|
||||
* @param string $content The CSS
|
||||
* @param mixed $callback A PHP callable
|
||||
* @param integer $limit Limit the number of replacements
|
||||
* @param integer $count Will be populated with the count
|
||||
*
|
||||
* @return string The filtered CSS
|
||||
*/
|
||||
protected function filterUrls($content, $callback, $limit = -1, & $count = 0)
|
||||
{
|
||||
return preg_replace_callback('/url\((["\']?)(?<url>.*?)(\\1)\)/', $callback, $content, $limit, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters all CSS imports through a callable.
|
||||
*
|
||||
* @param string $content The CSS
|
||||
* @param mixed $callback A PHP callable
|
||||
* @param integer $limit Limit the number of replacements
|
||||
* @param integer $count Will be populated with the count
|
||||
* @param Boolean $includeUrl Whether to include url() in the pattern
|
||||
*
|
||||
* @return string The filtered CSS
|
||||
*/
|
||||
protected function filterImports($content, $callback, $limit = -1, & $count = 0, $includeUrl = true)
|
||||
{
|
||||
$pattern = $includeUrl
|
||||
? '/@import (?:url\()?(\'|"|)(?<url>[^\'"\)\n\r]*)\1\)?;?/'
|
||||
: '/@import (?!url\()(\'|"|)(?<url>[^\'"\)\n\r]*)\1;?/';
|
||||
|
||||
return preg_replace_callback($pattern, $callback, $content, $limit, $count);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
|
||||
/**
|
||||
* A filter that wraps callables.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class CallablesFilter implements FilterInterface
|
||||
{
|
||||
private $loader;
|
||||
private $dumper;
|
||||
|
||||
public function __construct($loader = null, $dumper = null)
|
||||
{
|
||||
$this->loader = $loader;
|
||||
$this->dumper = $dumper;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
if (null !== $callable = $this->loader) {
|
||||
$callable($asset);
|
||||
}
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
if (null !== $callable = $this->dumper) {
|
||||
$callable($asset);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Exception\FilterException;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
|
||||
/**
|
||||
* Compiles CoffeeScript into Javascript.
|
||||
*
|
||||
* @link http://coffeescript.org/
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class CoffeeScriptFilter implements FilterInterface
|
||||
{
|
||||
private $coffeePath;
|
||||
private $nodePath;
|
||||
|
||||
// coffee options
|
||||
private $bare;
|
||||
|
||||
public function __construct($coffeePath = '/usr/bin/coffee', $nodePath = '/usr/bin/node')
|
||||
{
|
||||
$this->coffeePath = $coffeePath;
|
||||
$this->nodePath = $nodePath;
|
||||
}
|
||||
|
||||
public function setBare($bare)
|
||||
{
|
||||
$this->bare = $bare;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
$input = tempnam(sys_get_temp_dir(), 'assetic_coffeescript');
|
||||
file_put_contents($input, $asset->getContent());
|
||||
|
||||
$pb = new ProcessBuilder(array(
|
||||
$this->nodePath,
|
||||
$this->coffeePath,
|
||||
'-cp',
|
||||
));
|
||||
|
||||
if ($this->bare) {
|
||||
$pb->add('--bare');
|
||||
}
|
||||
|
||||
$pb->add($input);
|
||||
$proc = $pb->getProcess();
|
||||
$code = $proc->run();
|
||||
unlink($input);
|
||||
|
||||
if (0 < $code) {
|
||||
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
|
||||
}
|
||||
|
||||
$asset->setContent($proc->getOutput());
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,329 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Exception\FilterException;
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Filter\FilterInterface;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
|
||||
/**
|
||||
* Loads Compass files.
|
||||
*
|
||||
* @link http://compass-style.org/
|
||||
* @author Maxime Thirouin <maxime.thirouin@gmail.com>
|
||||
*/
|
||||
class CompassFilter implements FilterInterface
|
||||
{
|
||||
private $compassPath;
|
||||
private $scss;
|
||||
|
||||
// sass options
|
||||
private $unixNewlines;
|
||||
private $debugInfo;
|
||||
private $cacheLocation;
|
||||
private $noCache;
|
||||
|
||||
// compass options
|
||||
private $force;
|
||||
private $style;
|
||||
private $quiet;
|
||||
private $boring;
|
||||
private $noLineComments;
|
||||
private $imagesDir;
|
||||
private $javascriptsDir;
|
||||
|
||||
// compass configuration file options
|
||||
private $plugins = array();
|
||||
private $loadPaths = array();
|
||||
private $httpPath;
|
||||
private $httpImagesPath;
|
||||
private $httpJavascriptsPath;
|
||||
|
||||
public function __construct($compassPath = '/usr/bin/compass')
|
||||
{
|
||||
$this->compassPath = $compassPath;
|
||||
$this->cacheLocation = sys_get_temp_dir();
|
||||
|
||||
if ('cli' !== php_sapi_name()) {
|
||||
$this->boring = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function setScss($scss)
|
||||
{
|
||||
$this->scss = $scss;
|
||||
}
|
||||
|
||||
// sass options setters
|
||||
public function setUnixNewlines($unixNewlines)
|
||||
{
|
||||
$this->unixNewlines = $unixNewlines;
|
||||
}
|
||||
|
||||
public function setDebugInfo($debugInfo)
|
||||
{
|
||||
$this->debugInfo = $debugInfo;
|
||||
}
|
||||
|
||||
public function setCacheLocation($cacheLocation)
|
||||
{
|
||||
$this->cacheLocation = $cacheLocation;
|
||||
}
|
||||
|
||||
public function setNoCache($noCache)
|
||||
{
|
||||
$this->noCache = $noCache;
|
||||
}
|
||||
|
||||
// compass options setters
|
||||
public function setForce($force)
|
||||
{
|
||||
$this->force = $force;
|
||||
}
|
||||
|
||||
public function setStyle($style)
|
||||
{
|
||||
$this->style = $style;
|
||||
}
|
||||
|
||||
public function setQuiet($quiet)
|
||||
{
|
||||
$this->quiet = $quiet;
|
||||
}
|
||||
|
||||
public function setBoring($boring)
|
||||
{
|
||||
$this->boring = $boring;
|
||||
}
|
||||
|
||||
public function setNoLineComments($noLineComments)
|
||||
{
|
||||
$this->noLineComments = $noLineComments;
|
||||
}
|
||||
|
||||
public function setImagesDir($imagesDir)
|
||||
{
|
||||
$this->imagesDir = $imagesDir;
|
||||
}
|
||||
|
||||
public function setJavascriptsDir($javascriptsDir)
|
||||
{
|
||||
$this->javascriptsDir = $javascriptsDir;
|
||||
}
|
||||
|
||||
// compass configuration file options setters
|
||||
public function setPlugins(array $plugins)
|
||||
{
|
||||
$this->plugins = $plugins;
|
||||
}
|
||||
|
||||
public function addPlugin($plugin)
|
||||
{
|
||||
$this->plugins[] = $plugin;
|
||||
}
|
||||
|
||||
public function addLoadPath($loadPath)
|
||||
{
|
||||
$this->loadPaths[] = $loadPath;
|
||||
}
|
||||
|
||||
public function setHttpPath($httpPath)
|
||||
{
|
||||
$this->httpPath = $httpPath;
|
||||
}
|
||||
|
||||
public function setHttpImagesPath($httpImagesPath)
|
||||
{
|
||||
$this->httpImagesPath = $httpImagesPath;
|
||||
}
|
||||
|
||||
public function setHttpJavascriptsPath($httpJavascriptsPath)
|
||||
{
|
||||
$this->httpJavascriptsPath = $httpJavascriptsPath;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
$root = $asset->getSourceRoot();
|
||||
$path = $asset->getSourcePath();
|
||||
|
||||
$loadPaths = $this->loadPaths;
|
||||
if ($root && $path) {
|
||||
$loadPaths[] = dirname($root.'/'.$path);
|
||||
}
|
||||
|
||||
// compass does not seems to handle symlink, so we use realpath()
|
||||
$tempDir = realpath(sys_get_temp_dir());
|
||||
|
||||
$pb = new ProcessBuilder(array(
|
||||
$this->compassPath,
|
||||
'compile',
|
||||
$tempDir,
|
||||
));
|
||||
$pb->inheritEnvironmentVariables();
|
||||
|
||||
if ($this->force) {
|
||||
$pb->add('--force');
|
||||
}
|
||||
|
||||
if ($this->style) {
|
||||
$pb->add('--output-style')->add($this->style);
|
||||
}
|
||||
|
||||
if ($this->quiet) {
|
||||
$pb->add('--quiet');
|
||||
}
|
||||
|
||||
if ($this->boring) {
|
||||
$pb->add('--boring');
|
||||
}
|
||||
|
||||
if ($this->noLineComments) {
|
||||
$pb->add('--no-line-comments');
|
||||
}
|
||||
|
||||
// these two options are not passed into the config file
|
||||
// because like this, compass adapts this to be xxx_dir or xxx_path
|
||||
// whether it's an absolute path or not
|
||||
if ($this->imagesDir) {
|
||||
$pb->add('--images-dir')->add($this->imagesDir);
|
||||
}
|
||||
|
||||
if ($this->javascriptsDir) {
|
||||
$pb->add('--javascripts-dir')->add($this->javascriptsDir);
|
||||
}
|
||||
|
||||
// options in config file
|
||||
$optionsConfig = array();
|
||||
|
||||
if (!empty($loadPaths)) {
|
||||
$optionsConfig['additional_import_paths'] = $loadPaths;
|
||||
}
|
||||
|
||||
if ($this->unixNewlines) {
|
||||
$optionsConfig['sass_options']['unix_newlines'] = true;
|
||||
}
|
||||
|
||||
if ($this->debugInfo) {
|
||||
$optionsConfig['sass_options']['debug_info'] = true;
|
||||
}
|
||||
|
||||
if ($this->cacheLocation) {
|
||||
$optionsConfig['sass_options']['cache_location'] = $this->cacheLocation;
|
||||
}
|
||||
|
||||
if ($this->noCache) {
|
||||
$optionsConfig['sass_options']['no_cache'] = true;
|
||||
}
|
||||
|
||||
if ($this->httpPath) {
|
||||
$optionsConfig['http_path'] = $this->httpPath;
|
||||
}
|
||||
|
||||
if ($this->httpImagesPath) {
|
||||
$optionsConfig['http_images_path'] = $this->httpImagesPath;
|
||||
}
|
||||
|
||||
if ($this->httpJavascriptsPath) {
|
||||
$optionsConfig['http_javascripts_path'] = $this->httpJavascriptsPath;
|
||||
}
|
||||
|
||||
// options in configuration file
|
||||
if (count($optionsConfig)) {
|
||||
$config = array();
|
||||
foreach ($this->plugins as $plugin) {
|
||||
$config[] = sprintf("require '%s'", addcslashes($plugin, '\\'));
|
||||
}
|
||||
foreach ($optionsConfig as $name => $value) {
|
||||
if (!is_array($value)) {
|
||||
$config[] = sprintf('%s = "%s"', $name, addcslashes($value, '\\'));
|
||||
} elseif (!empty($value)) {
|
||||
$config[] = sprintf('%s = %s', $name, $this->formatArrayToRuby($value));
|
||||
}
|
||||
}
|
||||
|
||||
$configFile = tempnam($tempDir, 'assetic_compass');
|
||||
file_put_contents($configFile, implode("\n", $config)."\n");
|
||||
$pb->add('--config')->add($configFile);
|
||||
}
|
||||
|
||||
$pb->add('--sass-dir')->add('')->add('--css-dir')->add('');
|
||||
|
||||
// compass choose the type (sass or scss from the filename)
|
||||
if (null !== $this->scss) {
|
||||
$type = $this->scss ? 'scss' : 'sass';
|
||||
} elseif ($path) {
|
||||
// FIXME: what if the extension is something else?
|
||||
$type = pathinfo($path, PATHINFO_EXTENSION);
|
||||
} else {
|
||||
$type = 'scss';
|
||||
}
|
||||
|
||||
$tempName = tempnam($tempDir, 'assetic_compass');
|
||||
unlink($tempName); // FIXME: don't use tempnam() here
|
||||
|
||||
// input
|
||||
$pb->add($input = $tempName.'.'.$type);
|
||||
file_put_contents($input, $asset->getContent());
|
||||
|
||||
// output
|
||||
$output = $tempName.'.css';
|
||||
|
||||
// it's not really usefull but... https://github.com/chriseppstein/compass/issues/376
|
||||
$pb->setEnv('HOME', sys_get_temp_dir());
|
||||
|
||||
$proc = $pb->getProcess();
|
||||
$code = $proc->run();
|
||||
|
||||
if (0 < $code) {
|
||||
unlink($input);
|
||||
if (isset($configFile)) {
|
||||
unlink($configFile);
|
||||
}
|
||||
|
||||
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
|
||||
}
|
||||
|
||||
$asset->setContent(file_get_contents($output));
|
||||
|
||||
unlink($input);
|
||||
unlink($output);
|
||||
if (isset($configFile)) {
|
||||
unlink($configFile);
|
||||
}
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
|
||||
private function formatArrayToRuby($array)
|
||||
{
|
||||
$output = array();
|
||||
|
||||
// does we have an associative array ?
|
||||
if (count(array_filter(array_keys($array), "is_numeric")) != count($array)) {
|
||||
foreach($array as $name => $value) {
|
||||
$output[] = sprintf(' :%s => "%s"', $name, addcslashes($value, '\\'));
|
||||
}
|
||||
$output = "{\n".implode(",\n", $output)."\n}";
|
||||
} else {
|
||||
foreach($array as $name => $value) {
|
||||
$output[] = sprintf(' "%s"', addcslashes($value, '\\'));
|
||||
}
|
||||
$output = "[\n".implode(",\n", $output)."\n]";
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Exception\FilterException;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
|
||||
/**
|
||||
* CSSEmbed filter
|
||||
*
|
||||
* @link https://github.com/nzakas/cssembed
|
||||
* @author Maxime Thirouin <maxime.thirouin@gmail.com>
|
||||
*/
|
||||
class CssEmbedFilter implements FilterInterface
|
||||
{
|
||||
private $jarPath;
|
||||
private $javaPath;
|
||||
private $charset;
|
||||
private $mhtml; // Enable MHTML mode.
|
||||
private $mhtmlRoot; // Use <root> as the MHTML root for the file.
|
||||
private $root; // Prepends <root> to all relative URLs.
|
||||
private $skipMissing; // Don't throw an error for missing image files.
|
||||
private $maxUriLength; // Maximum length for a data URI. Defaults to 32768.
|
||||
private $maxImageSize; // Maximum image size (in bytes) to convert.
|
||||
|
||||
public function __construct($jarPath, $javaPath = '/usr/bin/java')
|
||||
{
|
||||
$this->jarPath = $jarPath;
|
||||
$this->javaPath = $javaPath;
|
||||
}
|
||||
|
||||
public function setCharset($charset)
|
||||
{
|
||||
$this->charset = $charset;
|
||||
}
|
||||
|
||||
public function setMhtml($mhtml)
|
||||
{
|
||||
$this->mhtml = $mhtml;
|
||||
}
|
||||
|
||||
public function setMhtmlRoot($mhtmlRoot)
|
||||
{
|
||||
$this->mhtmlRoot = $mhtmlRoot;
|
||||
}
|
||||
|
||||
public function setRoot($root)
|
||||
{
|
||||
$this->root = $root;
|
||||
}
|
||||
|
||||
public function setSkipMissing($skipMissing)
|
||||
{
|
||||
$this->skipMissing = $skipMissing;
|
||||
}
|
||||
|
||||
public function setMaxUriLength($maxUriLength)
|
||||
{
|
||||
$this->maxUriLength = $maxUriLength;
|
||||
}
|
||||
|
||||
public function setMaxImageSize($maxImageSize)
|
||||
{
|
||||
$this->maxImageSize = $maxImageSize;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
$pb = new ProcessBuilder(array(
|
||||
$this->javaPath,
|
||||
'-jar',
|
||||
$this->jarPath,
|
||||
));
|
||||
|
||||
if (null !== $this->charset) {
|
||||
$pb->add('--charset')->add($this->charset);
|
||||
}
|
||||
|
||||
if ($this->mhtml) {
|
||||
$pb->add('--mhtml');
|
||||
}
|
||||
|
||||
if (null !== $this->mhtmlRoot) {
|
||||
$pb->add('--mhtmlroot')->add($this->mhtmlRoot);
|
||||
}
|
||||
|
||||
// automatically define root if not already defined
|
||||
if (null === $this->root) {
|
||||
$root = $asset->getSourceRoot();
|
||||
$path = $asset->getSourcePath();
|
||||
|
||||
if ($root && $path) {
|
||||
$pb->add('--root')->add(dirname($root.'/'.$path));
|
||||
}
|
||||
} else {
|
||||
$pb->add('--root')->add($this->root);
|
||||
}
|
||||
|
||||
if ($this->skipMissing) {
|
||||
$pb->add('--skip-missing');
|
||||
}
|
||||
|
||||
if (null !== $this->maxUriLength) {
|
||||
$pb->add('--max-uri-length')->add($this->maxUriLength);
|
||||
}
|
||||
|
||||
if (null !== $this->maxImageSize) {
|
||||
$pb->add('--max-image-size')->add($this->maxImageSize);
|
||||
}
|
||||
|
||||
// input
|
||||
$pb->add($input = tempnam(sys_get_temp_dir(), 'assetic_cssembed'));
|
||||
file_put_contents($input, $asset->getContent());
|
||||
|
||||
$proc = $pb->getProcess();
|
||||
$code = $proc->run();
|
||||
unlink($input);
|
||||
|
||||
if (0 < $code) {
|
||||
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
|
||||
}
|
||||
|
||||
$asset->setContent($proc->getOutput());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Asset\FileAsset;
|
||||
use Assetic\Asset\HttpAsset;
|
||||
|
||||
/**
|
||||
* Inlines imported stylesheets.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class CssImportFilter extends BaseCssFilter
|
||||
{
|
||||
private $importFilter;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param FilterInterface $importFilter Filter for each imported asset
|
||||
*/
|
||||
public function __construct(FilterInterface $importFilter = null)
|
||||
{
|
||||
$this->importFilter = $importFilter ?: new CssRewriteFilter();
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
$importFilter = $this->importFilter;
|
||||
$sourceRoot = $asset->getSourceRoot();
|
||||
$sourcePath = $asset->getSourcePath();
|
||||
|
||||
$callback = function($matches) use($importFilter, $sourceRoot, $sourcePath)
|
||||
{
|
||||
if (!$matches['url'] || null === $sourceRoot) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$importRoot = $sourceRoot;
|
||||
|
||||
if (false !== strpos($matches['url'], '://')) {
|
||||
// absolute
|
||||
list($importScheme, $tmp) = explode('://', $matches['url'], 2);
|
||||
list($importHost, $importPath) = explode('/', $tmp, 2);
|
||||
$importRoot = $importScheme.'://'.$importHost;
|
||||
} elseif (0 === strpos($matches['url'], '//')) {
|
||||
// protocol-relative
|
||||
list($importHost, $importPath) = explode('/', substr($matches['url'], 2), 2);
|
||||
$importHost = '//'.$importHost;
|
||||
} elseif ('/' == $matches['url'][0]) {
|
||||
// root-relative
|
||||
$importPath = substr($matches['url'], 1);
|
||||
} elseif (null !== $sourcePath) {
|
||||
// document-relative
|
||||
$importPath = $matches['url'];
|
||||
if ('.' != $sourceDir = dirname($sourcePath)) {
|
||||
$importPath = $sourceDir.'/'.$importPath;
|
||||
}
|
||||
} else {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
// ignore other imports
|
||||
if ('css' != pathinfo($importPath, PATHINFO_EXTENSION)) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$importSource = $importRoot.'/'.$importPath;
|
||||
if (false !== strpos($importSource, '://') || 0 === strpos($importSource, '//')) {
|
||||
$import = new HttpAsset($importSource, array($importFilter), true);
|
||||
} elseif (!file_exists($importSource)) {
|
||||
// ignore not found imports
|
||||
return $matches[0];
|
||||
} else {
|
||||
$import = new FileAsset($importSource, array($importFilter), $importRoot, $importPath);
|
||||
}
|
||||
|
||||
$import->setTargetPath($sourcePath);
|
||||
|
||||
return $import->dump();
|
||||
};
|
||||
|
||||
$content = $asset->getContent();
|
||||
$lastHash = md5($content);
|
||||
|
||||
do {
|
||||
$content = $this->filterImports($content, $callback);
|
||||
$hash = md5($content);
|
||||
} while ($lastHash != $hash && $lastHash = $hash);
|
||||
|
||||
$asset->setContent($content);
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
|
||||
/**
|
||||
* Filters assets through CssMin.
|
||||
*
|
||||
* @link http://code.google.com/p/cssmin
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class CssMinFilter implements FilterInterface
|
||||
{
|
||||
private $filters;
|
||||
private $plugins;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->filters = array();
|
||||
$this->plugins = array();
|
||||
}
|
||||
|
||||
public function setFilters(array $filters)
|
||||
{
|
||||
$this->filters = $filters;
|
||||
}
|
||||
|
||||
public function setFilter($name, $value)
|
||||
{
|
||||
$this->filters[$name] = $value;
|
||||
}
|
||||
|
||||
public function setPlugins(array $plugins)
|
||||
{
|
||||
$this->plugins = $plugins;
|
||||
}
|
||||
|
||||
public function setPlugin($name, $value)
|
||||
{
|
||||
$this->plugins[$name] = $value;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
$filters = $this->filters;
|
||||
$plugins = $this->plugins;
|
||||
|
||||
if (isset($filters['ImportImports']) && true === $filters['ImportImports']) {
|
||||
$root = $asset->getSourceRoot();
|
||||
$path = $asset->getSourcePath();
|
||||
if ($root && $path) {
|
||||
$filters['ImportImports'] = array('BasePath' => dirname($root.'/'.$path));
|
||||
} else {
|
||||
unset($filters['ImportImports']);
|
||||
}
|
||||
}
|
||||
|
||||
$asset->setContent(\CssMin::minify($asset->getContent(), $filters, $plugins));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
|
||||
/**
|
||||
* Fixes relative CSS urls.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class CssRewriteFilter extends BaseCssFilter
|
||||
{
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
$sourceBase = $asset->getSourceRoot();
|
||||
$sourcePath = $asset->getSourcePath();
|
||||
$targetPath = $asset->getTargetPath();
|
||||
|
||||
if (null === $sourcePath || null === $targetPath || $sourcePath == $targetPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
// learn how to get from the target back to the source
|
||||
if (false !== strpos($sourceBase, '://')) {
|
||||
list($scheme, $url) = explode('://', $sourceBase.'/'.$sourcePath, 2);
|
||||
list($host, $path) = explode('/', $url, 2);
|
||||
|
||||
$host = $scheme.'://'.$host.'/';
|
||||
$path = false === strpos($path, '/') ? '' : dirname($path);
|
||||
$path .= '/';
|
||||
} else {
|
||||
// assume source and target are on the same host
|
||||
$host = '';
|
||||
|
||||
// pop entries off the target until it fits in the source
|
||||
if ('.' == dirname($sourcePath)) {
|
||||
$path = str_repeat('../', substr_count($targetPath, '/'));
|
||||
} elseif ('.' == $targetDir = dirname($targetPath)) {
|
||||
$path = dirname($sourcePath).'/';
|
||||
} else {
|
||||
$path = '';
|
||||
while (0 !== strpos($sourcePath, $targetDir)) {
|
||||
if (false !== $pos = strrpos($targetDir, '/')) {
|
||||
$targetDir = substr($targetDir, 0, $pos);
|
||||
$path .= '../';
|
||||
} else {
|
||||
$targetDir = '';
|
||||
$path .= '../';
|
||||
break;
|
||||
}
|
||||
}
|
||||
$path .= ltrim(substr(dirname($sourcePath).'/', strlen($targetDir)), '/');
|
||||
}
|
||||
}
|
||||
|
||||
$content = $this->filterReferences($asset->getContent(), function($matches) use($host, $path)
|
||||
{
|
||||
if (false !== strpos($matches['url'], '://') || 0 === strpos($matches['url'], '//') || 0 === strpos($matches['url'], 'data:')) {
|
||||
// absolute or protocol-relative or data uri
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
if ('/' == $matches['url'][0]) {
|
||||
// root relative
|
||||
return str_replace($matches['url'], $host.$matches['url'], $matches[0]);
|
||||
}
|
||||
|
||||
// document relative
|
||||
$url = $matches['url'];
|
||||
while (0 === strpos($url, '../') && 2 <= substr_count($path, '/')) {
|
||||
$path = substr($path, 0, strrpos(rtrim($path, '/'), '/') + 1);
|
||||
$url = substr($url, 3);
|
||||
}
|
||||
|
||||
return str_replace($matches['url'], $host.$path.$url, $matches[0]);
|
||||
});
|
||||
|
||||
$asset->setContent($content);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
|
||||
/**
|
||||
* A collection of filters.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class FilterCollection implements FilterInterface, \IteratorAggregate, \Countable
|
||||
{
|
||||
private $filters = array();
|
||||
|
||||
public function __construct($filters = array())
|
||||
{
|
||||
foreach ($filters as $filter) {
|
||||
$this->ensure($filter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the current collection contains the supplied filter.
|
||||
*
|
||||
* If the supplied filter is another filter collection, each of its
|
||||
* filters will be checked.
|
||||
*/
|
||||
public function ensure(FilterInterface $filter)
|
||||
{
|
||||
if ($filter instanceof \Traversable) {
|
||||
foreach ($filter as $f) {
|
||||
$this->ensure($f);
|
||||
}
|
||||
} elseif (!in_array($filter, $this->filters, true)) {
|
||||
$this->filters[] = $filter;
|
||||
}
|
||||
}
|
||||
|
||||
public function all()
|
||||
{
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
public function clear()
|
||||
{
|
||||
$this->filters = array();
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
foreach ($this->filters as $filter) {
|
||||
$filter->filterLoad($asset);
|
||||
}
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
foreach ($this->filters as $filter) {
|
||||
$filter->filterDump($asset);
|
||||
}
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->filters);
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
return count($this->filters);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
|
||||
/**
|
||||
* A filter manipulates an asset at load and dump.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
interface FilterInterface
|
||||
{
|
||||
/**
|
||||
* Filters an asset after it has been loaded.
|
||||
*
|
||||
* @param AssetInterface $asset An asset
|
||||
*/
|
||||
function filterLoad(AssetInterface $asset);
|
||||
|
||||
/**
|
||||
* Filters an asset just before it's dumped.
|
||||
*
|
||||
* @param AssetInterface $asset An asset
|
||||
*/
|
||||
function filterDump(AssetInterface $asset);
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter\GoogleClosure;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* Base filter for the Google Closure Compiler implementations.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
abstract class BaseCompilerFilter implements FilterInterface
|
||||
{
|
||||
// compilation levels
|
||||
const COMPILE_WHITESPACE_ONLY = 'WHITESPACE_ONLY';
|
||||
const COMPILE_SIMPLE_OPTIMIZATIONS = 'SIMPLE_OPTIMIZATIONS';
|
||||
const COMPILE_ADVANCED_OPTIMIZATIONS = 'ADVANCED_OPTIMIZATIONS';
|
||||
|
||||
// formatting modes
|
||||
const FORMAT_PRETTY_PRINT = 'pretty_print';
|
||||
const FORMAT_PRINT_INPUT_DELIMITER = 'print_input_delimiter';
|
||||
|
||||
// warning levels
|
||||
const LEVEL_QUIET = 'QUIET';
|
||||
const LEVEL_DEFAULT = 'DEFAULT';
|
||||
const LEVEL_VERBOSE = 'VERBOSE';
|
||||
|
||||
protected $compilationLevel;
|
||||
protected $jsExterns;
|
||||
protected $externsUrl;
|
||||
protected $excludeDefaultExterns;
|
||||
protected $formatting;
|
||||
protected $useClosureLibrary;
|
||||
protected $warningLevel;
|
||||
|
||||
public function setCompilationLevel($compilationLevel)
|
||||
{
|
||||
$this->compilationLevel = $compilationLevel;
|
||||
}
|
||||
|
||||
public function setJsExterns($jsExterns)
|
||||
{
|
||||
$this->jsExterns = $jsExterns;
|
||||
}
|
||||
|
||||
public function setExternsUrl($externsUrl)
|
||||
{
|
||||
$this->externsUrl = $externsUrl;
|
||||
}
|
||||
|
||||
public function setExcludeDefaultExterns($excludeDefaultExterns)
|
||||
{
|
||||
$this->excludeDefaultExterns = $excludeDefaultExterns;
|
||||
}
|
||||
|
||||
public function setFormatting($formatting)
|
||||
{
|
||||
$this->formatting = $formatting;
|
||||
}
|
||||
|
||||
public function setUseClosureLibrary($useClosureLibrary)
|
||||
{
|
||||
$this->useClosureLibrary = $useClosureLibrary;
|
||||
}
|
||||
|
||||
public function setWarningLevel($warningLevel)
|
||||
{
|
||||
$this->warningLevel = $warningLevel;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter\GoogleClosure;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
|
||||
/**
|
||||
* Filter for the Google Closure Compiler API.
|
||||
*
|
||||
* @link https://developers.google.com/closure/compiler/
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class CompilerApiFilter extends BaseCompilerFilter
|
||||
{
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
$query = array(
|
||||
'js_code' => $asset->getContent(),
|
||||
'output_format' => 'json',
|
||||
'output_info' => 'compiled_code',
|
||||
);
|
||||
|
||||
if (null !== $this->compilationLevel) {
|
||||
$query['compilation_level'] = $this->compilationLevel;
|
||||
}
|
||||
|
||||
if (null !== $this->jsExterns) {
|
||||
$query['js_externs'] = $this->jsExterns;
|
||||
}
|
||||
|
||||
if (null !== $this->externsUrl) {
|
||||
$query['externs_url'] = $this->externsUrl;
|
||||
}
|
||||
|
||||
if (null !== $this->excludeDefaultExterns) {
|
||||
$query['exclude_default_externs'] = $this->excludeDefaultExterns ? 'true' : 'false';
|
||||
}
|
||||
|
||||
if (null !== $this->formatting) {
|
||||
$query['formatting'] = $this->formatting;
|
||||
}
|
||||
|
||||
if (null !== $this->useClosureLibrary) {
|
||||
$query['use_closure_library'] = $this->useClosureLibrary ? 'true' : 'false';
|
||||
}
|
||||
|
||||
if (null !== $this->warningLevel) {
|
||||
$query['warning_level'] = $this->warningLevel;
|
||||
}
|
||||
|
||||
$context = stream_context_create(array('http' => array(
|
||||
'method' => 'POST',
|
||||
'header' => 'Content-Type: application/x-www-form-urlencoded',
|
||||
'content' => http_build_query($query),
|
||||
)));
|
||||
|
||||
$response = file_get_contents('http://closure-compiler.appspot.com/compile', false, $context);
|
||||
$data = json_decode($response);
|
||||
|
||||
if (isset($data->serverErrors) && 0 < count($data->serverErrors)) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new \RuntimeException(sprintf('The Google Closure Compiler API threw some server errors: '.print_r($data->serverErrors, true)));
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
if (isset($data->errors) && 0 < count($data->errors)) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new \RuntimeException(sprintf('The Google Closure Compiler API threw some errors: '.print_r($data->errors, true)));
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$asset->setContent($data->compiledCode);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter\GoogleClosure;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Exception\FilterException;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
|
||||
/**
|
||||
* Filter for the Google Closure Compiler JAR.
|
||||
*
|
||||
* @link https://developers.google.com/closure/compiler/
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class CompilerJarFilter extends BaseCompilerFilter
|
||||
{
|
||||
private $jarPath;
|
||||
private $javaPath;
|
||||
|
||||
public function __construct($jarPath, $javaPath = '/usr/bin/java')
|
||||
{
|
||||
$this->jarPath = $jarPath;
|
||||
$this->javaPath = $javaPath;
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
$cleanup = array();
|
||||
|
||||
$pb = new ProcessBuilder(array(
|
||||
$this->javaPath,
|
||||
'-jar',
|
||||
$this->jarPath,
|
||||
));
|
||||
|
||||
if (null !== $this->compilationLevel) {
|
||||
$pb->add('--compilation_level')->add($this->compilationLevel);
|
||||
}
|
||||
|
||||
if (null !== $this->jsExterns) {
|
||||
$cleanup[] = $externs = tempnam(sys_get_temp_dir(), 'assetic_google_closure_compiler');
|
||||
file_put_contents($externs, $this->jsExterns);
|
||||
$pb->add('--externs')->add($externs);
|
||||
}
|
||||
|
||||
if (null !== $this->externsUrl) {
|
||||
$cleanup[] = $externs = tempnam(sys_get_temp_dir(), 'assetic_google_closure_compiler');
|
||||
file_put_contents($externs, file_get_contents($this->externsUrl));
|
||||
$pb->add('--externs')->add($externs);
|
||||
}
|
||||
|
||||
if (null !== $this->excludeDefaultExterns) {
|
||||
$pb->add('--use_only_custom_externs');
|
||||
}
|
||||
|
||||
if (null !== $this->formatting) {
|
||||
$pb->add('--formatting')->add($this->formatting);
|
||||
}
|
||||
|
||||
if (null !== $this->useClosureLibrary) {
|
||||
$pb->add('--manage_closure_dependencies');
|
||||
}
|
||||
|
||||
if (null !== $this->warningLevel) {
|
||||
$pb->add('--warning_level')->add($this->warningLevel);
|
||||
}
|
||||
|
||||
$pb->add('--js')->add($cleanup[] = $input = tempnam(sys_get_temp_dir(), 'assetic_google_closure_compiler'));
|
||||
file_put_contents($input, $asset->getContent());
|
||||
|
||||
$proc = $pb->getProcess();
|
||||
$code = $proc->run();
|
||||
array_map('unlink', $cleanup);
|
||||
|
||||
if (0 < $code) {
|
||||
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
|
||||
}
|
||||
|
||||
$asset->setContent($proc->getOutput());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Exception\FilterException;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
|
||||
/**
|
||||
* Filter for the Google Closure Stylesheets Compiler JAR.
|
||||
*
|
||||
* @link http://code.google.com/p/closure-stylesheets/
|
||||
* @author Matthias Krauser <matthias@krauser.eu>
|
||||
*/
|
||||
class GssFilter implements FilterInterface
|
||||
{
|
||||
private $jarPath;
|
||||
private $javaPath;
|
||||
private $allowUnrecognizedFunctions;
|
||||
private $allowedNonStandardFunctions;
|
||||
private $copyrightNotice;
|
||||
private $define;
|
||||
private $gssFunctionMapProvider;
|
||||
private $inputOrientation;
|
||||
private $outputOrientation;
|
||||
private $prettyPrint;
|
||||
|
||||
public function __construct($jarPath, $javaPath = '/usr/bin/java')
|
||||
{
|
||||
$this->jarPath = $jarPath;
|
||||
$this->javaPath = $javaPath;
|
||||
}
|
||||
|
||||
public function setAllowUnrecognizedFunctions($allowUnrecognizedFunctions)
|
||||
{
|
||||
$this->allowUnrecognizedFunctions = $allowUnrecognizedFunctions;
|
||||
}
|
||||
|
||||
public function setAllowedNonStandardFunctions($allowNonStandardFunctions)
|
||||
{
|
||||
$this->allowedNonStandardFunctions = $allowNonStandardFunctions;
|
||||
}
|
||||
|
||||
public function setCopyrightNotice($copyrightNotice)
|
||||
{
|
||||
$this->copyrightNotice = $copyrightNotice;
|
||||
}
|
||||
|
||||
public function setDefine($define)
|
||||
{
|
||||
$this->define = $define;
|
||||
}
|
||||
|
||||
public function setGssFunctionMapProvider($gssFunctionMapProvider)
|
||||
{
|
||||
$this->gssFunctionMapProvider = $gssFunctionMapProvider;
|
||||
}
|
||||
|
||||
public function setInputOrientation($inputOrientation)
|
||||
{
|
||||
$this->inputOrientation = $inputOrientation;
|
||||
}
|
||||
|
||||
public function setOutputOrientation($outputOrientation)
|
||||
{
|
||||
$this->outputOrientation = $outputOrientation;
|
||||
}
|
||||
|
||||
public function setPrettyPrint($prettyPrint)
|
||||
{
|
||||
$this->prettyPrint = $prettyPrint;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
$cleanup = array();
|
||||
|
||||
$pb = new ProcessBuilder(array(
|
||||
$this->javaPath,
|
||||
'-jar',
|
||||
$this->jarPath,
|
||||
));
|
||||
|
||||
if (null !== $this->allowUnrecognizedFunctions) {
|
||||
$pb->add('--allow-unrecognized-functions');
|
||||
}
|
||||
|
||||
if (null !== $this->allowedNonStandardFunctions) {
|
||||
$pb->add('--allowed_non_standard_functions')->add($this->allowedNonStandardFunctions);
|
||||
}
|
||||
|
||||
if (null !== $this->copyrightNotice) {
|
||||
$pb->add('--copyright-notice')->add($this->copyrightNotice);
|
||||
}
|
||||
|
||||
if (null !== $this->define) {
|
||||
$pb->add('--define')->add($this->define);
|
||||
}
|
||||
|
||||
if (null !== $this->gssFunctionMapProvider) {
|
||||
$pb->add('--gss-function-map-provider')->add($this->gssFunctionMapProvider);
|
||||
}
|
||||
|
||||
if (null !== $this->inputOrientation) {
|
||||
$pb->add('--input-orientation')->add($this->inputOrientation);
|
||||
}
|
||||
|
||||
if (null !== $this->outputOrientation) {
|
||||
$pb->add('--output-orientation')->add($this->outputOrientation);
|
||||
}
|
||||
|
||||
if (null !== $this->prettyPrint) {
|
||||
$pb->add('--pretty-print');
|
||||
}
|
||||
|
||||
$pb->add($cleanup[] = $input = tempnam(sys_get_temp_dir(), 'assetic_google_closure_stylesheets_compiler'));
|
||||
file_put_contents($input, $asset->getContent());
|
||||
|
||||
$proc = $pb->getProcess();
|
||||
$code = $proc->run();
|
||||
array_map('unlink', $cleanup);
|
||||
|
||||
if (0 < $code) {
|
||||
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
|
||||
}
|
||||
|
||||
$asset->setContent($proc->getOutput());
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Exception\FilterException;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
|
||||
/**
|
||||
* Runs assets through Jpegoptim.
|
||||
*
|
||||
* @link http://www.kokkonen.net/tjko/projects.html
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class JpegoptimFilter implements FilterInterface
|
||||
{
|
||||
private $jpegoptimBin;
|
||||
private $stripAll;
|
||||
private $max;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $jpegoptimBin Path to the jpegoptim binary
|
||||
*/
|
||||
public function __construct($jpegoptimBin = '/usr/bin/jpegoptim')
|
||||
{
|
||||
$this->jpegoptimBin = $jpegoptimBin;
|
||||
}
|
||||
|
||||
public function setStripAll($stripAll)
|
||||
{
|
||||
$this->stripAll = $stripAll;
|
||||
}
|
||||
|
||||
public function setMax($max)
|
||||
{
|
||||
$this->max = $max;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
$pb = new ProcessBuilder(array($this->jpegoptimBin));
|
||||
|
||||
if ($this->stripAll) {
|
||||
$pb->add('--strip-all');
|
||||
}
|
||||
|
||||
if ($this->max) {
|
||||
$pb->add('--max='.$this->max);
|
||||
}
|
||||
|
||||
$pb->add($input = tempnam(sys_get_temp_dir(), 'assetic_jpegoptim'));
|
||||
file_put_contents($input, $asset->getContent());
|
||||
|
||||
$proc = $pb->getProcess();
|
||||
$proc->run();
|
||||
|
||||
if (false !== strpos($proc->getOutput(), 'ERROR')) {
|
||||
unlink($input);
|
||||
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
|
||||
}
|
||||
|
||||
$asset->setContent(file_get_contents($input));
|
||||
|
||||
unlink($input);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Exception\FilterException;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
|
||||
/**
|
||||
* Runs assets through jpegtran.
|
||||
*
|
||||
* @link http://jpegclub.org/jpegtran/
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class JpegtranFilter implements FilterInterface
|
||||
{
|
||||
const COPY_NONE = 'none';
|
||||
const COPY_COMMENTS = 'comments';
|
||||
const COPY_ALL = 'all';
|
||||
|
||||
private $jpegtranBin;
|
||||
private $optimize;
|
||||
private $copy;
|
||||
private $progressive;
|
||||
private $restart;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $jpegtranBin Path to the jpegtran binary
|
||||
*/
|
||||
public function __construct($jpegtranBin = '/usr/bin/jpegtran')
|
||||
{
|
||||
$this->jpegtranBin = $jpegtranBin;
|
||||
}
|
||||
|
||||
public function setOptimize($optimize)
|
||||
{
|
||||
$this->optimize = $optimize;
|
||||
}
|
||||
|
||||
public function setCopy($copy)
|
||||
{
|
||||
$this->copy = $copy;
|
||||
}
|
||||
|
||||
public function setProgressive($progressive)
|
||||
{
|
||||
$this->progressive = $progressive;
|
||||
}
|
||||
|
||||
public function setRestart($restart)
|
||||
{
|
||||
$this->restart = $restart;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
$pb = new ProcessBuilder(array($this->jpegtranBin));
|
||||
|
||||
if ($this->optimize) {
|
||||
$pb->add('-optimize');
|
||||
}
|
||||
|
||||
if ($this->copy) {
|
||||
$pb->add('-copy')->add($this->copy);
|
||||
}
|
||||
|
||||
if ($this->progressive) {
|
||||
$pb->add('-progressive');
|
||||
}
|
||||
|
||||
if (null !== $this->restart) {
|
||||
$pb->add('-restart')->add($this->restart);
|
||||
}
|
||||
|
||||
$pb->add($input = tempnam(sys_get_temp_dir(), 'assetic_jpegtran'));
|
||||
file_put_contents($input, $asset->getContent());
|
||||
|
||||
$proc = $pb->getProcess();
|
||||
$code = $proc->run();
|
||||
unlink($input);
|
||||
|
||||
if (0 < $code) {
|
||||
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
|
||||
}
|
||||
|
||||
$asset->setContent($proc->getOutput());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Exception\FilterException;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
|
||||
/**
|
||||
* Loads LESS files.
|
||||
*
|
||||
* @link http://lesscss.org/
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class LessFilter implements FilterInterface
|
||||
{
|
||||
private $nodeBin;
|
||||
private $nodePaths;
|
||||
private $compress;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $nodeBin The path to the node binary
|
||||
* @param array $nodePaths An array of node paths
|
||||
*/
|
||||
public function __construct($nodeBin = '/usr/bin/node', array $nodePaths = array())
|
||||
{
|
||||
$this->nodeBin = $nodeBin;
|
||||
$this->nodePaths = $nodePaths;
|
||||
}
|
||||
|
||||
public function setCompress($compress)
|
||||
{
|
||||
$this->compress = $compress;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
static $format = <<<'EOF'
|
||||
var less = require('less');
|
||||
var sys = require(process.binding('natives').util ? 'util' : 'sys');
|
||||
|
||||
new(less.Parser)(%s).parse(%s, function(e, tree) {
|
||||
if (e) {
|
||||
less.writeError(e);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
try {
|
||||
sys.print(tree.toCSS(%s));
|
||||
} catch (e) {
|
||||
less.writeError(e);
|
||||
process.exit(3);
|
||||
}
|
||||
});
|
||||
|
||||
EOF;
|
||||
|
||||
$root = $asset->getSourceRoot();
|
||||
$path = $asset->getSourcePath();
|
||||
|
||||
// parser options
|
||||
$parserOptions = array();
|
||||
if ($root && $path) {
|
||||
$parserOptions['paths'] = array(dirname($root.'/'.$path));
|
||||
$parserOptions['filename'] = basename($path);
|
||||
}
|
||||
|
||||
// tree options
|
||||
$treeOptions = array();
|
||||
if (null !== $this->compress) {
|
||||
$treeOptions['compress'] = $this->compress;
|
||||
}
|
||||
|
||||
$pb = new ProcessBuilder();
|
||||
$pb->inheritEnvironmentVariables();
|
||||
|
||||
// node.js configuration
|
||||
if (0 < count($this->nodePaths)) {
|
||||
$pb->setEnv('NODE_PATH', implode(':', $this->nodePaths));
|
||||
}
|
||||
|
||||
$pb->add($this->nodeBin)->add($input = tempnam(sys_get_temp_dir(), 'assetic_less'));
|
||||
file_put_contents($input, sprintf($format,
|
||||
json_encode($parserOptions),
|
||||
json_encode($asset->getContent()),
|
||||
json_encode($treeOptions)
|
||||
));
|
||||
|
||||
$proc = $pb->getProcess();
|
||||
$code = $proc->run();
|
||||
unlink($input);
|
||||
|
||||
if (0 < $code) {
|
||||
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
|
||||
}
|
||||
|
||||
$asset->setContent($proc->getOutput());
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
|
||||
/**
|
||||
* Loads LESS files using the PHP implementation of less, lessphp.
|
||||
*
|
||||
* Less files are mostly compatible, but there are slight differences.
|
||||
*
|
||||
* To use this, you need to clone https://github.com/leafo/lessphp and make
|
||||
* sure to either include lessphp.inc.php or tell your autoloader that's where
|
||||
* lessc is located.
|
||||
*
|
||||
* @link http://leafo.net/lessphp/
|
||||
*
|
||||
* @author David Buchmann <david@liip.ch>
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class LessphpFilter implements FilterInterface
|
||||
{
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
$root = $asset->getSourceRoot();
|
||||
$path = $asset->getSourcePath();
|
||||
|
||||
$lc = new \lessc();
|
||||
if ($root && $path) {
|
||||
$lc->importDir = dirname($root.'/'.$path);
|
||||
}
|
||||
|
||||
$asset->setContent($lc->parse($asset->getContent()));
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Exception\FilterException;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
|
||||
/**
|
||||
* Runs assets through OptiPNG.
|
||||
*
|
||||
* @link http://optipng.sourceforge.net/
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class OptiPngFilter implements FilterInterface
|
||||
{
|
||||
private $optipngBin;
|
||||
private $level;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $optipngBin Path to the optipng binary
|
||||
*/
|
||||
public function __construct($optipngBin = '/usr/bin/optipng')
|
||||
{
|
||||
$this->optipngBin = $optipngBin;
|
||||
}
|
||||
|
||||
public function setLevel($level)
|
||||
{
|
||||
$this->level = $level;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
$pb = new ProcessBuilder(array($this->optipngBin));
|
||||
|
||||
if (null !== $this->level) {
|
||||
$pb->add('-o')->add($this->level);
|
||||
}
|
||||
|
||||
$pb->add('-out')->add($output = tempnam(sys_get_temp_dir(), 'assetic_optipng'));
|
||||
unlink($output);
|
||||
|
||||
$pb->add($input = tempnam(sys_get_temp_dir(), 'assetic_optipng'));
|
||||
file_put_contents($input, $asset->getContent());
|
||||
|
||||
$proc = $pb->getProcess();
|
||||
$code = $proc->run();
|
||||
|
||||
if (0 < $code) {
|
||||
unlink($input);
|
||||
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
|
||||
}
|
||||
|
||||
$asset->setContent(file_get_contents($output));
|
||||
|
||||
unlink($input);
|
||||
unlink($output);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
|
||||
/**
|
||||
* Runs assets through Packager.
|
||||
*
|
||||
* @link https://github.com/kamicane/packager
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class PackagerFilter implements FilterInterface
|
||||
{
|
||||
private $packages;
|
||||
|
||||
public function __construct(array $packages = array())
|
||||
{
|
||||
$this->packages = $packages;
|
||||
}
|
||||
|
||||
public function addPackage($package)
|
||||
{
|
||||
$this->packages[] = $package;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
static $manifest = <<<EOF
|
||||
name: Application%s
|
||||
sources: [source.js]
|
||||
|
||||
EOF;
|
||||
|
||||
$hash = substr(sha1(time().rand(11111, 99999)), 0, 7);
|
||||
$package = sys_get_temp_dir().'/assetic_packager_'.$hash;
|
||||
|
||||
mkdir($package);
|
||||
file_put_contents($package.'/package.yml', sprintf($manifest, $hash));
|
||||
file_put_contents($package.'/source.js', $asset->getContent());
|
||||
|
||||
$packager = new \Packager(array_merge(array($package), $this->packages));
|
||||
$content = $packager->build(array(), array(), array('Application'.$hash));
|
||||
|
||||
unlink($package.'/package.yml');
|
||||
unlink($package.'/source.js');
|
||||
rmdir($package);
|
||||
|
||||
$asset->setContent($content);
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Exception\FilterException;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
|
||||
/**
|
||||
* Runs assets through pngout.
|
||||
*
|
||||
* @link http://advsys.net/ken/utils.htm#pngout
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class PngoutFilter implements FilterInterface
|
||||
{
|
||||
// -c#
|
||||
const COLOR_GREY = '0';
|
||||
const COLOR_RGB = '2';
|
||||
const COLOR_PAL = '3';
|
||||
const COLOR_GRAY_ALPHA = '4';
|
||||
const COLOR_RGB_ALPHA = '6';
|
||||
|
||||
// -f#
|
||||
const FILTER_NONE = '0';
|
||||
const FILTER_X = '1';
|
||||
const FILTER_Y = '2';
|
||||
const FILTER_X_Y = '3';
|
||||
const FILTER_PAETH = '4';
|
||||
const FILTER_MIXED = '5';
|
||||
|
||||
// -s#
|
||||
const STRATEGY_XTREME = '0';
|
||||
const STRATEGY_INTENSE = '1';
|
||||
const STRATEGY_LONGEST_MATCH = '2';
|
||||
const STRATEGY_HUFFMAN_ONLY = '3';
|
||||
const STRATEGY_UNCOMPRESSED = '4';
|
||||
|
||||
private $pngoutBin;
|
||||
private $color;
|
||||
private $filter;
|
||||
private $strategy;
|
||||
private $blockSplitThreshold;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $pngoutBin Path to the pngout binary
|
||||
*/
|
||||
public function __construct($pngoutBin = '/usr/bin/pngout')
|
||||
{
|
||||
$this->pngoutBin = $pngoutBin;
|
||||
}
|
||||
|
||||
public function setColor($color)
|
||||
{
|
||||
$this->color = $color;
|
||||
}
|
||||
|
||||
public function setFilter($filter)
|
||||
{
|
||||
$this->filter = $filter;
|
||||
}
|
||||
|
||||
public function setStrategy($strategy)
|
||||
{
|
||||
$this->strategy = $strategy;
|
||||
}
|
||||
|
||||
public function setBlockSplitThreshold($blockSplitThreshold)
|
||||
{
|
||||
$this->blockSplitThreshold = $blockSplitThreshold;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
$pb = new ProcessBuilder(array($this->pngoutBin));
|
||||
|
||||
if (null !== $this->color) {
|
||||
$pb->add('-c'.$this->color);
|
||||
}
|
||||
|
||||
if (null !== $this->filter) {
|
||||
$pb->add('-f'.$this->filter);
|
||||
}
|
||||
|
||||
if (null !== $this->strategy) {
|
||||
$pb->add('-s'.$this->strategy);
|
||||
}
|
||||
|
||||
if (null !== $this->blockSplitThreshold) {
|
||||
$pb->add('-b'.$this->blockSplitThreshold);
|
||||
}
|
||||
|
||||
$pb->add($input = tempnam(sys_get_temp_dir(), 'assetic_pngout'));
|
||||
file_put_contents($input, $asset->getContent());
|
||||
|
||||
$output = tempnam(sys_get_temp_dir(), 'assetic_pngout');
|
||||
unlink($output);
|
||||
$pb->add($output .= '.png');
|
||||
|
||||
$proc = $pb->getProcess();
|
||||
$code = $proc->run();
|
||||
|
||||
if (0 < $code) {
|
||||
unlink($input);
|
||||
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
|
||||
}
|
||||
|
||||
$asset->setContent(file_get_contents($output));
|
||||
|
||||
unlink($input);
|
||||
unlink($output);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter\Sass;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Filter\FilterInterface;
|
||||
use Assetic\Exception\FilterException;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
|
||||
/**
|
||||
* Loads SASS files.
|
||||
*
|
||||
* @link http://sass-lang.com/
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class SassFilter implements FilterInterface
|
||||
{
|
||||
const STYLE_NESTED = 'nested';
|
||||
const STYLE_EXPANDED = 'expanded';
|
||||
const STYLE_COMPACT = 'compact';
|
||||
const STYLE_COMPRESSED = 'compressed';
|
||||
|
||||
private $sassPath;
|
||||
private $unixNewlines;
|
||||
private $scss;
|
||||
private $style;
|
||||
private $quiet;
|
||||
private $debugInfo;
|
||||
private $lineNumbers;
|
||||
private $loadPaths = array();
|
||||
private $cacheLocation;
|
||||
private $noCache;
|
||||
private $compass;
|
||||
|
||||
public function __construct($sassPath = '/usr/bin/sass')
|
||||
{
|
||||
$this->sassPath = $sassPath;
|
||||
$this->cacheLocation = realpath(sys_get_temp_dir());
|
||||
}
|
||||
|
||||
public function setUnixNewlines($unixNewlines)
|
||||
{
|
||||
$this->unixNewlines = $unixNewlines;
|
||||
}
|
||||
|
||||
public function setScss($scss)
|
||||
{
|
||||
$this->scss = $scss;
|
||||
}
|
||||
|
||||
public function setStyle($style)
|
||||
{
|
||||
$this->style = $style;
|
||||
}
|
||||
|
||||
public function setQuiet($quiet)
|
||||
{
|
||||
$this->quiet = $quiet;
|
||||
}
|
||||
|
||||
public function setDebugInfo($debugInfo)
|
||||
{
|
||||
$this->debugInfo = $debugInfo;
|
||||
}
|
||||
|
||||
public function setLineNumbers($lineNumbers)
|
||||
{
|
||||
$this->lineNumbers = $lineNumbers;
|
||||
}
|
||||
|
||||
public function addLoadPath($loadPath)
|
||||
{
|
||||
$this->loadPaths[] = $loadPath;
|
||||
}
|
||||
|
||||
public function setCacheLocation($cacheLocation)
|
||||
{
|
||||
$this->cacheLocation = $cacheLocation;
|
||||
}
|
||||
|
||||
public function setNoCache($noCache)
|
||||
{
|
||||
$this->noCache = $noCache;
|
||||
}
|
||||
|
||||
public function setCompass($compass)
|
||||
{
|
||||
$this->compass = $compass;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
$pb = new ProcessBuilder(array($this->sassPath));
|
||||
|
||||
$root = $asset->getSourceRoot();
|
||||
$path = $asset->getSourcePath();
|
||||
|
||||
if ($root && $path) {
|
||||
$pb->add('--load-path')->add(dirname($root.'/'.$path));
|
||||
}
|
||||
|
||||
if ($this->unixNewlines) {
|
||||
$pb->add('--unix-newlines');
|
||||
}
|
||||
|
||||
if (true === $this->scss || (null === $this->scss && 'scss' == pathinfo($path, PATHINFO_EXTENSION))) {
|
||||
$pb->add('--scss');
|
||||
}
|
||||
|
||||
if ($this->style) {
|
||||
$pb->add('--style')->add($this->style);
|
||||
}
|
||||
|
||||
if ($this->quiet) {
|
||||
$pb->add('--quiet');
|
||||
}
|
||||
|
||||
if ($this->debugInfo) {
|
||||
$pb->add('--debug-info');
|
||||
}
|
||||
|
||||
if ($this->lineNumbers) {
|
||||
$pb->add('--line-numbers');
|
||||
}
|
||||
|
||||
foreach ($this->loadPaths as $loadPath) {
|
||||
$pb->add('--load-path')->add($loadPath);
|
||||
}
|
||||
|
||||
if ($this->cacheLocation) {
|
||||
$pb->add('--cache-location')->add($this->cacheLocation);
|
||||
}
|
||||
|
||||
if ($this->noCache) {
|
||||
$pb->add('--no-cache');
|
||||
}
|
||||
|
||||
if ($this->compass) {
|
||||
$pb->add('--compass');
|
||||
}
|
||||
|
||||
// input
|
||||
$pb->add($input = tempnam(sys_get_temp_dir(), 'assetic_sass'));
|
||||
file_put_contents($input, $asset->getContent());
|
||||
|
||||
$proc = $pb->getProcess();
|
||||
$code = $proc->run();
|
||||
unlink($input);
|
||||
|
||||
if (0 < $code) {
|
||||
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
|
||||
}
|
||||
|
||||
$asset->setContent($proc->getOutput());
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter\Sass;
|
||||
|
||||
/**
|
||||
* Loads SCSS files.
|
||||
*
|
||||
* @link http://sass-lang.com/
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class ScssFilter extends SassFilter
|
||||
{
|
||||
public function __construct($sassPath = '/usr/bin/sass')
|
||||
{
|
||||
parent::__construct($sassPath);
|
||||
|
||||
$this->setScss(true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Exception\FilterException;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
|
||||
/**
|
||||
* Runs assets through Sprockets.
|
||||
*
|
||||
* Requires Sprockets 1.0.x.
|
||||
*
|
||||
* @link http://getsprockets.org/
|
||||
* @link http://github.com/sstephenson/sprockets/tree/1.0.x
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class SprocketsFilter implements FilterInterface
|
||||
{
|
||||
private $sprocketsLib;
|
||||
private $rubyBin;
|
||||
private $includeDirs;
|
||||
private $assetRoot;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $sprocketsLib Path to the Sprockets lib/ directory
|
||||
* @param string $rubyBin Path to the ruby binary
|
||||
*/
|
||||
public function __construct($sprocketsLib = null, $rubyBin = '/usr/bin/ruby')
|
||||
{
|
||||
$this->sprocketsLib = $sprocketsLib;
|
||||
$this->rubyBin = $rubyBin;
|
||||
$this->includeDirs = array();
|
||||
}
|
||||
|
||||
public function addIncludeDir($directory)
|
||||
{
|
||||
$this->includeDirs[] = $directory;
|
||||
}
|
||||
|
||||
public function setAssetRoot($assetRoot)
|
||||
{
|
||||
$this->assetRoot = $assetRoot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hack around a bit, get the job done.
|
||||
*/
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
static $format = <<<'EOF'
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require %s
|
||||
%s
|
||||
options = { :load_path => [],
|
||||
:source_files => [%s],
|
||||
:expand_paths => false }
|
||||
|
||||
%ssecretary = Sprockets::Secretary.new(options)
|
||||
secretary.install_assets if options[:asset_root]
|
||||
print secretary.concatenation
|
||||
|
||||
EOF;
|
||||
|
||||
$more = '';
|
||||
|
||||
foreach ($this->includeDirs as $directory) {
|
||||
$more .= 'options[:load_path] << '.var_export($directory, true)."\n";
|
||||
}
|
||||
|
||||
if (null !== $this->assetRoot) {
|
||||
$more .= 'options[:asset_root] = '.var_export($this->assetRoot, true)."\n";
|
||||
}
|
||||
|
||||
if ($more) {
|
||||
$more .= "\n";
|
||||
}
|
||||
|
||||
$tmpAsset = tempnam(sys_get_temp_dir(), 'assetic_sprockets');
|
||||
file_put_contents($tmpAsset, $asset->getContent());
|
||||
|
||||
$input = tempnam(sys_get_temp_dir(), 'assetic_sprockets');
|
||||
file_put_contents($input, sprintf($format,
|
||||
$this->sprocketsLib
|
||||
? sprintf('File.join(%s, \'sprockets\')', var_export($this->sprocketsLib, true))
|
||||
: '\'sprockets\'',
|
||||
$this->getHack($asset),
|
||||
var_export($tmpAsset, true),
|
||||
$more
|
||||
));
|
||||
|
||||
$pb = new ProcessBuilder(array(
|
||||
$this->rubyBin,
|
||||
$input,
|
||||
));
|
||||
|
||||
$proc = $pb->getProcess();
|
||||
$code = $proc->run();
|
||||
unlink($tmpAsset);
|
||||
unlink($input);
|
||||
|
||||
if (0 < $code) {
|
||||
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
|
||||
}
|
||||
|
||||
$asset->setContent($proc->getOutput());
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
|
||||
private function getHack(AssetInterface $asset)
|
||||
{
|
||||
static $format = <<<'EOF'
|
||||
|
||||
module Sprockets
|
||||
class Preprocessor
|
||||
protected
|
||||
def pathname_for_relative_require_from(source_line)
|
||||
Sprockets::Pathname.new(@environment, File.join(%s, location_from(source_line)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
EOF;
|
||||
|
||||
$root = $asset->getSourceRoot();
|
||||
$path = $asset->getSourcePath();
|
||||
|
||||
if ($root && $path) {
|
||||
return sprintf($format, var_export(dirname($root.'/'.$path), true));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Exception\FilterException;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
|
||||
/**
|
||||
* Loads STYL files.
|
||||
*
|
||||
* @link http://learnboost.github.com/stylus/
|
||||
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||
*/
|
||||
class StylusFilter implements FilterInterface
|
||||
{
|
||||
private $nodeBin;
|
||||
private $nodePaths;
|
||||
private $compress;
|
||||
|
||||
/**
|
||||
* Constructs filter.
|
||||
*
|
||||
* @param string $nodeBin The path to the node binary
|
||||
* @param array $nodePaths An array of node paths
|
||||
*/
|
||||
public function __construct($nodeBin = '/usr/bin/node', array $nodePaths = array())
|
||||
{
|
||||
$this->nodeBin = $nodeBin;
|
||||
$this->nodePaths = $nodePaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable output compression.
|
||||
*
|
||||
* @param boolean $compress
|
||||
*/
|
||||
public function setCompress($compress)
|
||||
{
|
||||
$this->compress = $compress;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
static $format = <<<'EOF'
|
||||
var stylus = require('stylus');
|
||||
var sys = require(process.binding('natives').util ? 'util' : 'sys');
|
||||
|
||||
stylus(%s, %s).render(function(e, css){
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
sys.print(css);
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
EOF;
|
||||
|
||||
$root = $asset->getSourceRoot();
|
||||
$path = $asset->getSourcePath();
|
||||
|
||||
// parser options
|
||||
$parserOptions = array();
|
||||
if ($root && $path) {
|
||||
$parserOptions['paths'] = array(dirname($root.'/'.$path));
|
||||
$parserOptions['filename'] = basename($path);
|
||||
}
|
||||
|
||||
if (null !== $this->compress) {
|
||||
$parserOptions['compress'] = $this->compress;
|
||||
}
|
||||
|
||||
$pb = new ProcessBuilder();
|
||||
$pb->inheritEnvironmentVariables();
|
||||
|
||||
// node.js configuration
|
||||
if (0 < count($this->nodePaths)) {
|
||||
$pb->setEnv('NODE_PATH', implode(':', $this->nodePaths));
|
||||
}
|
||||
|
||||
$pb->add($this->nodeBin)->add($input = tempnam(sys_get_temp_dir(), 'assetic_stylus'));
|
||||
file_put_contents($input, sprintf($format,
|
||||
json_encode($asset->getContent()),
|
||||
json_encode($parserOptions)
|
||||
));
|
||||
|
||||
$proc = $pb->getProcess();
|
||||
$code = $proc->run();
|
||||
unlink($input);
|
||||
|
||||
if (0 < $code) {
|
||||
throw FilterException::fromProcess($proc)->setInput($asset->getContent());
|
||||
}
|
||||
|
||||
$asset->setContent($proc->getOutput());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter\Yui;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
use Assetic\Filter\FilterInterface;
|
||||
use Assetic\Exception\FilterException;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
|
||||
/**
|
||||
* Base YUI compressor filter.
|
||||
*
|
||||
* @link http://developer.yahoo.com/yui/compressor/
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
abstract class BaseCompressorFilter implements FilterInterface
|
||||
{
|
||||
private $jarPath;
|
||||
private $javaPath;
|
||||
private $charset;
|
||||
private $lineBreak;
|
||||
|
||||
public function __construct($jarPath, $javaPath = '/usr/bin/java')
|
||||
{
|
||||
$this->jarPath = $jarPath;
|
||||
$this->javaPath = $javaPath;
|
||||
}
|
||||
|
||||
public function setCharset($charset)
|
||||
{
|
||||
$this->charset = $charset;
|
||||
}
|
||||
|
||||
public function setLineBreak($lineBreak)
|
||||
{
|
||||
$this->lineBreak = $lineBreak;
|
||||
}
|
||||
|
||||
public function filterLoad(AssetInterface $asset)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses a string.
|
||||
*
|
||||
* @param string $content The content to compress
|
||||
* @param string $type The type of content, either "js" or "css"
|
||||
* @param array $options An indexed array of additional options
|
||||
*
|
||||
* @return string The compressed content
|
||||
*/
|
||||
protected function compress($content, $type, $options = array())
|
||||
{
|
||||
$pb = new ProcessBuilder(array(
|
||||
$this->javaPath,
|
||||
'-jar',
|
||||
$this->jarPath,
|
||||
));
|
||||
|
||||
foreach ($options as $option) {
|
||||
$pb->add($option);
|
||||
}
|
||||
|
||||
if (null !== $this->charset) {
|
||||
$pb->add('--charset')->add($this->charset);
|
||||
}
|
||||
|
||||
if (null !== $this->lineBreak) {
|
||||
$pb->add('--line-break')->add($this->lineBreak);
|
||||
}
|
||||
|
||||
// input and output files
|
||||
$tempDir = realpath(sys_get_temp_dir());
|
||||
$hash = substr(sha1(time().rand(11111, 99999)), 0, 7);
|
||||
$input = $tempDir.DIRECTORY_SEPARATOR.$hash.'.'.$type;
|
||||
$output = $tempDir.DIRECTORY_SEPARATOR.$hash.'-min.'.$type;
|
||||
file_put_contents($input, $content);
|
||||
$pb->add('-o')->add($output)->add($input);
|
||||
|
||||
$proc = $pb->getProcess();
|
||||
$code = $proc->run();
|
||||
unlink($input);
|
||||
|
||||
if (0 < $code) {
|
||||
if (file_exists($output)) {
|
||||
unlink($output);
|
||||
}
|
||||
|
||||
throw FilterException::fromProcess($proc)->setInput($content);
|
||||
} elseif (!file_exists($output)) {
|
||||
throw new \RuntimeException('Error creating output file.');
|
||||
}
|
||||
|
||||
$retval = file_get_contents($output);
|
||||
unlink($output);
|
||||
|
||||
return $retval;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter\Yui;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
|
||||
/**
|
||||
* CSS YUI compressor filter.
|
||||
*
|
||||
* @link http://developer.yahoo.com/yui/compressor/
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class CssCompressorFilter extends BaseCompressorFilter
|
||||
{
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
$asset->setContent($this->compress($asset->getContent(), 'css'));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Filter\Yui;
|
||||
|
||||
use Assetic\Asset\AssetInterface;
|
||||
|
||||
/**
|
||||
* Javascript YUI compressor filter.
|
||||
*
|
||||
* @link http://developer.yahoo.com/yui/compressor/
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class JsCompressorFilter extends BaseCompressorFilter
|
||||
{
|
||||
private $nomunge;
|
||||
private $preserveSemi;
|
||||
private $disableOptimizations;
|
||||
|
||||
public function setNomunge($nomunge = true)
|
||||
{
|
||||
$this->nomunge = $nomunge;
|
||||
}
|
||||
|
||||
public function setPreserveSemi($preserveSemi)
|
||||
{
|
||||
$this->preserveSemi = $preserveSemi;
|
||||
}
|
||||
|
||||
public function setDisableOptimizations($disableOptimizations)
|
||||
{
|
||||
$this->disableOptimizations = $disableOptimizations;
|
||||
}
|
||||
|
||||
public function filterDump(AssetInterface $asset)
|
||||
{
|
||||
$options = array();
|
||||
|
||||
if ($this->nomunge) {
|
||||
$options[] = '--nomunge';
|
||||
}
|
||||
|
||||
if ($this->preserveSemi) {
|
||||
$options[] = '--preserve-semi';
|
||||
}
|
||||
|
||||
if ($this->disableOptimizations) {
|
||||
$options[] = '--disable-optimizations';
|
||||
}
|
||||
|
||||
$asset->setContent($this->compress($asset->getContent(), 'js', $options));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic;
|
||||
|
||||
use Assetic\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* Manages the available filters.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class FilterManager
|
||||
{
|
||||
private $filters = array();
|
||||
|
||||
public function set($alias, FilterInterface $filter)
|
||||
{
|
||||
$this->checkName($alias);
|
||||
|
||||
$this->filters[$alias] = $filter;
|
||||
}
|
||||
|
||||
public function get($alias)
|
||||
{
|
||||
if (!isset($this->filters[$alias])) {
|
||||
throw new \InvalidArgumentException(sprintf('There is no "%s" filter.', $alias));
|
||||
}
|
||||
|
||||
return $this->filters[$alias];
|
||||
}
|
||||
|
||||
public function has($alias)
|
||||
{
|
||||
return isset($this->filters[$alias]);
|
||||
}
|
||||
|
||||
public function getNames()
|
||||
{
|
||||
return array_keys($this->filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a name is valid.
|
||||
*
|
||||
* @param string $name An asset name candidate
|
||||
*
|
||||
* @throws InvalidArgumentException If the asset name is invalid
|
||||
*/
|
||||
protected function checkName($name)
|
||||
{
|
||||
if (!ctype_alnum(str_replace('_', '', $name))) {
|
||||
throw new \InvalidArgumentException(sprintf('The name "%s" is invalid.', $name));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Assetic\Util;
|
||||
|
||||
/**
|
||||
* Path Utils.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
abstract class PathUtils
|
||||
{
|
||||
public static function resolvePath($path, array $vars, array $values)
|
||||
{
|
||||
$map = array();
|
||||
foreach ($vars as $var) {
|
||||
if (false === strpos($path, '{'.$var.'}')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($values[$var])) {
|
||||
throw new \InvalidArgumentException(sprintf('The path "%s" contains the variable "%s", but was not given any value for it.', $path, $var));
|
||||
}
|
||||
|
||||
$map['{'.$var.'}'] = $values[$var];
|
||||
}
|
||||
|
||||
return strtr($path, $map);
|
||||
}
|
||||
|
||||
private final function __construct() { }
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic\Util;
|
||||
|
||||
/**
|
||||
* An object that can be used as either a string or array.
|
||||
*
|
||||
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
|
||||
*/
|
||||
class TraversableString implements \IteratorAggregate, \Countable
|
||||
{
|
||||
private $one;
|
||||
private $many;
|
||||
|
||||
public function __construct($one, array $many)
|
||||
{
|
||||
$this->one = $one;
|
||||
$this->many = $many;
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->many);
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
return count($this->many);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return (string) $this->one;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Assetic;
|
||||
|
||||
/**
|
||||
* Value Supplier Interface.
|
||||
*
|
||||
* Implementations determine runtime values for compile-time variables.
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
*/
|
||||
interface ValueSupplierInterface
|
||||
{
|
||||
/**
|
||||
* Returns a map of values.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function getValues();
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Assetic package, an OpenSky project.
|
||||
*
|
||||
* (c) 2010-2011 OpenSky Project Inc
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use Assetic\Factory\AssetFactory;
|
||||
use Assetic\Util\TraversableString;
|
||||
|
||||
/**
|
||||
* Initializes the global Assetic object.
|
||||
*
|
||||
* @param AssetFactory $factory The asset factory
|
||||
*/
|
||||
function assetic_init(AssetFactory $factory)
|
||||
{
|
||||
global $_assetic;
|
||||
|
||||
$_assetic = new stdClass();
|
||||
$_assetic->factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of javascript URLs.
|
||||
*
|
||||
* @param array|string $inputs Input strings
|
||||
* @param array|string $filters Filter names
|
||||
* @param array $options An array of options
|
||||
*
|
||||
* @return array An array of javascript URLs
|
||||
*/
|
||||
function assetic_javascripts($inputs = array(), $filters = array(), array $options = array())
|
||||
{
|
||||
if (!isset($options['output'])) {
|
||||
$options['output'] = 'js/*.js';
|
||||
}
|
||||
|
||||
return _assetic_urls($inputs, $filters, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of stylesheet URLs.
|
||||
*
|
||||
* @param array|string $inputs Input strings
|
||||
* @param array|string $filters Filter names
|
||||
* @param array $options An array of options
|
||||
*
|
||||
* @return array An array of stylesheet URLs
|
||||
*/
|
||||
function assetic_stylesheets($inputs = array(), $filters = array(), array $options = array())
|
||||
{
|
||||
if (!isset($options['output'])) {
|
||||
$options['output'] = 'css/*.css';
|
||||
}
|
||||
|
||||
return _assetic_urls($inputs, $filters, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an image URL.
|
||||
*
|
||||
* @param string $input An input
|
||||
* @param array|string $filters Filter names
|
||||
* @param array $options An array of options
|
||||
*
|
||||
* @return string An image URL
|
||||
*/
|
||||
function assetic_image($input, $filters = array(), array $options = array())
|
||||
{
|
||||
if (!isset($options['output'])) {
|
||||
$options['output'] = 'images/*';
|
||||
}
|
||||
|
||||
$urls = _assetic_urls($input, $filters, $options);
|
||||
|
||||
return current($urls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of asset urls.
|
||||
*
|
||||
* @param array|string $inputs Input strings
|
||||
* @param array|string $filters Filter names
|
||||
* @param array $options An array of options
|
||||
*
|
||||
* @return array An array of URLs
|
||||
*/
|
||||
function _assetic_urls($inputs = array(), $filters = array(), array $options = array())
|
||||
{
|
||||
global $_assetic;
|
||||
|
||||
if (!is_array($inputs)) {
|
||||
$inputs = array_filter(array_map('trim', explode(',', $inputs)));
|
||||
}
|
||||
|
||||
if (!is_array($filters)) {
|
||||
$filters = array_filter(array_map('trim', explode(',', $filters)));
|
||||
}
|
||||
|
||||
$coll = $_assetic->factory->createAsset($inputs, $filters, $options);
|
||||
|
||||
$debug = isset($options['debug']) ? $options['debug'] : $_assetic->factory->isDebug();
|
||||
$combine = isset($options['combine']) ? $options['combine'] : !$debug;
|
||||
|
||||
$one = $coll->getTargetPath();
|
||||
if ($combine) {
|
||||
$many = array();
|
||||
foreach ($coll as $leaf) {
|
||||
$many[] = $leaf->getTargetPath();
|
||||
}
|
||||
} else {
|
||||
$many = array($one);
|
||||
}
|
||||
|
||||
return new TraversableString($one, $many);
|
||||
}
|
|
@ -0,0 +1,910 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* A Mustache implementation in PHP.
|
||||
*
|
||||
* {@link http://defunkt.github.com/mustache}
|
||||
*
|
||||
* Mustache is a framework-agnostic logic-less templating language. It enforces separation of view
|
||||
* logic from template files. In fact, it is not even possible to embed logic in the template.
|
||||
*
|
||||
* This is very, very rad.
|
||||
*
|
||||
* @author Justin Hileman {@link http://justinhileman.com}
|
||||
*/
|
||||
class Mustache {
|
||||
|
||||
const VERSION = '1.0.0';
|
||||
const SPEC_VERSION = '1.1.2';
|
||||
|
||||
/**
|
||||
* Should this Mustache throw exceptions when it finds unexpected tags?
|
||||
*
|
||||
* @see self::_throwsException()
|
||||
*/
|
||||
protected $_throwsExceptions = array(
|
||||
MustacheException::UNKNOWN_VARIABLE => false,
|
||||
MustacheException::UNCLOSED_SECTION => true,
|
||||
MustacheException::UNEXPECTED_CLOSE_SECTION => true,
|
||||
MustacheException::UNKNOWN_PARTIAL => false,
|
||||
MustacheException::UNKNOWN_PRAGMA => true,
|
||||
);
|
||||
|
||||
// Override charset passed to htmlentities() and htmlspecialchars(). Defaults to UTF-8.
|
||||
protected $_charset = 'UTF-8';
|
||||
|
||||
/**
|
||||
* Pragmas are macro-like directives that, when invoked, change the behavior or
|
||||
* syntax of Mustache.
|
||||
*
|
||||
* They should be considered extremely experimental. Most likely their implementation
|
||||
* will change in the future.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The {{%UNESCAPED}} pragma swaps the meaning of the {{normal}} and {{{unescaped}}}
|
||||
* Mustache tags. That is, once this pragma is activated the {{normal}} tag will not be
|
||||
* escaped while the {{{unescaped}}} tag will be escaped.
|
||||
*
|
||||
* Pragmas apply only to the current template. Partials, even those included after the
|
||||
* {{%UNESCAPED}} call, will need their own pragma declaration.
|
||||
*
|
||||
* This may be useful in non-HTML Mustache situations.
|
||||
*/
|
||||
const PRAGMA_UNESCAPED = 'UNESCAPED';
|
||||
|
||||
/**
|
||||
* Constants used for section and tag RegEx
|
||||
*/
|
||||
const SECTION_TYPES = '\^#\/';
|
||||
const TAG_TYPES = '#\^\/=!<>\\{&';
|
||||
|
||||
protected $_otag = '{{';
|
||||
protected $_ctag = '}}';
|
||||
|
||||
protected $_tagRegEx;
|
||||
|
||||
protected $_template = '';
|
||||
protected $_context = array();
|
||||
protected $_partials = array();
|
||||
protected $_pragmas = array();
|
||||
|
||||
protected $_pragmasImplemented = array(
|
||||
self::PRAGMA_UNESCAPED
|
||||
);
|
||||
|
||||
protected $_localPragmas = array();
|
||||
|
||||
/**
|
||||
* Mustache class constructor.
|
||||
*
|
||||
* This method accepts a $template string and a $view object. Optionally, pass an associative
|
||||
* array of partials as well.
|
||||
*
|
||||
* Passing an $options array allows overriding certain Mustache options during instantiation:
|
||||
*
|
||||
* $options = array(
|
||||
* // `charset` -- must be supported by `htmlspecialentities()`. defaults to 'UTF-8'
|
||||
* 'charset' => 'ISO-8859-1',
|
||||
*
|
||||
* // opening and closing delimiters, as an array or a space-separated string
|
||||
* 'delimiters' => '<% %>',
|
||||
*
|
||||
* // an array of pragmas to enable/disable
|
||||
* 'pragmas' => array(
|
||||
* Mustache::PRAGMA_UNESCAPED => true
|
||||
* ),
|
||||
*
|
||||
* // an array of thrown exceptions to enable/disable
|
||||
* 'throws_exceptions' => array(
|
||||
* MustacheException::UNKNOWN_VARIABLE => false,
|
||||
* MustacheException::UNCLOSED_SECTION => true,
|
||||
* MustacheException::UNEXPECTED_CLOSE_SECTION => true,
|
||||
* MustacheException::UNKNOWN_PARTIAL => false,
|
||||
* MustacheException::UNKNOWN_PRAGMA => true,
|
||||
* ),
|
||||
* );
|
||||
*
|
||||
* @access public
|
||||
* @param string $template (default: null)
|
||||
* @param mixed $view (default: null)
|
||||
* @param array $partials (default: null)
|
||||
* @param array $options (default: array())
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($template = null, $view = null, $partials = null, array $options = null) {
|
||||
if ($template !== null) $this->_template = $template;
|
||||
if ($partials !== null) $this->_partials = $partials;
|
||||
if ($view !== null) $this->_context = array($view);
|
||||
if ($options !== null) $this->_setOptions($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for setting options from constructor args.
|
||||
*
|
||||
* @access protected
|
||||
* @param array $options
|
||||
* @return void
|
||||
*/
|
||||
protected function _setOptions(array $options) {
|
||||
if (isset($options['charset'])) {
|
||||
$this->_charset = $options['charset'];
|
||||
}
|
||||
|
||||
if (isset($options['delimiters'])) {
|
||||
$delims = $options['delimiters'];
|
||||
if (!is_array($delims)) {
|
||||
$delims = array_map('trim', explode(' ', $delims, 2));
|
||||
}
|
||||
$this->_otag = $delims[0];
|
||||
$this->_ctag = $delims[1];
|
||||
}
|
||||
|
||||
if (isset($options['pragmas'])) {
|
||||
foreach ($options['pragmas'] as $pragma_name => $pragma_value) {
|
||||
if (!in_array($pragma_name, $this->_pragmasImplemented, true)) {
|
||||
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
|
||||
}
|
||||
}
|
||||
$this->_pragmas = $options['pragmas'];
|
||||
}
|
||||
|
||||
if (isset($options['throws_exceptions'])) {
|
||||
foreach ($options['throws_exceptions'] as $exception => $value) {
|
||||
$this->_throwsExceptions[$exception] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mustache class clone method.
|
||||
*
|
||||
* A cloned Mustache instance should have pragmas, delimeters and root context
|
||||
* reset to default values.
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function __clone() {
|
||||
$this->_otag = '{{';
|
||||
$this->_ctag = '}}';
|
||||
$this->_localPragmas = array();
|
||||
|
||||
if ($keys = array_keys($this->_context)) {
|
||||
$last = array_pop($keys);
|
||||
if ($this->_context[$last] instanceof Mustache) {
|
||||
$this->_context[$last] =& $this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the given template and view object.
|
||||
*
|
||||
* Defaults to the template and view passed to the class constructor unless a new one is provided.
|
||||
* Optionally, pass an associative array of partials as well.
|
||||
*
|
||||
* @access public
|
||||
* @param string $template (default: null)
|
||||
* @param mixed $view (default: null)
|
||||
* @param array $partials (default: null)
|
||||
* @return string Rendered Mustache template.
|
||||
*/
|
||||
public function render($template = null, $view = null, $partials = null) {
|
||||
if ($template === null) $template = $this->_template;
|
||||
if ($partials !== null) $this->_partials = $partials;
|
||||
|
||||
$otag_orig = $this->_otag;
|
||||
$ctag_orig = $this->_ctag;
|
||||
|
||||
if ($view) {
|
||||
$this->_context = array($view);
|
||||
} else if (empty($this->_context)) {
|
||||
$this->_context = array($this);
|
||||
}
|
||||
|
||||
$template = $this->_renderPragmas($template);
|
||||
$template = $this->_renderTemplate($template);
|
||||
|
||||
$this->_otag = $otag_orig;
|
||||
$this->_ctag = $ctag_orig;
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the render() function for string conversion.
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
// PHP doesn't like exceptions in __toString.
|
||||
// catch any exceptions and convert them to strings.
|
||||
try {
|
||||
$result = $this->render();
|
||||
return $result;
|
||||
} catch (Exception $e) {
|
||||
return "Error rendering mustache: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal render function, used for recursive calls.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $template
|
||||
* @return string Rendered Mustache template.
|
||||
*/
|
||||
protected function _renderTemplate($template) {
|
||||
if ($section = $this->_findSection($template)) {
|
||||
list($before, $type, $tag_name, $content, $after) = $section;
|
||||
|
||||
$rendered_before = $this->_renderTags($before);
|
||||
|
||||
$rendered_content = '';
|
||||
$val = $this->_getVariable($tag_name);
|
||||
switch($type) {
|
||||
// inverted section
|
||||
case '^':
|
||||
if (empty($val)) {
|
||||
$rendered_content = $this->_renderTemplate($content);
|
||||
}
|
||||
break;
|
||||
|
||||
// regular section
|
||||
case '#':
|
||||
// higher order sections
|
||||
if ($this->_varIsCallable($val)) {
|
||||
$rendered_content = $this->_renderTemplate(call_user_func($val, $content));
|
||||
} else if ($this->_varIsIterable($val)) {
|
||||
foreach ($val as $local_context) {
|
||||
$this->_pushContext($local_context);
|
||||
$rendered_content .= $this->_renderTemplate($content);
|
||||
$this->_popContext();
|
||||
}
|
||||
} else if ($val) {
|
||||
if (is_array($val) || is_object($val)) {
|
||||
$this->_pushContext($val);
|
||||
$rendered_content = $this->_renderTemplate($content);
|
||||
$this->_popContext();
|
||||
} else {
|
||||
$rendered_content = $this->_renderTemplate($content);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $rendered_before . $rendered_content . $this->_renderTemplate($after);
|
||||
}
|
||||
|
||||
return $this->_renderTags($template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a section RegEx string for the given opening/closing tags.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $otag
|
||||
* @param string $ctag
|
||||
* @return string
|
||||
*/
|
||||
protected function _prepareSectionRegEx($otag, $ctag) {
|
||||
return sprintf(
|
||||
'/(?:(?<=\\n)[ \\t]*)?%s(?:(?P<type>[%s])(?P<tag_name>.+?)|=(?P<delims>.*?)=)%s\\n?/s',
|
||||
preg_quote($otag, '/'),
|
||||
self::SECTION_TYPES,
|
||||
preg_quote($ctag, '/')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the first section from $template.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $template
|
||||
* @return array $before, $type, $tag_name, $content and $after
|
||||
*/
|
||||
protected function _findSection($template) {
|
||||
$regEx = $this->_prepareSectionRegEx($this->_otag, $this->_ctag);
|
||||
|
||||
$section_start = null;
|
||||
$section_type = null;
|
||||
$content_start = null;
|
||||
|
||||
$search_offset = 0;
|
||||
|
||||
$section_stack = array();
|
||||
$matches = array();
|
||||
while (preg_match($regEx, $template, $matches, PREG_OFFSET_CAPTURE, $search_offset)) {
|
||||
if (isset($matches['delims'][0])) {
|
||||
list($otag, $ctag) = explode(' ', $matches['delims'][0]);
|
||||
$regEx = $this->_prepareSectionRegEx($otag, $ctag);
|
||||
$search_offset = $matches[0][1] + strlen($matches[0][0]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$match = $matches[0][0];
|
||||
$offset = $matches[0][1];
|
||||
$type = $matches['type'][0];
|
||||
$tag_name = trim($matches['tag_name'][0]);
|
||||
|
||||
$search_offset = $offset + strlen($match);
|
||||
|
||||
switch ($type) {
|
||||
case '^':
|
||||
case '#':
|
||||
if (empty($section_stack)) {
|
||||
$section_start = $offset;
|
||||
$section_type = $type;
|
||||
$content_start = $search_offset;
|
||||
}
|
||||
array_push($section_stack, $tag_name);
|
||||
break;
|
||||
case '/':
|
||||
if (empty($section_stack) || ($tag_name !== array_pop($section_stack))) {
|
||||
if ($this->_throwsException(MustacheException::UNEXPECTED_CLOSE_SECTION)) {
|
||||
throw new MustacheException('Unexpected close section: ' . $tag_name, MustacheException::UNEXPECTED_CLOSE_SECTION);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($section_stack)) {
|
||||
// $before, $type, $tag_name, $content, $after
|
||||
return array(
|
||||
substr($template, 0, $section_start),
|
||||
$section_type,
|
||||
$tag_name,
|
||||
substr($template, $content_start, $offset - $content_start),
|
||||
substr($template, $search_offset),
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($section_stack)) {
|
||||
if ($this->_throwsException(MustacheException::UNCLOSED_SECTION)) {
|
||||
throw new MustacheException('Unclosed section: ' . $section_stack[0], MustacheException::UNCLOSED_SECTION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a pragma RegEx for the given opening/closing tags.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $otag
|
||||
* @param string $ctag
|
||||
* @return string
|
||||
*/
|
||||
protected function _preparePragmaRegEx($otag, $ctag) {
|
||||
return sprintf(
|
||||
'/%s%%\\s*(?P<pragma_name>[\\w_-]+)(?P<options_string>(?: [\\w]+=[\\w]+)*)\\s*%s\\n?/s',
|
||||
preg_quote($otag, '/'),
|
||||
preg_quote($ctag, '/')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize pragmas and remove all pragma tags.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $template
|
||||
* @return string
|
||||
*/
|
||||
protected function _renderPragmas($template) {
|
||||
$this->_localPragmas = $this->_pragmas;
|
||||
|
||||
// no pragmas
|
||||
if (strpos($template, $this->_otag . '%') === false) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
$regEx = $this->_preparePragmaRegEx($this->_otag, $this->_ctag);
|
||||
return preg_replace_callback($regEx, array($this, '_renderPragma'), $template);
|
||||
}
|
||||
|
||||
/**
|
||||
* A preg_replace helper to remove {{%PRAGMA}} tags and enable requested pragma.
|
||||
*
|
||||
* @access protected
|
||||
* @param mixed $matches
|
||||
* @return void
|
||||
* @throws MustacheException unknown pragma
|
||||
*/
|
||||
protected function _renderPragma($matches) {
|
||||
$pragma = $matches[0];
|
||||
$pragma_name = $matches['pragma_name'];
|
||||
$options_string = $matches['options_string'];
|
||||
|
||||
if (!in_array($pragma_name, $this->_pragmasImplemented)) {
|
||||
if ($this->_throwsException(MustacheException::UNKNOWN_PRAGMA)) {
|
||||
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
$options = array();
|
||||
foreach (explode(' ', trim($options_string)) as $o) {
|
||||
if ($p = trim($o)) {
|
||||
$p = explode('=', $p);
|
||||
$options[$p[0]] = $p[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($options)) {
|
||||
$this->_localPragmas[$pragma_name] = true;
|
||||
} else {
|
||||
$this->_localPragmas[$pragma_name] = $options;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this Mustache has a specific pragma.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $pragma_name
|
||||
* @return bool
|
||||
*/
|
||||
protected function _hasPragma($pragma_name) {
|
||||
if (array_key_exists($pragma_name, $this->_localPragmas) && $this->_localPragmas[$pragma_name]) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return pragma options, if any.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $pragma_name
|
||||
* @return mixed
|
||||
* @throws MustacheException Unknown pragma
|
||||
*/
|
||||
protected function _getPragmaOptions($pragma_name) {
|
||||
if (!$this->_hasPragma($pragma_name)) {
|
||||
if ($this->_throwsException(MustacheException::UNKNOWN_PRAGMA)) {
|
||||
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
|
||||
}
|
||||
}
|
||||
|
||||
return (is_array($this->_localPragmas[$pragma_name])) ? $this->_localPragmas[$pragma_name] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this Mustache instance throws a given exception.
|
||||
*
|
||||
* Expects exceptions to be MustacheException error codes (i.e. class constants).
|
||||
*
|
||||
* @access protected
|
||||
* @param mixed $exception
|
||||
* @return void
|
||||
*/
|
||||
protected function _throwsException($exception) {
|
||||
return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a tag RegEx for the given opening/closing tags.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $otag
|
||||
* @param string $ctag
|
||||
* @return string
|
||||
*/
|
||||
protected function _prepareTagRegEx($otag, $ctag, $first = false) {
|
||||
return sprintf(
|
||||
'/(?P<leading>(?:%s\\r?\\n)[ \\t]*)?%s(?P<type>[%s]?)(?P<tag_name>.+?)(?:\\2|})?%s(?P<trailing>\\s*(?:\\r?\\n|\\Z))?/s',
|
||||
($first ? '\\A|' : ''),
|
||||
preg_quote($otag, '/'),
|
||||
self::TAG_TYPES,
|
||||
preg_quote($ctag, '/')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop through and render individual Mustache tags.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $template
|
||||
* @return void
|
||||
*/
|
||||
protected function _renderTags($template) {
|
||||
if (strpos($template, $this->_otag) === false) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
$first = true;
|
||||
$this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag, true);
|
||||
|
||||
$html = '';
|
||||
$matches = array();
|
||||
while (preg_match($this->_tagRegEx, $template, $matches, PREG_OFFSET_CAPTURE)) {
|
||||
$tag = $matches[0][0];
|
||||
$offset = $matches[0][1];
|
||||
$modifier = $matches['type'][0];
|
||||
$tag_name = trim($matches['tag_name'][0]);
|
||||
|
||||
if (isset($matches['leading']) && $matches['leading'][1] > -1) {
|
||||
$leading = $matches['leading'][0];
|
||||
} else {
|
||||
$leading = null;
|
||||
}
|
||||
|
||||
if (isset($matches['trailing']) && $matches['trailing'][1] > -1) {
|
||||
$trailing = $matches['trailing'][0];
|
||||
} else {
|
||||
$trailing = null;
|
||||
}
|
||||
|
||||
$html .= substr($template, 0, $offset);
|
||||
|
||||
$next_offset = $offset + strlen($tag);
|
||||
if ((substr($html, -1) == "\n") && (substr($template, $next_offset, 1) == "\n")) {
|
||||
$next_offset++;
|
||||
}
|
||||
$template = substr($template, $next_offset);
|
||||
|
||||
$html .= $this->_renderTag($modifier, $tag_name, $leading, $trailing);
|
||||
|
||||
if ($first == true) {
|
||||
$first = false;
|
||||
$this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag);
|
||||
}
|
||||
}
|
||||
|
||||
return $html . $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the named tag, given the specified modifier.
|
||||
*
|
||||
* Accepted modifiers are `=` (change delimiter), `!` (comment), `>` (partial)
|
||||
* `{` or `&` (don't escape output), or none (render escaped output).
|
||||
*
|
||||
* @access protected
|
||||
* @param string $modifier
|
||||
* @param string $tag_name
|
||||
* @param string $leading Whitespace
|
||||
* @param string $trailing Whitespace
|
||||
* @throws MustacheException Unmatched section tag encountered.
|
||||
* @return string
|
||||
*/
|
||||
protected function _renderTag($modifier, $tag_name, $leading, $trailing) {
|
||||
switch ($modifier) {
|
||||
case '=':
|
||||
return $this->_changeDelimiter($tag_name, $leading, $trailing);
|
||||
break;
|
||||
case '!':
|
||||
return $this->_renderComment($tag_name, $leading, $trailing);
|
||||
break;
|
||||
case '>':
|
||||
case '<':
|
||||
return $this->_renderPartial($tag_name, $leading, $trailing);
|
||||
break;
|
||||
case '{':
|
||||
// strip the trailing } ...
|
||||
if ($tag_name[(strlen($tag_name) - 1)] == '}') {
|
||||
$tag_name = substr($tag_name, 0, -1);
|
||||
}
|
||||
case '&':
|
||||
if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) {
|
||||
return $this->_renderEscaped($tag_name, $leading, $trailing);
|
||||
} else {
|
||||
return $this->_renderUnescaped($tag_name, $leading, $trailing);
|
||||
}
|
||||
break;
|
||||
case '#':
|
||||
case '^':
|
||||
case '/':
|
||||
// remove any leftover section tags
|
||||
return $leading . $trailing;
|
||||
break;
|
||||
default:
|
||||
if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) {
|
||||
return $this->_renderUnescaped($modifier . $tag_name, $leading, $trailing);
|
||||
} else {
|
||||
return $this->_renderEscaped($modifier . $tag_name, $leading, $trailing);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any of its args contains the "\r" character.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $str
|
||||
* @return boolean
|
||||
*/
|
||||
protected function _stringHasR($str) {
|
||||
foreach (func_get_args() as $arg) {
|
||||
if (strpos($arg, "\r") !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape and return the requested tag.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $tag_name
|
||||
* @param string $leading Whitespace
|
||||
* @param string $trailing Whitespace
|
||||
* @return string
|
||||
*/
|
||||
protected function _renderEscaped($tag_name, $leading, $trailing) {
|
||||
$rendered = htmlentities($this->_renderUnescaped($tag_name, '', ''), ENT_COMPAT, $this->_charset);
|
||||
return $leading . $rendered . $trailing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a comment (i.e. return an empty string).
|
||||
*
|
||||
* @access protected
|
||||
* @param string $tag_name
|
||||
* @param string $leading Whitespace
|
||||
* @param string $trailing Whitespace
|
||||
* @return string
|
||||
*/
|
||||
protected function _renderComment($tag_name, $leading, $trailing) {
|
||||
if ($leading !== null && $trailing !== null) {
|
||||
if (strpos($leading, "\n") === false) {
|
||||
return '';
|
||||
}
|
||||
return $this->_stringHasR($leading, $trailing) ? "\r\n" : "\n";
|
||||
}
|
||||
return $leading . $trailing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the requested tag unescaped.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $tag_name
|
||||
* @param string $leading Whitespace
|
||||
* @param string $trailing Whitespace
|
||||
* @return string
|
||||
*/
|
||||
protected function _renderUnescaped($tag_name, $leading, $trailing) {
|
||||
$val = $this->_getVariable($tag_name);
|
||||
|
||||
if ($this->_varIsCallable($val)) {
|
||||
$val = $this->_renderTemplate(call_user_func($val));
|
||||
}
|
||||
|
||||
return $leading . $val . $trailing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the requested partial.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $tag_name
|
||||
* @param string $leading Whitespace
|
||||
* @param string $trailing Whitespace
|
||||
* @return string
|
||||
*/
|
||||
protected function _renderPartial($tag_name, $leading, $trailing) {
|
||||
$partial = $this->_getPartial($tag_name);
|
||||
if ($leading !== null && $trailing !== null) {
|
||||
$whitespace = trim($leading, "\r\n");
|
||||
$partial = preg_replace('/(\\r?\\n)(?!$)/s', "\\1" . $whitespace, $partial);
|
||||
}
|
||||
|
||||
$view = clone($this);
|
||||
|
||||
if ($leading !== null && $trailing !== null) {
|
||||
return $leading . $view->render($partial);
|
||||
} else {
|
||||
return $leading . $view->render($partial) . $trailing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the Mustache tag delimiter. This method also replaces this object's current
|
||||
* tag RegEx with one using the new delimiters.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $tag_name
|
||||
* @param string $leading Whitespace
|
||||
* @param string $trailing Whitespace
|
||||
* @return string
|
||||
*/
|
||||
protected function _changeDelimiter($tag_name, $leading, $trailing) {
|
||||
list($otag, $ctag) = explode(' ', $tag_name);
|
||||
$this->_otag = $otag;
|
||||
$this->_ctag = $ctag;
|
||||
|
||||
$this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag);
|
||||
|
||||
if ($leading !== null && $trailing !== null) {
|
||||
if (strpos($leading, "\n") === false) {
|
||||
return '';
|
||||
}
|
||||
return $this->_stringHasR($leading, $trailing) ? "\r\n" : "\n";
|
||||
}
|
||||
return $leading . $trailing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a local context onto the stack.
|
||||
*
|
||||
* @access protected
|
||||
* @param array &$local_context
|
||||
* @return void
|
||||
*/
|
||||
protected function _pushContext(&$local_context) {
|
||||
$new = array();
|
||||
$new[] =& $local_context;
|
||||
foreach (array_keys($this->_context) as $key) {
|
||||
$new[] =& $this->_context[$key];
|
||||
}
|
||||
$this->_context = $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the latest context from the stack.
|
||||
*
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function _popContext() {
|
||||
$new = array();
|
||||
|
||||
$keys = array_keys($this->_context);
|
||||
array_shift($keys);
|
||||
foreach ($keys as $key) {
|
||||
$new[] =& $this->_context[$key];
|
||||
}
|
||||
$this->_context = $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a variable from the context array.
|
||||
*
|
||||
* If the view is an array, returns the value with array key $tag_name.
|
||||
* If the view is an object, this will check for a public member variable
|
||||
* named $tag_name. If none is available, this method will execute and return
|
||||
* any class method named $tag_name. Failing all of the above, this method will
|
||||
* return an empty string.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $tag_name
|
||||
* @throws MustacheException Unknown variable name.
|
||||
* @return string
|
||||
*/
|
||||
protected function _getVariable($tag_name) {
|
||||
if ($tag_name === '.') {
|
||||
return $this->_context[0];
|
||||
} else if (strpos($tag_name, '.') !== false) {
|
||||
$chunks = explode('.', $tag_name);
|
||||
$first = array_shift($chunks);
|
||||
|
||||
$ret = $this->_findVariableInContext($first, $this->_context);
|
||||
foreach ($chunks as $next) {
|
||||
// Slice off a chunk of context for dot notation traversal.
|
||||
$c = array($ret);
|
||||
$ret = $this->_findVariableInContext($next, $c);
|
||||
}
|
||||
return $ret;
|
||||
} else {
|
||||
return $this->_findVariableInContext($tag_name, $this->_context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a variable from the context array. Internal helper used by getVariable() to abstract
|
||||
* variable traversal for dot notation.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $tag_name
|
||||
* @param array $context
|
||||
* @throws MustacheException Unknown variable name.
|
||||
* @return string
|
||||
*/
|
||||
protected function _findVariableInContext($tag_name, $context) {
|
||||
foreach ($context as $view) {
|
||||
if (is_object($view)) {
|
||||
if (method_exists($view, $tag_name)) {
|
||||
return $view->$tag_name();
|
||||
} else if (isset($view->$tag_name)) {
|
||||
return $view->$tag_name;
|
||||
}
|
||||
} else if (is_array($view) && array_key_exists($tag_name, $view)) {
|
||||
return $view[$tag_name];
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->_throwsException(MustacheException::UNKNOWN_VARIABLE)) {
|
||||
throw new MustacheException("Unknown variable: " . $tag_name, MustacheException::UNKNOWN_VARIABLE);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the partial corresponding to the requested tag name.
|
||||
*
|
||||
* Silently fails (i.e. returns '') when the requested partial is not found.
|
||||
*
|
||||
* @access protected
|
||||
* @param string $tag_name
|
||||
* @throws MustacheException Unknown partial name.
|
||||
* @return string
|
||||
*/
|
||||
protected function _getPartial($tag_name) {
|
||||
if ((is_array($this->_partials) || $this->_partials instanceof ArrayAccess) && isset($this->_partials[$tag_name])) {
|
||||
return $this->_partials[$tag_name];
|
||||
}
|
||||
|
||||
if ($this->_throwsException(MustacheException::UNKNOWN_PARTIAL)) {
|
||||
throw new MustacheException('Unknown partial: ' . $tag_name, MustacheException::UNKNOWN_PARTIAL);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given $var should be iterated (i.e. in a section context).
|
||||
*
|
||||
* @access protected
|
||||
* @param mixed $var
|
||||
* @return bool
|
||||
*/
|
||||
protected function _varIsIterable($var) {
|
||||
return $var instanceof Traversable || (is_array($var) && !array_diff_key($var, array_keys(array_keys($var))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Higher order sections helper: tests whether the section $var is a valid callback.
|
||||
*
|
||||
* In Mustache.php, a variable is considered 'callable' if the variable is:
|
||||
*
|
||||
* 1. an anonymous function.
|
||||
* 2. an object and the name of a public function, i.e. `array($SomeObject, 'methodName')`
|
||||
* 3. a class name and the name of a public static function, i.e. `array('SomeClass', 'methodName')`
|
||||
*
|
||||
* @access protected
|
||||
* @param mixed $var
|
||||
* @return bool
|
||||
*/
|
||||
protected function _varIsCallable($var) {
|
||||
return !is_string($var) && is_callable($var);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* MustacheException class.
|
||||
*
|
||||
* @extends Exception
|
||||
*/
|
||||
class MustacheException extends Exception {
|
||||
|
||||
// An UNKNOWN_VARIABLE exception is thrown when a {{variable}} is not found
|
||||
// in the current context.
|
||||
const UNKNOWN_VARIABLE = 0;
|
||||
|
||||
// An UNCLOSED_SECTION exception is thrown when a {{#section}} is not closed.
|
||||
const UNCLOSED_SECTION = 1;
|
||||
|
||||
// An UNEXPECTED_CLOSE_SECTION exception is thrown when {{/section}} appears
|
||||
// without a corresponding {{#section}} or {{^section}}.
|
||||
const UNEXPECTED_CLOSE_SECTION = 2;
|
||||
|
||||
// An UNKNOWN_PARTIAL exception is thrown whenever a {{>partial}} tag appears
|
||||
// with no associated partial.
|
||||
const UNKNOWN_PARTIAL = 3;
|
||||
|
||||
// An UNKNOWN_PRAGMA exception is thrown whenever a {{%PRAGMA}} tag appears
|
||||
// which can't be handled by this Mustache instance.
|
||||
const UNKNOWN_PRAGMA = 4;
|
||||
|
||||
}
|
|
@ -0,0 +1,660 @@
|
|||
For ease of distribution, lessphp 0.2.0 is under a dual license.
|
||||
You are free to pick which one suits your needs.
|
||||
|
||||
|
||||
|
||||
|
||||
MIT LICENSE
|
||||
|
||||
|
||||
|
||||
|
||||
Copyright (c) 2010 Leaf Corcoran, http://leafo.net/lessphp
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
|
||||
GPL VERSION 3
|
||||
|
||||
|
||||
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
# lessphp v0.3.3
|
||||
### <http://leafo.net/lessphp>
|
||||
|
||||
`lessphp` is a compiler for LESS written in PHP. The documentation is great,
|
||||
so check it out: <http://leafo.net/lessphp/docs/>.
|
||||
|
||||
Here's a quick tutorial:
|
||||
|
||||
### How to use in your PHP project
|
||||
|
||||
Copy `lessc.inc.php` to your include directory and include it into your project.
|
||||
|
||||
There are a few ways to interface with the compiler. The easiest is to have it
|
||||
compile a LESS file when the page is requested. The static function
|
||||
`lessc::ccompile`, checked compile, will compile the input LESS file only when it
|
||||
is newer than the output file.
|
||||
|
||||
try {
|
||||
lessc::ccompile('input.less', 'output.css');
|
||||
} catch (exception $ex) {
|
||||
exit($ex->getMessage());
|
||||
}
|
||||
|
||||
`lessc::ccompile` is not aware of imported files that change. Read [about
|
||||
`lessc::cexecute`](http://leafo.net/lessphp/docs/#compiling_automatically).
|
||||
|
||||
Note that all failures with lessc are reported through exceptions.
|
||||
If you need more control you can make your own instance of lessc.
|
||||
|
||||
$input = 'mystyle.less';
|
||||
|
||||
$lc = new lessc($input);
|
||||
|
||||
try {
|
||||
file_put_contents('mystyle.css', $lc->parse());
|
||||
} catch (exception $ex) { ... }
|
||||
|
||||
In addition to loading from file, you can also parse from a string like so:
|
||||
|
||||
$lc = new lessc();
|
||||
$lesscode = 'body { ... }';
|
||||
$out = $lc->parse($lesscode);
|
||||
|
||||
### How to use from the command line
|
||||
|
||||
An additional script has been included to use the compiler from the command
|
||||
line. In the simplest invocation, you specify an input file and the compiled
|
||||
css is written to standard out:
|
||||
|
||||
$ plessc input.less > output.css
|
||||
|
||||
Using the -r flag, you can specify LESS code directly as an argument or, if
|
||||
the argument is left off, from standard in:
|
||||
|
||||
$ plessc -r "my less code here"
|
||||
|
||||
Finally, by using the -w flag you can watch a specified input file and have it
|
||||
compile as needed to the output file
|
||||
|
||||
$ plessc -w input-file output-file
|
||||
|
||||
Errors from watch mode are written to standard out.
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/php -q
|
||||
<?php
|
||||
|
||||
if (php_sapi_name() != "cli") {
|
||||
err($fa.$argv[0]." must be run in the command line.");
|
||||
exit(1);
|
||||
}
|
||||
$exe = array_shift($argv); // remove filename
|
||||
|
||||
if (!$fname = array_shift($argv)) {
|
||||
exit("Usage: ".$exe." input-file\n");
|
||||
}
|
||||
|
||||
require "lessify.inc.php";
|
||||
|
||||
try {
|
||||
$parser = new lessify($fname);
|
||||
echo $parser->parse();
|
||||
} catch (exception $e) {
|
||||
exit("Fatal error: ".$e->getMessage()."\n");
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,447 @@
|
|||
<?php
|
||||
/**
|
||||
* lessify
|
||||
* Convert a css file into a less file
|
||||
* http://leafo.net/lessphp
|
||||
* Copyright 2010, leaf corcoran <leafot@gmail.com>
|
||||
*
|
||||
* WARNING: THIS DOES NOT WORK ANYMORE. NEEDS TO BE UPDATED FOR
|
||||
* LATEST VERSION OF LESSPHP.
|
||||
*
|
||||
*/
|
||||
|
||||
require "lessc.inc.php";
|
||||
|
||||
//
|
||||
// check if the merge during mixin is overwriting values. should or should it not?
|
||||
//
|
||||
|
||||
//
|
||||
// 1. split apart class tags
|
||||
//
|
||||
|
||||
class easyparse {
|
||||
var $buffer;
|
||||
var $count;
|
||||
|
||||
function __construct($str) {
|
||||
$this->count = 0;
|
||||
$this->buffer = trim($str);
|
||||
}
|
||||
|
||||
function seek($where = null) {
|
||||
if ($where === null) return $this->count;
|
||||
else $this->count = $where;
|
||||
return true;
|
||||
}
|
||||
|
||||
function preg_quote($what) {
|
||||
return preg_quote($what, '/');
|
||||
}
|
||||
|
||||
function match($regex, &$out, $eatWhitespace = true) {
|
||||
$r = '/'.$regex.($eatWhitespace ? '\s*' : '').'/Ais';
|
||||
if (preg_match($r, $this->buffer, $out, null, $this->count)) {
|
||||
$this->count += strlen($out[0]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function literal($what, $eatWhitespace = true) {
|
||||
// this is here mainly prevent notice from { } string accessor
|
||||
if ($this->count >= strlen($this->buffer)) return false;
|
||||
|
||||
// shortcut on single letter
|
||||
if (!$eatWhitespace and strlen($what) == 1) {
|
||||
if ($this->buffer{$this->count} == $what) {
|
||||
$this->count++;
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
return $this->match($this->preg_quote($what), $m, $eatWhitespace);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class tagparse extends easyparse {
|
||||
static private $combinators = null;
|
||||
static private $match_opts = null;
|
||||
|
||||
function parse() {
|
||||
if (empty(self::$combinators)) {
|
||||
self::$combinators = '('.implode('|', array_map(array($this, 'preg_quote'),
|
||||
array('+', '>', '~'))).')';
|
||||
self::$match_opts = '('.implode('|', array_map(array($this, 'preg_quote'),
|
||||
array('=', '~=', '|=', '$=', '*='))).')';
|
||||
}
|
||||
|
||||
// crush whitespace
|
||||
$this->buffer = preg_replace('/\s+/', ' ', $this->buffer).' ';
|
||||
|
||||
$tags = array();
|
||||
while ($this->tag($t)) $tags[] = $t;
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
static function compileString($string) {
|
||||
list(, $delim, $str) = $string;
|
||||
$str = str_replace($delim, "\\".$delim, $str);
|
||||
$str = str_replace("\n", "\\\n", $str);
|
||||
return $delim.$str.$delim;
|
||||
}
|
||||
|
||||
static function compilePaths($paths) {
|
||||
return implode(', ', array_map(array('self', 'compilePath'), $paths));
|
||||
}
|
||||
|
||||
// array of tags
|
||||
static function compilePath($path) {
|
||||
return implode(' ', array_map(array('self', 'compileTag'), $path));
|
||||
}
|
||||
|
||||
|
||||
static function compileTag($tag) {
|
||||
ob_start();
|
||||
if (isset($tag['comb'])) echo $tag['comb']." ";
|
||||
if (isset($tag['front'])) echo $tag['front'];
|
||||
if (isset($tag['attr'])) {
|
||||
echo '['.$tag['attr'];
|
||||
if (isset($tag['op'])) {
|
||||
echo $tag['op'].$tag['op_value'];
|
||||
}
|
||||
echo ']';
|
||||
}
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
function string(&$out) {
|
||||
$s = $this->seek();
|
||||
|
||||
if ($this->literal('"')) {
|
||||
$delim = '"';
|
||||
} elseif ($this->literal("'")) {
|
||||
$delim = "'";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
// step through letters looking for either end or escape
|
||||
$buff = "";
|
||||
$escapeNext = false;
|
||||
$finished = false;
|
||||
for ($i = $this->count; $i < strlen($this->buffer); $i++) {
|
||||
$char = $this->buffer[$i];
|
||||
switch ($char) {
|
||||
case $delim:
|
||||
if ($escapeNext) {
|
||||
$buff .= $char;
|
||||
$escapeNext = false;
|
||||
break;
|
||||
}
|
||||
$finished = true;
|
||||
break 2;
|
||||
case "\\":
|
||||
if ($escapeNext) {
|
||||
$buff .= $char;
|
||||
$escapeNext = false;
|
||||
} else {
|
||||
$escapeNext = true;
|
||||
}
|
||||
break;
|
||||
case "\n":
|
||||
if (!$escapeNext) {
|
||||
break 3;
|
||||
}
|
||||
|
||||
$buff .= $char;
|
||||
$escapeNext = false;
|
||||
break;
|
||||
default:
|
||||
if ($escapeNext) {
|
||||
$buff .= "\\";
|
||||
$escapeNext = false;
|
||||
}
|
||||
$buff .= $char;
|
||||
}
|
||||
}
|
||||
if (!$finished) break;
|
||||
$out = array('string', $delim, $buff);
|
||||
$this->seek($i+1);
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
return false;
|
||||
}
|
||||
|
||||
function tag(&$out) {
|
||||
$s = $this->seek();
|
||||
$tag = array();
|
||||
if ($this->combinator($op)) $tag['comb'] = $op;
|
||||
|
||||
if (!$this->match('(.*?)( |$|\[|'.self::$combinators.')', $match)) {
|
||||
$this->seek($s);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($match[3])) {
|
||||
// give back combinator
|
||||
$this->count-=strlen($match[3]);
|
||||
}
|
||||
|
||||
if (!empty($match[1])) $tag['front'] = $match[1];
|
||||
|
||||
if ($match[2] == '[') {
|
||||
if ($this->ident($i)) {
|
||||
$tag['attr'] = $i;
|
||||
|
||||
if ($this->match(self::$match_opts, $m) && $this->value($v)) {
|
||||
$tag['op'] = $m[1];
|
||||
$tag['op_value'] = $v;
|
||||
}
|
||||
|
||||
if ($this->literal(']')) {
|
||||
$out = $tag;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} elseif (isset($tag['front'])) {
|
||||
$out = $tag;
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
return false;
|
||||
}
|
||||
|
||||
function ident(&$out) {
|
||||
// [-]?{nmstart}{nmchar}*
|
||||
// nmstart: [_a-z]|{nonascii}|{escape}
|
||||
// nmchar: [_a-z0-9-]|{nonascii}|{escape}
|
||||
if ($this->match('(-?[_a-z][_\w]*)', $m)) {
|
||||
$out = $m[1];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function value(&$out) {
|
||||
if ($this->string($str)) {
|
||||
$out = $this->compileString($str);
|
||||
return true;
|
||||
} elseif ($this->ident($id)) {
|
||||
$out = $id;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function combinator(&$op) {
|
||||
if ($this->match(self::$combinators, $m)) {
|
||||
$op = $m[1];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class nodecounter {
|
||||
var $count = 0;
|
||||
var $children = array();
|
||||
|
||||
var $name;
|
||||
var $child_blocks;
|
||||
var $the_block;
|
||||
|
||||
function __construct($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
function dump($stack = null) {
|
||||
if (is_null($stack)) $stack = array();
|
||||
$stack[] = $this->getName();
|
||||
echo implode(' -> ', $stack)." ($this->count)\n";
|
||||
foreach ($this->children as $child) {
|
||||
$child->dump($stack);
|
||||
}
|
||||
}
|
||||
|
||||
static function compileProperties($c, $block) {
|
||||
foreach($block as $name => $value) {
|
||||
if ($c->isProperty($name, $value)) {
|
||||
echo $c->compileProperty($name, $value)."\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function compile($c, $path = null) {
|
||||
if (is_null($path)) $path = array();
|
||||
$path[] = $this->name;
|
||||
|
||||
$isVisible = !is_null($this->the_block) || !is_null($this->child_blocks);
|
||||
|
||||
if ($isVisible) {
|
||||
echo $c->indent(implode(' ', $path).' {');
|
||||
$c->indentLevel++;
|
||||
$path = array();
|
||||
|
||||
if ($this->the_block) {
|
||||
$this->compileProperties($c, $this->the_block);
|
||||
}
|
||||
|
||||
if ($this->child_blocks) {
|
||||
foreach ($this->child_blocks as $block) {
|
||||
echo $c->indent(tagparse::compilePaths($block['__tags']).' {');
|
||||
$c->indentLevel++;
|
||||
$this->compileProperties($c, $block);
|
||||
$c->indentLevel--;
|
||||
echo $c->indent('}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// compile child nodes
|
||||
foreach($this->children as $node) {
|
||||
$node->compile($c, $path);
|
||||
}
|
||||
|
||||
if ($isVisible) {
|
||||
$c->indentLevel--;
|
||||
echo $c->indent('}');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getName() {
|
||||
if (is_null($this->name)) return "[root]";
|
||||
else return $this->name;
|
||||
}
|
||||
|
||||
function getNode($name) {
|
||||
if (!isset($this->children[$name])) {
|
||||
$this->children[$name] = new nodecounter($name);
|
||||
}
|
||||
|
||||
return $this->children[$name];
|
||||
}
|
||||
|
||||
function findNode($path) {
|
||||
$current = $this;
|
||||
for ($i = 0; $i < count($path); $i++) {
|
||||
$t = tagparse::compileTag($path[$i]);
|
||||
$current = $current->getNode($t);
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
function addBlock($path, $block) {
|
||||
$node = $this->findNode($path);
|
||||
if (!is_null($node->the_block)) throw new exception("can this happen?");
|
||||
|
||||
unset($block['__tags']);
|
||||
$node->the_block = $block;
|
||||
}
|
||||
|
||||
function addToNode($path, $block) {
|
||||
$node = $this->findNode($path);
|
||||
$node->child_blocks[] = $block;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* create a less file from a css file by combining blocks where appropriate
|
||||
*/
|
||||
class lessify extends lessc {
|
||||
public function dump() {
|
||||
print_r($this->env);
|
||||
}
|
||||
|
||||
public function parse($str = null) {
|
||||
$this->prepareParser($str ? $str : $this->buffer);
|
||||
while (false !== $this->parseChunk());
|
||||
|
||||
$root = new nodecounter(null);
|
||||
|
||||
// attempt to preserve some of the block order
|
||||
$order = array();
|
||||
|
||||
$visitedTags = array();
|
||||
foreach (end($this->env) as $name => $block) {
|
||||
if (!$this->isBlock($name, $block)) continue;
|
||||
if (isset($visitedTags[$name])) continue;
|
||||
|
||||
foreach ($block['__tags'] as $t) {
|
||||
$visitedTags[$t] = true;
|
||||
}
|
||||
|
||||
// skip those with more than 1
|
||||
if (count($block['__tags']) == 1) {
|
||||
$p = new tagparse(end($block['__tags']));
|
||||
$path = $p->parse();
|
||||
$root->addBlock($path, $block);
|
||||
$order[] = array('compressed', $path, $block);
|
||||
continue;
|
||||
} else {
|
||||
$common = null;
|
||||
$paths = array();
|
||||
foreach ($block['__tags'] as $rawtag) {
|
||||
$p = new tagparse($rawtag);
|
||||
$paths[] = $path = $p->parse();
|
||||
if (is_null($common)) $common = $path;
|
||||
else {
|
||||
$new_common = array();
|
||||
foreach ($path as $tag) {
|
||||
$head = array_shift($common);
|
||||
if ($tag == $head) {
|
||||
$new_common[] = $head;
|
||||
} else break;
|
||||
}
|
||||
$common = $new_common;
|
||||
if (empty($common)) {
|
||||
// nothing in common
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($common)) {
|
||||
$new_paths = array();
|
||||
foreach ($paths as $p) $new_paths[] = array_slice($p, count($common));
|
||||
$block['__tags'] = $new_paths;
|
||||
$root->addToNode($common, $block);
|
||||
$order[] = array('compressed', $common, $block);
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$order[] = array('none', $block['__tags'], $block);
|
||||
}
|
||||
|
||||
|
||||
$compressed = $root->children;
|
||||
foreach ($order as $item) {
|
||||
list($type, $tags, $block) = $item;
|
||||
if ($type == 'compressed') {
|
||||
$top = tagparse::compileTag(reset($tags));
|
||||
if (isset($compressed[$top])) {
|
||||
$compressed[$top]->compile($this);
|
||||
unset($compressed[$top]);
|
||||
}
|
||||
} else {
|
||||
echo $this->indent(implode(', ', $tags).' {');
|
||||
$this->indentLevel++;
|
||||
nodecounter::compileProperties($this, $block);
|
||||
$this->indentLevel--;
|
||||
echo $this->indent('}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/sh
|
||||
|
||||
# creates tar.gz for current version
|
||||
|
||||
VERSION=`./plessc -v | sed -n 's/^v\(.*\)$/\1/p'`
|
||||
OUT_DIR="tmp/lessphp"
|
||||
TMP=`dirname $OUT_DIR`
|
||||
|
||||
mkdir -p $OUT_DIR
|
||||
tar -c `git ls-files` | tar -C $OUT_DIR -x
|
||||
|
||||
rm $OUT_DIR/.gitignore
|
||||
rm $OUT_DIR/package.sh
|
||||
rm $OUT_DIR/lessify
|
||||
rm $OUT_DIR/lessify.inc.php
|
||||
|
||||
OUT_NAME="lessphp-$VERSION.tar.gz"
|
||||
tar -czf $OUT_NAME -C $TMP lessphp/
|
||||
echo "Wrote $OUT_NAME"
|
||||
|
||||
rm -r $TMP
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
#!/usr/bin/php -q
|
||||
<?php
|
||||
//
|
||||
// command line utility to compile less to stdout
|
||||
// leaf corcoran <leafo.net>
|
||||
|
||||
error_reporting(E_ALL);
|
||||
$path = realpath(dirname(__FILE__)).'/';
|
||||
|
||||
require $path."lessc.inc.php";
|
||||
|
||||
$VERSION = lessc::$VERSION;
|
||||
|
||||
$fa = "Fatal Error: ";
|
||||
function err($msg) {
|
||||
fwrite(STDERR, $msg."\n");
|
||||
}
|
||||
|
||||
if (php_sapi_name() != "cli") {
|
||||
err($fa.$argv[0]." must be run in the command line.");
|
||||
exit(1);
|
||||
}
|
||||
$exe = array_shift($argv); // remove filename
|
||||
|
||||
function process($data, $import = null) {
|
||||
global $fa;
|
||||
|
||||
$l = new lessc();
|
||||
if ($import) $l->importDir = $import;
|
||||
try {
|
||||
echo $l->parse($data);
|
||||
exit(0);
|
||||
} catch (exception $ex) {
|
||||
err($fa."\n".str_repeat('=', 20)."\n".
|
||||
$ex->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// process args
|
||||
$opts = array();
|
||||
foreach ($argv as $loc => $a) {
|
||||
if (preg_match("/^-([a-zA-Z]+)$/", $a, $m)) {
|
||||
$m = $m[1];
|
||||
for ($i = 0; $i < strlen($m); $i++)
|
||||
$opts[$m{$i}] = $loc;
|
||||
unset($argv[$loc]);
|
||||
}
|
||||
}
|
||||
|
||||
function has($o, &$loc = null) {
|
||||
global $opts;
|
||||
if (!isset($opts[$o])) return false;
|
||||
$loc = $opts[$o];
|
||||
return true;
|
||||
}
|
||||
|
||||
function hasValue($o, &$value = null) {
|
||||
global $argv;
|
||||
if (!has($o,$loc)) return false;
|
||||
if (!isset($argv[$loc+1])) return false;
|
||||
$value = $argv[$loc+1];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (has("v")) {
|
||||
exit($VERSION."\n");
|
||||
}
|
||||
|
||||
if (has("r", $loc)) {
|
||||
if (!hasValue("r", $data)) {
|
||||
while (!feof(STDIN)) {
|
||||
$data .= fread(STDIN, 8192);
|
||||
}
|
||||
}
|
||||
return process($data);
|
||||
}
|
||||
|
||||
if (has("w")) {
|
||||
// need two files
|
||||
if (!is_file($in = array_shift($argv)) ||
|
||||
null == $out = array_shift($argv))
|
||||
{
|
||||
err($fa.$exe." -w infile outfile");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "Watching ".$in.
|
||||
(has("n") ? ' with notifications' : '').
|
||||
", press Ctrl + c to exit.\n";
|
||||
|
||||
$cache = $in;
|
||||
$last_action = 0;
|
||||
while (1) {
|
||||
clearstatcache();
|
||||
|
||||
// check if anything has changed since last fail
|
||||
$updated = false;
|
||||
if (is_array($cache)) {
|
||||
foreach ($cache['files'] as $fname=>$_) {
|
||||
if (filemtime($fname) > $last_action) {
|
||||
$updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else $updated = true;
|
||||
|
||||
// try to compile it
|
||||
if ($updated) {
|
||||
$last_action = time();
|
||||
|
||||
try {
|
||||
$cache = lessc::cexecute($cache);
|
||||
echo "Writing updated file: ".$out."\n";
|
||||
if (!file_put_contents($out, $cache['compiled'])) {
|
||||
err($fa."Could not write to file ".$out);
|
||||
exit(1);
|
||||
}
|
||||
} catch (exception $ex) {
|
||||
echo "\nFatal Error:\n".str_repeat('=', 20)."\n".$ex->getMessage()."\n\n";
|
||||
|
||||
if (has("n")) {
|
||||
`notify-send -u critical "compile failed" "{$ex->getMessage()}"`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (!$fname = array_shift($argv)) {
|
||||
echo "Usage: ".$exe." input-file [output-file]\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
function dumpValue($node, $depth = 0) {
|
||||
if (is_object($node)) {
|
||||
$indent = str_repeat(" ", $depth);
|
||||
$out = array();
|
||||
foreach ($node->props as $prop) {
|
||||
$out[] = $indent . dumpValue($prop, $depth + 1);
|
||||
}
|
||||
$out = implode("\n", $out);
|
||||
if (!empty($node->tags)) {
|
||||
$out = "+ ".implode(", ", $node->tags)."\n".$out;
|
||||
}
|
||||
return $out;
|
||||
} elseif (is_array($node)) {
|
||||
if (empty($node)) return "[]";
|
||||
$type = $node[0];
|
||||
if ($type == "block")
|
||||
return dumpValue($node[1], $depth);
|
||||
|
||||
$out = array();
|
||||
foreach ($node as $value) {
|
||||
$out[] = dumpValue($value, $depth);
|
||||
}
|
||||
return "{ ".implode(", ", $out)." }";
|
||||
} else {
|
||||
if (is_string($node) && preg_match("/[\s,]/", $node)) {
|
||||
return '"'.$node.'"';
|
||||
}
|
||||
return $node; // normal value
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$l = new lessc($fname);
|
||||
if (has("T") || has("X")) {
|
||||
$t = $l->parseTree();
|
||||
if (has("X"))
|
||||
$out = print_r($t, 1);
|
||||
else
|
||||
$out = dumpValue($t)."\n";
|
||||
} else {
|
||||
$out = $l->parse();
|
||||
}
|
||||
|
||||
if (!$fout = array_shift($argv)) {
|
||||
echo $out;
|
||||
} else {
|
||||
file_put_contents($fout, $out);
|
||||
}
|
||||
|
||||
} catch (exception $ex) {
|
||||
err($fa.$ex->getMessage());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
?>
|
|
@ -0,0 +1,36 @@
|
|||
PHP Markdown & Extra
|
||||
Copyright (c) 2004-2009 Michel Fortin
|
||||
<http://michelf.com/>
|
||||
All rights reserved.
|
||||
|
||||
Based on Markdown
|
||||
Copyright (c) 2003-2006 John Gruber
|
||||
<http://daringfireball.net/>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name "Markdown" nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as
|
||||
is" and any express or implied warranties, including, but not limited
|
||||
to, the implied warranties of merchantability and fitness for a
|
||||
particular purpose are disclaimed. In no event shall the copyright owner
|
||||
or contributors be liable for any direct, indirect, incidental, special,
|
||||
exemplary, or consequential damages (including, but not limited to,
|
||||
procurement of substitute goods or services; loss of use, data, or
|
||||
profits; or business interruption) however caused and on any theory of
|
||||
liability, whether in contract, strict liability, or tort (including
|
||||
negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
|
@ -0,0 +1,802 @@
|
|||
PHP Markdown Extra
|
||||
==================
|
||||
|
||||
Version 1.2.5 - Sun 8 Jan 2012
|
||||
|
||||
by Michel Fortin
|
||||
<http://michelf.com/>
|
||||
|
||||
based on Markdown by John Gruber
|
||||
<http://daringfireball.net/>
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This is a special version of PHP Markdown with extra features. See
|
||||
<http://michelf.com/projects/php-markdown/extra/> for details.
|
||||
|
||||
Markdown is a text-to-HTML conversion tool for web writers. Markdown
|
||||
allows you to write using an easy-to-read, easy-to-write plain text
|
||||
format, then convert it to structurally valid XHTML (or HTML).
|
||||
|
||||
"Markdown" is two things: a plain text markup syntax, and a software
|
||||
tool, written in Perl, that converts the plain text markup to HTML.
|
||||
PHP Markdown is a port to PHP of the original Markdown program by
|
||||
John Gruber.
|
||||
|
||||
PHP Markdown can work as a plug-in for WordPress and bBlog, as a
|
||||
modifier for the Smarty templating engine, or as a remplacement for
|
||||
textile formatting in any software that support textile.
|
||||
|
||||
Full documentation of Markdown's syntax is available on John's
|
||||
Markdown page: <http://daringfireball.net/projects/markdown/>
|
||||
|
||||
|
||||
Installation and Requirement
|
||||
----------------------------
|
||||
|
||||
PHP Markdown requires PHP version 4.0.5 or later.
|
||||
|
||||
|
||||
### WordPress ###
|
||||
|
||||
PHP Markdown works with [WordPress][wp], version 1.2 or later.
|
||||
|
||||
[wp]: http://wordpress.org/
|
||||
|
||||
1. To use PHP Markdown with WordPress, place the "makrdown.php" file
|
||||
in the "plugins" folder. This folder is located inside
|
||||
"wp-content" at the root of your site:
|
||||
|
||||
(site home)/wp-content/plugins/
|
||||
|
||||
2. Activate the plugin with the administrative interface of
|
||||
WordPress. In the "Plugins" section you will now find Markdown.
|
||||
To activate the plugin, click on the "Activate" button on the
|
||||
same line than Markdown. Your entries will now be formatted by
|
||||
PHP Markdown.
|
||||
|
||||
3. To post Markdown content, you'll first have to disable the
|
||||
"visual" editor in the User section of WordPress.
|
||||
|
||||
You can configure PHP Markdown to not apply to the comments on your
|
||||
WordPress weblog. See the "Configuration" section below.
|
||||
|
||||
It is not possible at this time to apply a different set of
|
||||
filters to different entries. All your entries will be formated by
|
||||
PHP Markdown. This is a limitation of WordPress. If your old entries
|
||||
are written in HTML (as opposed to another formatting syntax, like
|
||||
Textile), they'll probably stay fine after installing Markdown.
|
||||
|
||||
|
||||
### bBlog ###
|
||||
|
||||
PHP Markdown also works with [bBlog][bb].
|
||||
|
||||
[bb]: http://www.bblog.com/
|
||||
|
||||
To use PHP Markdown with bBlog, rename "markdown.php" to
|
||||
"modifier.markdown.php" and place the file in the "bBlog_plugins"
|
||||
folder. This folder is located inside the "bblog" directory of
|
||||
your site, like this:
|
||||
|
||||
(site home)/bblog/bBlog_plugins/modifier.markdown.php
|
||||
|
||||
Select "Markdown" as the "Entry Modifier" when you post a new
|
||||
entry. This setting will only apply to the entry you are editing.
|
||||
|
||||
|
||||
### Replacing Textile in TextPattern ###
|
||||
|
||||
[TextPattern][tp] use [Textile][tx] to format your text. You can
|
||||
replace Textile by Markdown in TextPattern without having to change
|
||||
any code by using the *Texitle Compatibility Mode*. This may work
|
||||
with other software that expect Textile too.
|
||||
|
||||
[tx]: http://www.textism.com/tools/textile/
|
||||
[tp]: http://www.textpattern.com/
|
||||
|
||||
1. Rename the "markdown.php" file to "classTextile.php". This will
|
||||
make PHP Markdown behave as if it was the actual Textile parser.
|
||||
|
||||
2. Replace the "classTextile.php" file TextPattern installed in your
|
||||
web directory. It can be found in the "lib" directory:
|
||||
|
||||
(site home)/textpattern/lib/
|
||||
|
||||
Contrary to Textile, Markdown does not convert quotes to curly ones
|
||||
and does not convert multiple hyphens (`--` and `---`) into en- and
|
||||
em-dashes. If you use PHP Markdown in Textile Compatibility Mode, you
|
||||
can solve this problem by installing the "smartypants.php" file from
|
||||
[PHP SmartyPants][psp] beside the "classTextile.php" file. The Textile
|
||||
Compatibility Mode function will use SmartyPants automatically without
|
||||
further modification.
|
||||
|
||||
[psp]: http://michelf.com/projects/php-smartypants/
|
||||
|
||||
|
||||
### In Your Own Programs ###
|
||||
|
||||
You can use PHP Markdown easily in your current PHP program. Simply
|
||||
include the file and then call the Markdown function on the text you
|
||||
want to convert:
|
||||
|
||||
include_once "markdown.php";
|
||||
$my_html = Markdown($my_text);
|
||||
|
||||
If you wish to use PHP Markdown with another text filter function
|
||||
built to parse HTML, you should filter the text *after* the Markdown
|
||||
function call. This is an example with [PHP SmartyPants][psp]:
|
||||
|
||||
$my_html = SmartyPants(Markdown($my_text));
|
||||
|
||||
|
||||
### With Smarty ###
|
||||
|
||||
If your program use the [Smarty][sm] template engine, PHP Markdown
|
||||
can now be used as a modifier for your templates. Rename "markdown.php"
|
||||
to "modifier.markdown.php" and put it in your smarty plugins folder.
|
||||
|
||||
[sm]: http://smarty.php.net/
|
||||
|
||||
If you are using MovableType 3.1 or later, the Smarty plugin folder is
|
||||
located at `(MT CGI root)/php/extlib/smarty/plugins`. This will allow
|
||||
Markdown to work on dynamic pages.
|
||||
|
||||
|
||||
### Updating Markdown in Other Programs ###
|
||||
|
||||
Many web applications now ship with PHP Markdown, or have plugins to
|
||||
perform the conversion to HTML. You can update PHP Markdown -- or
|
||||
replace it with PHP Markdown Extra -- in many of these programs by
|
||||
swapping the old "markdown.php" file for the new one.
|
||||
|
||||
Here is a short non-exhaustive list of some programs and where they
|
||||
hide the "markdown.php" file.
|
||||
|
||||
| Program | Path to Markdown
|
||||
| ------- | ----------------
|
||||
| [Pivot][] | `(site home)/pivot/includes/markdown/`
|
||||
|
||||
If you're unsure if you can do this with your application, ask the
|
||||
developer, or wait for the developer to update his application or
|
||||
plugin with the new version of PHP Markdown.
|
||||
|
||||
[Pivot]: http://pivotlog.net/
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
By default, PHP Markdown produces XHTML output for tags with empty
|
||||
elements. E.g.:
|
||||
|
||||
<br />
|
||||
|
||||
Markdown can be configured to produce HTML-style tags; e.g.:
|
||||
|
||||
<br>
|
||||
|
||||
To do this, you must edit the "MARKDOWN_EMPTY_ELEMENT_SUFFIX"
|
||||
definition below the "Global default settings" header at the start of
|
||||
the "markdown.php" file.
|
||||
|
||||
|
||||
### WordPress-Specific Settings ###
|
||||
|
||||
By default, the Markdown plugin applies to both posts and comments on
|
||||
your WordPress weblog. To deactivate one or the other, edit the
|
||||
`MARKDOWN_WP_POSTS` or `MARKDOWN_WP_COMMENTS` definitions under the
|
||||
"WordPress settings" header at the start of the "markdown.php" file.
|
||||
|
||||
|
||||
Bugs
|
||||
----
|
||||
|
||||
To file bug reports please send email to:
|
||||
<michel.fortin@michelf.com>
|
||||
|
||||
Please include with your report: (1) the example input; (2) the output you
|
||||
expected; (3) the output PHP Markdown actually produced.
|
||||
|
||||
|
||||
Version History
|
||||
---------------
|
||||
|
||||
1.0.1o (8 Jan 2012):
|
||||
|
||||
* Silenced a new warning introduced around PHP 5.3 complaining about
|
||||
POSIX characters classes not being implemented. PHP Markdown does not
|
||||
use POSIX character classes, but it nevertheless trigged that warning.
|
||||
|
||||
|
||||
Extra 1.2.5 (8 Jan 2012):
|
||||
|
||||
* Fixed an issue preventing fenced code blocks indented inside lists items
|
||||
and elsewhere from being interpreted correctly.
|
||||
|
||||
* Fixed an issue where HTML tags inside fenced code blocks were sometime
|
||||
not encoded with entities.
|
||||
|
||||
|
||||
1.0.1n (10 Oct 2009):
|
||||
|
||||
* Enabled reference-style shortcut links. Now you can write reference-style
|
||||
links with less brakets:
|
||||
|
||||
This is [my website].
|
||||
|
||||
[my website]: http://example.com/
|
||||
|
||||
This was added in the 1.0.2 betas, but commented out in the 1.0.1 branch,
|
||||
waiting for the feature to be officialized. [But half of the other Markdown
|
||||
implementations are supporting this syntax][half], so it makes sense for
|
||||
compatibility's sake to allow it in PHP Markdown too.
|
||||
|
||||
[half]: http://babelmark.bobtfish.net/?markdown=This+is+%5Bmy+website%5D.%0D%0A%09%09%0D%0A%5Bmy+website%5D%3A+http%3A%2F%2Fexample.com%2F%0D%0A&src=1&dest=2
|
||||
|
||||
* Now accepting many valid email addresses in autolinks that were
|
||||
previously rejected, such as:
|
||||
|
||||
<abc+mailbox/department=shipping@example.com>
|
||||
<!#$%&'*+-/=?^_`.{|}~@example.com>
|
||||
<"abc@def"@example.com>
|
||||
<"Fred Bloggs"@example.com>
|
||||
<jsmith@[192.0.2.1]>
|
||||
|
||||
* Now accepting spaces in URLs for inline and reference-style links. Such
|
||||
URLs need to be surrounded by angle brakets. For instance:
|
||||
|
||||
[link text](<http://url/with space> "optional title")
|
||||
|
||||
[link text][ref]
|
||||
[ref]: <http://url/with space> "optional title"
|
||||
|
||||
There is still a quirk which may prevent this from working correctly with
|
||||
relative URLs in inline-style links however.
|
||||
|
||||
* Fix for adjacent list of different kind where the second list could
|
||||
end as a sublist of the first when not separated by an empty line.
|
||||
|
||||
* Fixed a bug where inline-style links wouldn't be recognized when the link
|
||||
definition contains a line break between the url and the title.
|
||||
|
||||
* Fixed a bug where tags where the name contains an underscore aren't parsed
|
||||
correctly.
|
||||
|
||||
* Fixed some corner-cases mixing underscore-ephasis and asterisk-emphasis.
|
||||
|
||||
|
||||
Extra 1.2.4 (10 Oct 2009):
|
||||
|
||||
* Fixed a problem where unterminated tags in indented code blocks could
|
||||
prevent proper escaping of characaters in the code block.
|
||||
|
||||
|
||||
Extra 1.2.3 (31 Dec 2008):
|
||||
|
||||
* In WordPress pages featuring more than one post, footnote id prefixes are
|
||||
now automatically applied with the current post ID to avoid clashes
|
||||
between footnotes belonging to different posts.
|
||||
|
||||
* Fix for a bug introduced in Extra 1.2 where block-level HTML tags where
|
||||
not detected correctly, thus the addition of erroneous `<p>` tags and
|
||||
interpretation of their content as Markdown-formatted instead of
|
||||
HTML-formatted.
|
||||
|
||||
|
||||
Extra 1.2.2 (21 Jun 2008):
|
||||
|
||||
* Fixed a problem where abbreviation definitions, footnote
|
||||
definitions and link references were stripped inside
|
||||
fenced code blocks.
|
||||
|
||||
* Fixed a bug where characters such as `"` in abbreviation
|
||||
definitions weren't properly encoded to HTML entities.
|
||||
|
||||
* Fixed a bug where double quotes `"` were not correctly encoded
|
||||
as HTML entities when used inside a footnote reference id.
|
||||
|
||||
|
||||
1.0.1m (21 Jun 2008):
|
||||
|
||||
* Lists can now have empty items.
|
||||
|
||||
* Rewrote the emphasis and strong emphasis parser to fix some issues
|
||||
with odly placed and overlong markers.
|
||||
|
||||
|
||||
Extra 1.2.1 (27 May 2008):
|
||||
|
||||
* Fixed a problem where Markdown headers and horizontal rules were
|
||||
transformed into their HTML equivalent inside fenced code blocks.
|
||||
|
||||
|
||||
Extra 1.2 (11 May 2008):
|
||||
|
||||
* Added fenced code block syntax which don't require indentation
|
||||
and can start and end with blank lines. A fenced code block
|
||||
starts with a line of consecutive tilde (~) and ends on the
|
||||
next line with the same number of consecutive tilde. Here's an
|
||||
example:
|
||||
|
||||
~~~~~~~~~~~~
|
||||
Hello World!
|
||||
~~~~~~~~~~~~
|
||||
|
||||
* Rewrote parts of the HTML block parser to better accomodate
|
||||
fenced code blocks.
|
||||
|
||||
* Footnotes may now be referenced from within another footnote.
|
||||
|
||||
* Added programatically-settable parser property `predef_attr` for
|
||||
predefined attribute definitions.
|
||||
|
||||
* Fixed an issue where an indented code block preceded by a blank
|
||||
line containing some other whitespace would confuse the HTML
|
||||
block parser into creating an HTML block when it should have
|
||||
been code.
|
||||
|
||||
|
||||
1.0.1l (11 May 2008):
|
||||
|
||||
* Now removing the UTF-8 BOM at the start of a document, if present.
|
||||
|
||||
* Now accepting capitalized URI schemes (such as HTTP:) in automatic
|
||||
links, such as `<HTTP://EXAMPLE.COM/>`.
|
||||
|
||||
* Fixed a problem where `<hr@example.com>` was seen as a horizontal
|
||||
rule instead of an automatic link.
|
||||
|
||||
* Fixed an issue where some characters in Markdown-generated HTML
|
||||
attributes weren't properly escaped with entities.
|
||||
|
||||
* Fix for code blocks as first element of a list item. Previously,
|
||||
this didn't create any code block for item 2:
|
||||
|
||||
* Item 1 (regular paragraph)
|
||||
|
||||
* Item 2 (code block)
|
||||
|
||||
* A code block starting on the second line of a document wasn't seen
|
||||
as a code block. This has been fixed.
|
||||
|
||||
* Added programatically-settable parser properties `predef_urls` and
|
||||
`predef_titles` for predefined URLs and titles for reference-style
|
||||
links. To use this, your PHP code must call the parser this way:
|
||||
|
||||
$parser = new Markdwon_Parser;
|
||||
$parser->predef_urls = array('linkref' => 'http://example.com');
|
||||
$html = $parser->transform($text);
|
||||
|
||||
You can then use the URL as a normal link reference:
|
||||
|
||||
[my link][linkref]
|
||||
[my link][linkRef]
|
||||
|
||||
Reference names in the parser properties *must* be lowercase.
|
||||
Reference names in the Markdown source may have any case.
|
||||
|
||||
* Added `setup` and `teardown` methods which can be used by subclassers
|
||||
as hook points to arrange the state of some parser variables before and
|
||||
after parsing.
|
||||
|
||||
|
||||
Extra 1.1.7 (26 Sep 2007):
|
||||
|
||||
1.0.1k (26 Sep 2007):
|
||||
|
||||
* Fixed a problem introduced in 1.0.1i where three or more identical
|
||||
uppercase letters, as well as a few other symbols, would trigger
|
||||
a horizontal line.
|
||||
|
||||
|
||||
Extra 1.1.6 (4 Sep 2007):
|
||||
|
||||
1.0.1j (4 Sep 2007):
|
||||
|
||||
* Fixed a problem introduced in 1.0.1i where the closing `code` and
|
||||
`pre` tags at the end of a code block were appearing in the wrong
|
||||
order.
|
||||
|
||||
* Overriding configuration settings by defining constants from an
|
||||
external before markdown.php is included is now possible without
|
||||
producing a PHP warning.
|
||||
|
||||
|
||||
Extra 1.1.5 (31 Aug 2007):
|
||||
|
||||
1.0.1i (31 Aug 2007):
|
||||
|
||||
* Fixed a problem where an escaped backslash before a code span
|
||||
would prevent the code span from being created. This should now
|
||||
work as expected:
|
||||
|
||||
Litteral backslash: \\`code span`
|
||||
|
||||
* Overall speed improvements, especially with long documents.
|
||||
|
||||
|
||||
Extra 1.1.4 (3 Aug 2007):
|
||||
|
||||
1.0.1h (3 Aug 2007):
|
||||
|
||||
* Added two properties (`no_markup` and `no_entities`) to the parser
|
||||
allowing HTML tags and entities to be disabled.
|
||||
|
||||
* Fix for a problem introduced in 1.0.1g where posting comments in
|
||||
WordPress would trigger PHP warnings and cause some markup to be
|
||||
incorrectly filtered by the kses filter in WordPress.
|
||||
|
||||
|
||||
Extra 1.1.3 (3 Jul 2007):
|
||||
|
||||
* Fixed a performance problem when parsing some invalid HTML as an HTML
|
||||
block which was resulting in too much recusion and a segmentation fault
|
||||
for long documents.
|
||||
|
||||
* The markdown="" attribute now accepts unquoted values.
|
||||
|
||||
* Fixed an issue where underscore-emphasis didn't work when applied on the
|
||||
first or the last word of an element having the markdown="1" or
|
||||
markdown="span" attribute set unless there was some surrounding whitespace.
|
||||
This didn't work:
|
||||
|
||||
<p markdown="1">_Hello_ _world_</p>
|
||||
|
||||
Now it does produce emphasis as expected.
|
||||
|
||||
* Fixed an issue preventing footnotes from working when the parser's
|
||||
footnote id prefix variable (fn_id_prefix) is not empty.
|
||||
|
||||
* Fixed a performance problem where the regular expression for strong
|
||||
emphasis introduced in version 1.1 could sometime be long to process,
|
||||
give slightly wrong results, and in some circumstances could remove
|
||||
entirely the content for a whole paragraph.
|
||||
|
||||
* Fixed an issue were abbreviations tags could be incorrectly added
|
||||
inside URLs and title of links.
|
||||
|
||||
* Placing footnote markers inside a link, resulting in two nested links, is
|
||||
no longer allowed.
|
||||
|
||||
|
||||
1.0.1g (3 Jul 2007):
|
||||
|
||||
* Fix for PHP 5 compiled without the mbstring module. Previous fix to
|
||||
calculate the length of UTF-8 strings in `detab` when `mb_strlen` is
|
||||
not available was only working with PHP 4.
|
||||
|
||||
* Fixed a problem with WordPress 2.x where full-content posts in RSS feeds
|
||||
were not processed correctly by Markdown.
|
||||
|
||||
* Now supports URLs containing literal parentheses for inline links
|
||||
and images, such as:
|
||||
|
||||
[WIMP](http://en.wikipedia.org/wiki/WIMP_(computing))
|
||||
|
||||
Such parentheses may be arbitrarily nested, but must be
|
||||
balanced. Unbalenced parentheses are allowed however when the URL
|
||||
when escaped or when the URL is enclosed in angle brakets `<>`.
|
||||
|
||||
* Fixed a performance problem where the regular expression for strong
|
||||
emphasis introduced in version 1.0.1d could sometime be long to process,
|
||||
give slightly wrong results, and in some circumstances could remove
|
||||
entirely the content for a whole paragraph.
|
||||
|
||||
* Some change in version 1.0.1d made possible the incorrect nesting of
|
||||
anchors within each other. This is now fixed.
|
||||
|
||||
* Fixed a rare issue where certain MD5 hashes in the content could
|
||||
be changed to their corresponding text. For instance, this:
|
||||
|
||||
The MD5 value for "+" is "26b17225b626fb9238849fd60eabdf60".
|
||||
|
||||
was incorrectly changed to this in previous versions of PHP Markdown:
|
||||
|
||||
<p>The MD5 value for "+" is "+".</p>
|
||||
|
||||
* Now convert escaped characters to their numeric character
|
||||
references equivalent.
|
||||
|
||||
This fix an integration issue with SmartyPants and backslash escapes.
|
||||
Since Markdown and SmartyPants have some escapable characters in common,
|
||||
it was sometime necessary to escape them twice. Previously, two
|
||||
backslashes were sometime required to prevent Markdown from "eating" the
|
||||
backslash before SmartyPants sees it:
|
||||
|
||||
Here are two hyphens: \\--
|
||||
|
||||
Now, only one backslash will do:
|
||||
|
||||
Here are two hyphens: \--
|
||||
|
||||
|
||||
Extra 1.1.2 (7 Feb 2007)
|
||||
|
||||
* Fixed an issue where headers preceded too closely by a paragraph
|
||||
(with no blank line separating them) where put inside the paragraph.
|
||||
|
||||
* Added the missing TextileRestricted method that was added to regular
|
||||
PHP Markdown since 1.0.1d but which I forgot to add to Extra.
|
||||
|
||||
|
||||
1.0.1f (7 Feb 2007):
|
||||
|
||||
* Fixed an issue with WordPress where manually-entered excerpts, but
|
||||
not the auto-generated ones, would contain nested paragraphs.
|
||||
|
||||
* Fixed an issue introduced in 1.0.1d where headers and blockquotes
|
||||
preceded too closely by a paragraph (not separated by a blank line)
|
||||
where incorrectly put inside the paragraph.
|
||||
|
||||
* Fixed an issue introduced in 1.0.1d in the tokenizeHTML method where
|
||||
two consecutive code spans would be merged into one when together they
|
||||
form a valid tag in a multiline paragraph.
|
||||
|
||||
* Fixed an long-prevailing issue where blank lines in code blocks would
|
||||
be doubled when the code block is in a list item.
|
||||
|
||||
This was due to the list processing functions relying on artificially
|
||||
doubled blank lines to correctly determine when list items should
|
||||
contain block-level content. The list item processing model was thus
|
||||
changed to avoid the need for double blank lines.
|
||||
|
||||
* Fixed an issue with `<% asp-style %>` instructions used as inline
|
||||
content where the opening `<` was encoded as `<`.
|
||||
|
||||
* Fixed a parse error occuring when PHP is configured to accept
|
||||
ASP-style delimiters as boundaries for PHP scripts.
|
||||
|
||||
* Fixed a bug introduced in 1.0.1d where underscores in automatic links
|
||||
got swapped with emphasis tags.
|
||||
|
||||
|
||||
Extra 1.1.1 (28 Dec 2006)
|
||||
|
||||
* Fixed a problem where whitespace at the end of the line of an atx-style
|
||||
header would cause tailing `#` to appear as part of the header's content.
|
||||
This was caused by a small error in the regex that handles the definition
|
||||
for the id attribute in PHP Markdown Extra.
|
||||
|
||||
* Fixed a problem where empty abbreviations definitions would eat the
|
||||
following line as its definition.
|
||||
|
||||
* Fixed an issue with calling the Markdown parser repetitivly with text
|
||||
containing footnotes. The footnote hashes were not reinitialized properly.
|
||||
|
||||
|
||||
1.0.1e (28 Dec 2006)
|
||||
|
||||
* Added support for internationalized domain names for email addresses in
|
||||
automatic link. Improved the speed at which email addresses are converted
|
||||
to entities. Thanks to Milian Wolff for his optimisations.
|
||||
|
||||
* Made deterministic the conversion to entities of email addresses in
|
||||
automatic links. This means that a given email address will always be
|
||||
encoded the same way.
|
||||
|
||||
* PHP Markdown will now use its own function to calculate the length of an
|
||||
UTF-8 string in `detab` when `mb_strlen` is not available instead of
|
||||
giving a fatal error.
|
||||
|
||||
|
||||
Extra 1.1 (1 Dec 2006)
|
||||
|
||||
* Added a syntax for footnotes.
|
||||
|
||||
* Added an experimental syntax to define abbreviations.
|
||||
|
||||
|
||||
1.0.1d (1 Dec 2006)
|
||||
|
||||
* Fixed a bug where inline images always had an empty title attribute. The
|
||||
title attribute is now present only when explicitly defined.
|
||||
|
||||
* Link references definitions can now have an empty title, previously if the
|
||||
title was defined but left empty the link definition was ignored. This can
|
||||
be useful if you want an empty title attribute in images to hide the
|
||||
tooltip in Internet Explorer.
|
||||
|
||||
* Made `detab` aware of UTF-8 characters. UTF-8 multi-byte sequences are now
|
||||
correctly mapped to one character instead of the number of bytes.
|
||||
|
||||
* Fixed a small bug with WordPress where WordPress' default filter `wpautop`
|
||||
was not properly deactivated on comment text, resulting in hard line breaks
|
||||
where Markdown do not prescribes them.
|
||||
|
||||
* Added a `TextileRestrited` method to the textile compatibility mode. There
|
||||
is no restriction however, as Markdown does not have a restricted mode at
|
||||
this point. This should make PHP Markdown work again in the latest
|
||||
versions of TextPattern.
|
||||
|
||||
* Converted PHP Markdown to a object-oriented design.
|
||||
|
||||
* Changed span and block gamut methods so that they loop over a
|
||||
customizable list of methods. This makes subclassing the parser a more
|
||||
interesting option for creating syntax extensions.
|
||||
|
||||
* Also added a "document" gamut loop which can be used to hook document-level
|
||||
methods (like for striping link definitions).
|
||||
|
||||
* Changed all methods which were inserting HTML code so that they now return
|
||||
a hashed representation of the code. New methods `hashSpan` and `hashBlock`
|
||||
are used to hash respectivly span- and block-level generated content. This
|
||||
has a couple of significant effects:
|
||||
|
||||
1. It prevents invalid nesting of Markdown-generated elements which
|
||||
could occur occuring with constructs like `*something [link*][1]`.
|
||||
2. It prevents problems occuring with deeply nested lists on which
|
||||
paragraphs were ill-formed.
|
||||
3. It removes the need to call `hashHTMLBlocks` twice during the the
|
||||
block gamut.
|
||||
|
||||
Hashes are turned back to HTML prior output.
|
||||
|
||||
* Made the block-level HTML parser smarter using a specially-crafted regular
|
||||
expression capable of handling nested tags.
|
||||
|
||||
* Solved backtick issues in tag attributes by rewriting the HTML tokenizer to
|
||||
be aware of code spans. All these lines should work correctly now:
|
||||
|
||||
<span attr='`ticks`'>bar</span>
|
||||
<span attr='``double ticks``'>bar</span>
|
||||
`<test a="` content of attribute `">`
|
||||
|
||||
* Changed the parsing of HTML comments to match simply from `<!--` to `-->`
|
||||
instead using of the more complicated SGML-style rule with paired `--`.
|
||||
This is how most browsers parse comments and how XML defines them too.
|
||||
|
||||
* `<address>` has been added to the list of block-level elements and is now
|
||||
treated as an HTML block instead of being wrapped within paragraph tags.
|
||||
|
||||
* Now only trim trailing newlines from code blocks, instead of trimming
|
||||
all trailing whitespace characters.
|
||||
|
||||
* Fixed bug where this:
|
||||
|
||||
[text](http://m.com "title" )
|
||||
|
||||
wasn't working as expected, because the parser wasn't allowing for spaces
|
||||
before the closing paren.
|
||||
|
||||
* Filthy hack to support markdown='1' in div tags.
|
||||
|
||||
* _DoAutoLinks() now supports the 'dict://' URL scheme.
|
||||
|
||||
* PHP- and ASP-style processor instructions are now protected as
|
||||
raw HTML blocks.
|
||||
|
||||
<? ... ?>
|
||||
<% ... %>
|
||||
|
||||
* Fix for escaped backticks still triggering code spans:
|
||||
|
||||
There are two raw backticks here: \` and here: \`, not a code span
|
||||
|
||||
|
||||
Extra 1.0 - 5 September 2005
|
||||
|
||||
* Added support for setting the id attributes for headers like this:
|
||||
|
||||
Header 1 {#header1}
|
||||
========
|
||||
|
||||
## Header 2 ## {#header2}
|
||||
|
||||
This only work only for headers for now.
|
||||
|
||||
* Tables will now work correctly as the first element of a definition
|
||||
list. For example, this input:
|
||||
|
||||
Term
|
||||
|
||||
: Header | Header
|
||||
------- | -------
|
||||
Cell | Cell
|
||||
|
||||
used to produce no definition list and a table where the first
|
||||
header was named ": Header". This is now fixed.
|
||||
|
||||
* Fix for a problem where a paragraph following a table was not
|
||||
placed between `<p>` tags.
|
||||
|
||||
|
||||
Extra 1.0b4 - 1 August 2005
|
||||
|
||||
* Fixed some issues where whitespace around HTML blocks were trigging
|
||||
empty paragraph tags.
|
||||
|
||||
* Fixed an HTML block parsing issue that would cause a block element
|
||||
following a code span or block with unmatched opening bracket to be
|
||||
placed inside a paragraph.
|
||||
|
||||
* Removed some PHP notices that could appear when parsing definition
|
||||
lists and tables with PHP notice reporting flag set.
|
||||
|
||||
|
||||
Extra 1.0b3 - 29 July 2005
|
||||
|
||||
* Definition lists now require a blank line before each term. Solves
|
||||
an ambiguity where the last line of lazy-indented definitions could
|
||||
be mistaken by PHP Markdown as a new term in the list.
|
||||
|
||||
* Definition lists now support multiple terms per definition.
|
||||
|
||||
* Some special tags were replaced in the output by their md5 hash
|
||||
key. Things such as this now work as expected:
|
||||
|
||||
## Header <?php echo $number ?> ##
|
||||
|
||||
|
||||
Extra 1.0b2 - 26 July 2005
|
||||
|
||||
* Definition lists can now take two or more definitions for one term.
|
||||
This should have been the case before, but a bug prevented this
|
||||
from working right.
|
||||
|
||||
* Fixed a problem where single column table with a pipe only at the
|
||||
end where not parsed as table. Here is such a table:
|
||||
|
||||
| header
|
||||
| ------
|
||||
| cell
|
||||
|
||||
* Fixed problems with empty cells in the first column of a table with
|
||||
no leading pipe, like this one:
|
||||
|
||||
header | header
|
||||
------ | ------
|
||||
| cell
|
||||
|
||||
* Code spans containing pipes did not within a table. This is now
|
||||
fixed by parsing code spans before splitting rows into cells.
|
||||
|
||||
* Added the pipe character to the backlash escape character lists.
|
||||
|
||||
Extra 1.0b1 (25 Jun 2005)
|
||||
|
||||
* First public release of PHP Markdown Extra.
|
||||
|
||||
|
||||
Copyright and License
|
||||
---------------------
|
||||
|
||||
PHP Markdown & Extra
|
||||
Copyright (c) 2004-2009 Michel Fortin
|
||||
<http://michelf.com/>
|
||||
All rights reserved.
|
||||
|
||||
Based on Markdown
|
||||
Copyright (c) 2003-2005 John Gruber
|
||||
<http://daringfireball.net/>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
* Neither the name "Markdown" nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as
|
||||
is" and any express or implied warranties, including, but not limited
|
||||
to, the implied warranties of merchantability and fitness for a
|
||||
particular purpose are disclaimed. In no event shall the copyright owner
|
||||
or contributors be liable for any direct, indirect, incidental, special,
|
||||
exemplary, or consequential damages (including, but not limited to,
|
||||
procurement of substitute goods or services; loss of use, data, or
|
||||
profits; or business interruption) however caused and on any theory of
|
||||
liability, whether in contract, strict liability, or tort (including
|
||||
negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,36 @@
|
|||
PHP SmartyPants & Typographer
|
||||
Copyright (c) 2005-2006 Michel Fortin
|
||||
<http://www.michelf.com/>
|
||||
All rights reserved.
|
||||
|
||||
Original SmartyPants
|
||||
Copyright (c) 2003-2004 John Gruber
|
||||
<http://daringfireball.net/>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name "SmartyPants" nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as
|
||||
is" and any express or implied warranties, including, but not limited
|
||||
to, the implied warranties of merchantability and fitness for a
|
||||
particular purpose are disclaimed. In no event shall the copyright owner
|
||||
or contributors be liable for any direct, indirect, incidental, special,
|
||||
exemplary, or consequential damages (including, but not limited to,
|
||||
procurement of substitute goods or services; loss of use, data, or
|
||||
profits; or business interruption) however caused and on any theory of
|
||||
liability, whether in contract, strict liability, or tort (including
|
||||
negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
|
@ -0,0 +1,261 @@
|
|||
PHP SmartyPants Typographer
|
||||
===========================
|
||||
|
||||
Version 1.0 - Wed 28 Jun 2006
|
||||
|
||||
by Michel Fortin
|
||||
<http://www.michelf.com/>
|
||||
|
||||
Original SmartyPants by John Gruber
|
||||
<http://daringfireball.net/>
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This is a special version of PHP SmartyPants with extra features. See
|
||||
<http://www.michelf.com/projects/php-smartypants/typographer/> for
|
||||
details.
|
||||
|
||||
PHP SmartyPants is a free web publishing plug-in for WordPress and
|
||||
Smarty template engine that easily translates plain ASCII punctuation
|
||||
characters into "smart" typographic punctuation HTML entities.
|
||||
SmartyPants can also be invoked as a standalone PHP function.
|
||||
|
||||
PHP SmartyPants is a port to PHP of the original SmartyPants written
|
||||
in Perl by John Gruber.
|
||||
|
||||
SmartyPants can perform the following transformations:
|
||||
|
||||
* Straight quotes (`"` and `'`) into "curly" quote HTML entities
|
||||
* Backtick-style quotes (` ``like this'' `) into "curly" quote HTML
|
||||
entities
|
||||
* Dashes (`--` and `---`) into en- and em-dash entities
|
||||
* Three consecutive dots (`...`) into an ellipsis entity
|
||||
|
||||
This means you can write, edit, and save using plain old ASCII straight
|
||||
quotes, plain dashes, and plain dots, but your published posts (and
|
||||
final HTML output) will appear with smart quotes, em-dashes, and proper
|
||||
ellipses.
|
||||
|
||||
SmartyPants does not modify characters within `<pre>`, `<code>`,
|
||||
`<kbd>`, or `<script>` tag blocks. Typically, these tags are used to
|
||||
display text where smart quotes and other "smart punctuation" would not
|
||||
be appropriate, such as source code or example markup.
|
||||
|
||||
|
||||
### Backslash Escapes ###
|
||||
|
||||
If you need to use literal straight quotes (or plain hyphens and
|
||||
periods), SmartyPants accepts the following backslash escape sequences
|
||||
to force non-smart punctuation. It does so by transforming the escape
|
||||
sequence into a decimal-encoded HTML entity:
|
||||
|
||||
|
||||
Escape Value Character
|
||||
------ ----- ---------
|
||||
\\ \ \
|
||||
\" " "
|
||||
\' ' '
|
||||
\. . .
|
||||
\- - -
|
||||
\` ` `
|
||||
|
||||
|
||||
This is useful, for example, when you want to use straight quotes as
|
||||
foot and inch marks:
|
||||
|
||||
6\'2\" tall
|
||||
|
||||
translates into:
|
||||
|
||||
6'2" tall
|
||||
|
||||
in SmartyPants's HTML output. Which, when rendered by a web browser,
|
||||
looks like:
|
||||
|
||||
6'2" tall
|
||||
|
||||
|
||||
Installation and Requirement
|
||||
----------------------------
|
||||
|
||||
PHP SmartyPants require PHP version 4.0.5 or later.
|
||||
|
||||
|
||||
### WordPress ###
|
||||
|
||||
WordPress already include a filter called "Texturize" with the same
|
||||
goal as SmartyPants. You could still find some usefulness to
|
||||
PHP SmartyPants if you are not happy enough with the standard algorithm.
|
||||
|
||||
PHP SmartyPants works with [WordPress][wp], version 1.2 or later.
|
||||
|
||||
[wp]: http://wordpress.org/
|
||||
|
||||
1. To use PHP SmartyPants with WordPress, place the "smartypants.php"
|
||||
file in the "plugins" folder. This folder is hidden inside
|
||||
"wp-content" at the root of your site:
|
||||
|
||||
(site home)/wp-content/plugins/smartypants.php
|
||||
|
||||
2. Activate the plugin with the administrative interface of WordPress.
|
||||
In the "Plugins" section you will now find SmartyPants. To activate
|
||||
the plugin, click on the "Activate" button on the same line than
|
||||
SmartyPants. Your entries will now be filtered by PHP SmartyPants.
|
||||
|
||||
Note: It is not possible at this time to apply a different set of
|
||||
filters to different entries. All your entries will be filtered by
|
||||
PHP SmartyPants if the plugin is active. This is currently a limitation
|
||||
of WordPress.
|
||||
|
||||
|
||||
### In your programs ###
|
||||
|
||||
You can use PHP SmartyPants easily in your current PHP program. Simply
|
||||
include the file and then call the `SmartyPants` function on the text
|
||||
you want to convert:
|
||||
|
||||
include_once "smartypants.php";
|
||||
$my_text = SmartyPants($my_text);
|
||||
|
||||
|
||||
### With Smarty ###
|
||||
|
||||
If your program use the [Smarty][sm] template engine, PHP SmartyPants
|
||||
can now be used as a modifier for your templates. Rename
|
||||
"smartypants.php" to "modifier.smartypants.php" and put it in your
|
||||
smarty plugins folder.
|
||||
|
||||
[sm]: http://smarty.php.net/
|
||||
|
||||
|
||||
Options and Configuration
|
||||
-------------------------
|
||||
|
||||
Settings are specified by editing the value of the `$smartypants_attr`
|
||||
variable in the "smartypants.php" file. For users of the Smarty template
|
||||
engine, the "smartypants" modifier also takes an optional attribute where
|
||||
you can specify configuration options, like this:
|
||||
`{$var|smartypants:1}` (where "1" is the configuration option).
|
||||
|
||||
Numeric values are the easiest way to configure SmartyPants's behavior:
|
||||
|
||||
"0"
|
||||
Suppress all transformations. (Do nothing.)
|
||||
|
||||
"1"
|
||||
Performs default SmartyPants transformations: quotes (including
|
||||
backticks-style), em-dashes, and ellipses. `--` (dash dash) is
|
||||
used to signify an em-dash; there is no support for en-dashes.
|
||||
|
||||
"2"
|
||||
Same as smarty_pants="1", except that it uses the old-school
|
||||
typewriter shorthand for dashes: `--` (dash dash) for en-dashes,
|
||||
`---` (dash dash dash) for em-dashes.
|
||||
|
||||
"3"
|
||||
Same as smarty_pants="2", but inverts the shorthand for dashes: `--`
|
||||
(dash dash) for em-dashes, and `---` (dash dash dash) for en-dashes.
|
||||
|
||||
"-1"
|
||||
Stupefy mode. Reverses the SmartyPants transformation process,
|
||||
turning the HTML entities produced by SmartyPants into their ASCII
|
||||
equivalents. E.g. `“` is turned into a simple double-quote
|
||||
(`"`), `—` is turned into two dashes, etc. This is useful if you
|
||||
wish to suppress smart punctuation in specific pages, such as
|
||||
RSS feeds.
|
||||
|
||||
The following single-character attribute values can be combined to
|
||||
toggle individual transformations from within the smarty_pants
|
||||
attribute. For example, to educate normal quotes and em-dashes, but not
|
||||
ellipses or backticks-style quotes:
|
||||
|
||||
$smartypants_attr = "qd";
|
||||
|
||||
Or inside a Smarty template:
|
||||
|
||||
{$var|smartypants:"qd"}
|
||||
|
||||
"q"
|
||||
Educates normal quote characters: (`"`) and (`'`).
|
||||
|
||||
"b"
|
||||
Educates ` ``backticks'' ` double quotes.
|
||||
|
||||
"B"
|
||||
Educates backticks-style double quotes and ` `single' ` quotes.
|
||||
|
||||
"d"
|
||||
Educates em-dashes.
|
||||
|
||||
"D"
|
||||
Educates em-dashes and en-dashes, using old-school typewriter
|
||||
shorthand: (dash dash) for en-dashes, (dash dash dash) for
|
||||
em-dashes.
|
||||
|
||||
"i"
|
||||
Educates em-dashes and en-dashes, using inverted old-school
|
||||
typewriter shorthand: (dash dash) for em-dashes, (dash dash dash)
|
||||
for en-dashes.
|
||||
|
||||
"e"
|
||||
Educates ellipses.
|
||||
|
||||
"w"
|
||||
Translates any instance of `"` into a normal double-quote
|
||||
character. This should be of no interest to most people, but of
|
||||
particular interest to anyone who writes their posts using
|
||||
Dreamweaver, as Dreamweaver inexplicably uses this entity to
|
||||
represent a literal double-quote character. SmartyPants only
|
||||
educates normal quotes, not entities (because ordinarily, entities
|
||||
are used for the explicit purpose of representing the specific
|
||||
character they represent). The "w" option must be used in
|
||||
conjunction with one (or both) of the other quote options ("q" or
|
||||
"b"). Thus, if you wish to apply all SmartyPants transformations
|
||||
(quotes, en- and em-dashes, and ellipses) and also translate
|
||||
`"` entities into regular quotes so SmartyPants can educate
|
||||
them, you should set the SMARTYPANTS_ATTR constant at the top of
|
||||
the file to:
|
||||
|
||||
define( 'SMARTYPANTS_ATTR', "qDew" );
|
||||
|
||||
Inside a Smarty template, you could also pass the string as a
|
||||
parameter:
|
||||
|
||||
{$var|smartypants:"qDew"}
|
||||
|
||||
|
||||
### Algorithmic Shortcomings ###
|
||||
|
||||
One situation in which quotes will get curled the wrong way is when
|
||||
apostrophes are used at the start of leading contractions. For example:
|
||||
|
||||
'Twas the night before Christmas.
|
||||
|
||||
In the case above, SmartyPants will turn the apostrophe into an opening
|
||||
single-quote, when in fact it should be a closing one. I don't think
|
||||
this problem can be solved in the general case -- every word processor
|
||||
I've tried gets this wrong as well. In such cases, it's best to use the
|
||||
proper HTML entity for closing single-quotes (`’` or `’`) by
|
||||
hand.
|
||||
|
||||
|
||||
Bugs
|
||||
----
|
||||
|
||||
To file bug reports or feature requests (other than topics listed in the
|
||||
Caveats section above) please send email to:
|
||||
|
||||
<michel.fortin@michelf.com>
|
||||
|
||||
If the bug involves quotes being curled the wrong way, please send
|
||||
example text to illustrate.
|
||||
|
||||
|
||||
Version History
|
||||
---------------
|
||||
|
||||
1.0 (28 Jun 2006)
|
||||
|
||||
* First public release of PHP SmartyPants Typographer.
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue