[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Source map generator 5 * 6 * @package Less 7 * @subpackage Output 8 */ 9 class Less_SourceMap_Generator extends Less_Configurable { 10 11 /** 12 * What version of source map does the generator generate? 13 */ 14 const VERSION = 3; 15 16 /** 17 * Array of default options 18 * 19 * @var array 20 */ 21 protected $defaultOptions = array( 22 // an optional source root, useful for relocating source files 23 // on a server or removing repeated values in the 'sources' entry. 24 // This value is prepended to the individual entries in the 'source' field. 25 'sourceRoot' => '', 26 27 // an optional name of the generated code that this source map is associated with. 28 'sourceMapFilename' => null, 29 30 // url of the map 31 'sourceMapURL' => null, 32 33 // absolute path to a file to write the map to 34 'sourceMapWriteTo' => null, 35 36 // output source contents? 37 'outputSourceFiles' => false, 38 39 // base path for filename normalization 40 'sourceMapBasepath' => '' 41 ); 42 43 /** 44 * The base64 VLQ encoder 45 * 46 * @var Less_SourceMap_Base64VLQ 47 */ 48 protected $encoder; 49 50 /** 51 * Array of mappings 52 * 53 * @var array 54 */ 55 protected $mappings = array(); 56 57 /** 58 * The root node 59 * 60 * @var Less_Tree_Ruleset 61 */ 62 protected $root; 63 64 /** 65 * Array of contents map 66 * 67 * @var array 68 */ 69 protected $contentsMap = array(); 70 71 /** 72 * File to content map 73 * 74 * @var array 75 */ 76 protected $sources = array(); 77 78 /** 79 * Constructor 80 * 81 * @param Less_Tree_Ruleset $root The root node 82 * @param array $options Array of options 83 */ 84 public function __construct(Less_Tree_Ruleset $root, $contentsMap, $options = array()){ 85 $this->root = $root; 86 $this->contentsMap = $contentsMap; 87 $this->encoder = new Less_SourceMap_Base64VLQ(); 88 89 $this->SetOptions($options); 90 91 92 // fix windows paths 93 if( isset($this->options['sourceMapBasepath']) ){ 94 $this->options['sourceMapBasepath'] = str_replace('\\', '/', $this->options['sourceMapBasepath']); 95 } 96 } 97 98 /** 99 * Generates the CSS 100 * 101 * @return string 102 */ 103 public function generateCSS(){ 104 $output = new Less_Output_Mapped($this->contentsMap, $this); 105 106 // catch the output 107 $this->root->genCSS($output); 108 109 110 $sourceMapUrl = $this->getOption('sourceMapURL'); 111 $sourceMapFilename = $this->getOption('sourceMapFilename'); 112 $sourceMapContent = $this->generateJson(); 113 $sourceMapWriteTo = $this->getOption('sourceMapWriteTo'); 114 115 if( !$sourceMapUrl && $sourceMapFilename ){ 116 $sourceMapUrl = $this->normalizeFilename($sourceMapFilename); 117 } 118 119 // write map to a file 120 if( $sourceMapWriteTo ){ 121 $this->saveMap($sourceMapWriteTo, $sourceMapContent); 122 } 123 124 // inline the map 125 if( !$sourceMapUrl ){ 126 $sourceMapUrl = sprintf('data:application/json,%s', Less_Functions::encodeURIComponent($sourceMapContent)); 127 } 128 129 if( $sourceMapUrl ){ 130 $output->add( sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl) ); 131 } 132 133 return $output->toString(); 134 } 135 136 /** 137 * Saves the source map to a file 138 * 139 * @param string $file The absolute path to a file 140 * @param string $content The content to write 141 * @throws Exception If the file could not be saved 142 */ 143 protected function saveMap($file, $content){ 144 $dir = dirname($file); 145 // directory does not exist 146 if( !is_dir($dir) ){ 147 // FIXME: create the dir automatically? 148 throw new Exception(sprintf('The directory "%s" does not exist. Cannot save the source map.', $dir)); 149 } 150 // FIXME: proper saving, with dir write check! 151 if(file_put_contents($file, $content) === false){ 152 throw new Exception(sprintf('Cannot save the source map to "%s"', $file)); 153 } 154 return true; 155 } 156 157 /** 158 * Normalizes the filename 159 * 160 * @param string $filename 161 * @return string 162 */ 163 protected function normalizeFilename($filename){ 164 $filename = str_replace('\\', '/', $filename); 165 $basePath = $this->getOption('sourceMapBasepath'); 166 167 if( $basePath && ($pos = strpos($filename, $basePath)) !== false ){ 168 $filename = substr($filename, $pos + strlen($basePath)); 169 if(strpos($filename, '\\') === 0 || strpos($filename, '/') === 0){ 170 $filename = substr($filename, 1); 171 } 172 } 173 return sprintf('%s%s', $this->getOption('sourceMapRootpath'), $filename); 174 } 175 176 /** 177 * Adds a mapping 178 * 179 * @param integer $generatedLine The line number in generated file 180 * @param integer $generatedColumn The column number in generated file 181 * @param integer $originalLine The line number in original file 182 * @param integer $originalColumn The column number in original file 183 * @param string $sourceFile The original source file 184 */ 185 public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $sourceFile){ 186 $this->mappings[] = array( 187 'generated_line' => $generatedLine, 188 'generated_column' => $generatedColumn, 189 'original_line' => $originalLine, 190 'original_column' => $originalColumn, 191 'source_file' => $sourceFile 192 ); 193 194 195 $norm_file = $this->normalizeFilename($sourceFile); 196 197 $this->sources[$norm_file] = $sourceFile; 198 } 199 200 201 /** 202 * Generates the JSON source map 203 * 204 * @return string 205 * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit# 206 */ 207 protected function generateJson(){ 208 209 $sourceMap = array(); 210 $mappings = $this->generateMappings(); 211 212 // File version (always the first entry in the object) and must be a positive integer. 213 $sourceMap['version'] = self::VERSION; 214 215 216 // An optional name of the generated code that this source map is associated with. 217 $file = $this->getOption('sourceMapFilename'); 218 if( $file ){ 219 $sourceMap['file'] = $file; 220 } 221 222 223 // 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. 224 $root = $this->getOption('sourceRoot'); 225 if( $root ){ 226 $sourceMap['sourceRoot'] = $root; 227 } 228 229 230 // A list of original sources used by the 'mappings' entry. 231 $sourceMap['sources'] = array_keys($this->sources); 232 233 234 235 // A list of symbol names used by the 'mappings' entry. 236 $sourceMap['names'] = array(); 237 238 // A string with the encoded mapping data. 239 $sourceMap['mappings'] = $mappings; 240 241 if( $this->getOption('outputSourceFiles') ){ 242 // An optional list of source content, useful when the 'source' can't be hosted. 243 // The contents are listed in the same order as the sources above. 244 // 'null' may be used if some original sources should be retrieved by name. 245 $sourceMap['sourcesContent'] = $this->getSourcesContent(); 246 } 247 248 // less.js compat fixes 249 if( count($sourceMap['sources']) && empty($sourceMap['sourceRoot']) ){ 250 unset($sourceMap['sourceRoot']); 251 } 252 253 return json_encode($sourceMap); 254 } 255 256 /** 257 * Returns the sources contents 258 * 259 * @return array|null 260 */ 261 protected function getSourcesContent(){ 262 if(empty($this->sources)){ 263 return; 264 } 265 $content = array(); 266 foreach($this->sources as $sourceFile){ 267 $content[] = file_get_contents($sourceFile); 268 } 269 return $content; 270 } 271 272 /** 273 * Generates the mappings string 274 * 275 * @return string 276 */ 277 public function generateMappings(){ 278 279 if( !count($this->mappings) ){ 280 return ''; 281 } 282 283 // group mappings by generated line number. 284 $groupedMap = $groupedMapEncoded = array(); 285 foreach($this->mappings as $m){ 286 $groupedMap[$m['generated_line']][] = $m; 287 } 288 ksort($groupedMap); 289 290 $lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0; 291 292 foreach($groupedMap as $lineNumber => $line_map){ 293 while(++$lastGeneratedLine < $lineNumber){ 294 $groupedMapEncoded[] = ';'; 295 } 296 297 $lineMapEncoded = array(); 298 $lastGeneratedColumn = 0; 299 300 foreach($line_map as $m){ 301 $mapEncoded = $this->encoder->encode($m['generated_column'] - $lastGeneratedColumn); 302 $lastGeneratedColumn = $m['generated_column']; 303 304 // find the index 305 if( $m['source_file'] ){ 306 $index = $this->findFileIndex($this->normalizeFilename($m['source_file'])); 307 if( $index !== false ){ 308 $mapEncoded .= $this->encoder->encode($index - $lastOriginalIndex); 309 $lastOriginalIndex = $index; 310 311 // lines are stored 0-based in SourceMap spec version 3 312 $mapEncoded .= $this->encoder->encode($m['original_line'] - 1 - $lastOriginalLine); 313 $lastOriginalLine = $m['original_line'] - 1; 314 315 $mapEncoded .= $this->encoder->encode($m['original_column'] - $lastOriginalColumn); 316 $lastOriginalColumn = $m['original_column']; 317 } 318 } 319 320 $lineMapEncoded[] = $mapEncoded; 321 } 322 323 $groupedMapEncoded[] = implode(',', $lineMapEncoded) . ';'; 324 } 325 326 return rtrim(implode($groupedMapEncoded), ';'); 327 } 328 329 /** 330 * Finds the index for the filename 331 * 332 * @param string $filename 333 * @return integer|false 334 */ 335 protected function findFileIndex($filename){ 336 return array_search($filename, array_keys($this->sources)); 337 } 338 339 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |