Viewing file: Generator.php (9.21 KB) -rw-rw-rw- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
/** * Source map generator * * @package Less * @subpackage Output */ class Less_SourceMap_Generator extends Less_Configurable {
/** * What version of source map does the generator generate? */ const VERSION = 3;
/** * Array of default options * * @var array */ protected $defaultOptions = array( // an optional source root, useful for relocating source files // on a server or removing repeated values in the 'sources' entry. // This value is prepended to the individual entries in the 'source' field. 'sourceRoot' => '',
// an optional name of the generated code that this source map is associated with. 'sourceMapFilename' => null,
// url of the map 'sourceMapURL' => null,
// absolute path to a file to write the map to 'sourceMapWriteTo' => null,
// output source contents? 'outputSourceFiles' => false,
// base path for filename normalization 'sourceMapRootpath' => '',
// base path for filename normalization 'sourceMapBasepath' => '' );
/** * The base64 VLQ encoder * * @var Less_SourceMap_Base64VLQ */ protected $encoder;
/** * Array of mappings * * @var array */ protected $mappings = array();
/** * The root node * * @var Less_Tree_Ruleset */ protected $root;
/** * Array of contents map * * @var array */ protected $contentsMap = array();
/** * File to content map * * @var array */ protected $sources = array(); protected $source_keys = array();
/** * Constructor * * @param Less_Tree_Ruleset $root The root node * @param array $options Array of options */ public function __construct(Less_Tree_Ruleset $root, $contentsMap, $options = array()){ $this->root = $root; $this->contentsMap = $contentsMap; $this->encoder = new Less_SourceMap_Base64VLQ();
$this->SetOptions($options); $this->options['sourceMapRootpath'] = $this->fixWindowsPath($this->options['sourceMapRootpath'], true); $this->options['sourceMapBasepath'] = $this->fixWindowsPath($this->options['sourceMapBasepath'], true); }
/** * Generates the CSS * * @return string */ public function generateCSS(){ $output = new Less_Output_Mapped($this->contentsMap, $this);
// catch the output $this->root->genCSS($output);
$sourceMapUrl = $this->getOption('sourceMapURL'); $sourceMapFilename = $this->getOption('sourceMapFilename'); $sourceMapContent = $this->generateJson(); $sourceMapWriteTo = $this->getOption('sourceMapWriteTo');
if( !$sourceMapUrl && $sourceMapFilename ){ $sourceMapUrl = $this->normalizeFilename($sourceMapFilename); }
// write map to a file if( $sourceMapWriteTo ){ $this->saveMap($sourceMapWriteTo, $sourceMapContent); }
// inline the map if( !$sourceMapUrl ){ $sourceMapUrl = sprintf('data:application/json,%s', Less_Functions::encodeURIComponent($sourceMapContent)); }
if( $sourceMapUrl ){ $output->add( sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl) ); }
return $output->toString(); }
/** * Saves the source map to a file * * @param string $file The absolute path to a file * @param string $content The content to write * @throws Exception If the file could not be saved */ protected function saveMap($file, $content){ $dir = dirname($file); // directory does not exist if( !is_dir($dir) ){ // FIXME: create the dir automatically? throw new Exception(sprintf('The directory "%s" does not exist. Cannot save the source map.', $dir)); } // FIXME: proper saving, with dir write check! if(file_put_contents($file, $content) === false){ throw new Exception(sprintf('Cannot save the source map to "%s"', $file)); } return true; }
/** * Normalizes the filename * * @param string $filename * @return string */ protected function normalizeFilename($filename){
$filename = $this->fixWindowsPath($filename);
$rootpath = $this->getOption('sourceMapRootpath'); $basePath = $this->getOption('sourceMapBasepath');
// "Trim" the 'sourceMapBasepath' from the output filename. if (strpos($filename, $basePath) === 0) { $filename = substr($filename, strlen($basePath)); }
// Remove extra leading path separators. if(strpos($filename, '\\') === 0 || strpos($filename, '/') === 0){ $filename = substr($filename, 1); }
return $rootpath . $filename; }
/** * Adds a mapping * * @param integer $generatedLine The line number in generated file * @param integer $generatedColumn The column number in generated file * @param integer $originalLine The line number in original file * @param integer $originalColumn The column number in original file * @param string $sourceFile The original source file */ public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $fileInfo ){
$this->mappings[] = array( 'generated_line' => $generatedLine, 'generated_column' => $generatedColumn, 'original_line' => $originalLine, 'original_column' => $originalColumn, 'source_file' => $fileInfo['currentUri'] );
$this->sources[$fileInfo['currentUri']] = $fileInfo['filename']; }
/** * Generates the JSON source map * * @return string * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit# */ protected function generateJson(){
$sourceMap = array(); $mappings = $this->generateMappings();
// File version (always the first entry in the object) and must be a positive integer. $sourceMap['version'] = self::VERSION;
// An optional name of the generated code that this source map is associated with. $file = $this->getOption('sourceMapFilename'); if( $file ){ $sourceMap['file'] = $file; }
// An optional source root, useful for relocating source files on a server or removing repeated values in the 'sources' entry. This value is prepended to the individual entries in the 'source' field. $root = $this->getOption('sourceRoot'); if( $root ){ $sourceMap['sourceRoot'] = $root; }
// A list of original sources used by the 'mappings' entry. $sourceMap['sources'] = array(); foreach($this->sources as $source_uri => $source_filename){ $sourceMap['sources'][] = $this->normalizeFilename($source_filename); }
// A list of symbol names used by the 'mappings' entry. $sourceMap['names'] = array();
// A string with the encoded mapping data. $sourceMap['mappings'] = $mappings;
if( $this->getOption('outputSourceFiles') ){ // An optional list of source content, useful when the 'source' can't be hosted. // The contents are listed in the same order as the sources above. // 'null' may be used if some original sources should be retrieved by name. $sourceMap['sourcesContent'] = $this->getSourcesContent(); }
// less.js compat fixes if( count($sourceMap['sources']) && empty($sourceMap['sourceRoot']) ){ unset($sourceMap['sourceRoot']); }
return json_encode($sourceMap); }
/** * Returns the sources contents * * @return array|null */ protected function getSourcesContent(){ if(empty($this->sources)){ return; } $content = array(); foreach($this->sources as $sourceFile){ $content[] = file_get_contents($sourceFile); } return $content; }
/** * Generates the mappings string * * @return string */ public function generateMappings(){
if( !count($this->mappings) ){ return ''; }
$this->source_keys = array_flip(array_keys($this->sources));
// group mappings by generated line number. $groupedMap = $groupedMapEncoded = array(); foreach($this->mappings as $m){ $groupedMap[$m['generated_line']][] = $m; } ksort($groupedMap);
$lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
foreach($groupedMap as $lineNumber => $line_map){ while(++$lastGeneratedLine < $lineNumber){ $groupedMapEncoded[] = ';'; }
$lineMapEncoded = array(); $lastGeneratedColumn = 0;
foreach($line_map as $m){ $mapEncoded = $this->encoder->encode($m['generated_column'] - $lastGeneratedColumn); $lastGeneratedColumn = $m['generated_column'];
// find the index if( $m['source_file'] ){ $index = $this->findFileIndex($m['source_file']); if( $index !== false ){ $mapEncoded .= $this->encoder->encode($index - $lastOriginalIndex); $lastOriginalIndex = $index;
// lines are stored 0-based in SourceMap spec version 3 $mapEncoded .= $this->encoder->encode($m['original_line'] - 1 - $lastOriginalLine); $lastOriginalLine = $m['original_line'] - 1;
$mapEncoded .= $this->encoder->encode($m['original_column'] - $lastOriginalColumn); $lastOriginalColumn = $m['original_column']; } }
$lineMapEncoded[] = $mapEncoded; }
$groupedMapEncoded[] = implode(',', $lineMapEncoded) . ';'; }
return rtrim(implode($groupedMapEncoded), ';'); }
/** * Finds the index for the filename * * @param string $filename * @return integer|false */ protected function findFileIndex($filename){ return $this->source_keys[$filename]; }
/** * fix windows paths * @param string $path * @return string */ public function fixWindowsPath($path, $addEndSlash = false){ $slash = ($addEndSlash) ? '/' : ''; if( !empty($path) ){ $path = str_replace('\\', '/', $path); $path = rtrim($path,'/') . $slash; }
return $path; }
}
|