| var List = require('css-tree').List; | 
| var clone = require('css-tree').clone; | 
| var usageUtils = require('./usage'); | 
| var clean = require('./clean'); | 
| var replace = require('./replace'); | 
| var restructure = require('./restructure'); | 
| var walk = require('css-tree').walk; | 
|   | 
| function readChunk(children, specialComments) { | 
|     var buffer = new List(); | 
|     var nonSpaceTokenInBuffer = false; | 
|     var protectedComment; | 
|   | 
|     children.nextUntil(children.head, function(node, item, list) { | 
|         if (node.type === 'Comment') { | 
|             if (!specialComments || node.value.charAt(0) !== '!') { | 
|                 list.remove(item); | 
|                 return; | 
|             } | 
|   | 
|             if (nonSpaceTokenInBuffer || protectedComment) { | 
|                 return true; | 
|             } | 
|   | 
|             list.remove(item); | 
|             protectedComment = node; | 
|             return; | 
|         } | 
|   | 
|         if (node.type !== 'WhiteSpace') { | 
|             nonSpaceTokenInBuffer = true; | 
|         } | 
|   | 
|         buffer.insert(list.remove(item)); | 
|     }); | 
|   | 
|     return { | 
|         comment: protectedComment, | 
|         stylesheet: { | 
|             type: 'StyleSheet', | 
|             loc: null, | 
|             children: buffer | 
|         } | 
|     }; | 
| } | 
|   | 
| function compressChunk(ast, firstAtrulesAllowed, num, options) { | 
|     options.logger('Compress block #' + num, null, true); | 
|   | 
|     var seed = 1; | 
|   | 
|     if (ast.type === 'StyleSheet') { | 
|         ast.firstAtrulesAllowed = firstAtrulesAllowed; | 
|         ast.id = seed++; | 
|     } | 
|   | 
|     walk(ast, { | 
|         visit: 'Atrule', | 
|         enter: function markScopes(node) { | 
|             if (node.block !== null) { | 
|                 node.block.id = seed++; | 
|             } | 
|         } | 
|     }); | 
|     options.logger('init', ast); | 
|   | 
|     // remove redundant | 
|     clean(ast, options); | 
|     options.logger('clean', ast); | 
|   | 
|     // replace nodes for shortened forms | 
|     replace(ast, options); | 
|     options.logger('replace', ast); | 
|   | 
|     // structure optimisations | 
|     if (options.restructuring) { | 
|         restructure(ast, options); | 
|     } | 
|   | 
|     return ast; | 
| } | 
|   | 
| function getCommentsOption(options) { | 
|     var comments = 'comments' in options ? options.comments : 'exclamation'; | 
|   | 
|     if (typeof comments === 'boolean') { | 
|         comments = comments ? 'exclamation' : false; | 
|     } else if (comments !== 'exclamation' && comments !== 'first-exclamation') { | 
|         comments = false; | 
|     } | 
|   | 
|     return comments; | 
| } | 
|   | 
| function getRestructureOption(options) { | 
|     if ('restructure' in options) { | 
|         return options.restructure; | 
|     } | 
|   | 
|     return 'restructuring' in options ? options.restructuring : true; | 
| } | 
|   | 
| function wrapBlock(block) { | 
|     return new List().appendData({ | 
|         type: 'Rule', | 
|         loc: null, | 
|         prelude: { | 
|             type: 'SelectorList', | 
|             loc: null, | 
|             children: new List().appendData({ | 
|                 type: 'Selector', | 
|                 loc: null, | 
|                 children: new List().appendData({ | 
|                     type: 'TypeSelector', | 
|                     loc: null, | 
|                     name: 'x' | 
|                 }) | 
|             }) | 
|         }, | 
|         block: block | 
|     }); | 
| } | 
|   | 
| module.exports = function compress(ast, options) { | 
|     ast = ast || { type: 'StyleSheet', loc: null, children: new List() }; | 
|     options = options || {}; | 
|   | 
|     var compressOptions = { | 
|         logger: typeof options.logger === 'function' ? options.logger : function() {}, | 
|         restructuring: getRestructureOption(options), | 
|         forceMediaMerge: Boolean(options.forceMediaMerge), | 
|         usage: options.usage ? usageUtils.buildIndex(options.usage) : false | 
|     }; | 
|     var specialComments = getCommentsOption(options); | 
|     var firstAtrulesAllowed = true; | 
|     var input; | 
|     var output = new List(); | 
|     var chunk; | 
|     var chunkNum = 1; | 
|     var chunkChildren; | 
|   | 
|     if (options.clone) { | 
|         ast = clone(ast); | 
|     } | 
|   | 
|     if (ast.type === 'StyleSheet') { | 
|         input = ast.children; | 
|         ast.children = output; | 
|     } else { | 
|         input = wrapBlock(ast); | 
|     } | 
|   | 
|     do { | 
|         chunk = readChunk(input, Boolean(specialComments)); | 
|         compressChunk(chunk.stylesheet, firstAtrulesAllowed, chunkNum++, compressOptions); | 
|         chunkChildren = chunk.stylesheet.children; | 
|   | 
|         if (chunk.comment) { | 
|             // add \n before comment if there is another content in output | 
|             if (!output.isEmpty()) { | 
|                 output.insert(List.createItem({ | 
|                     type: 'Raw', | 
|                     value: '\n' | 
|                 })); | 
|             } | 
|   | 
|             output.insert(List.createItem(chunk.comment)); | 
|   | 
|             // add \n after comment if chunk is not empty | 
|             if (!chunkChildren.isEmpty()) { | 
|                 output.insert(List.createItem({ | 
|                     type: 'Raw', | 
|                     value: '\n' | 
|                 })); | 
|             } | 
|         } | 
|   | 
|         if (firstAtrulesAllowed && !chunkChildren.isEmpty()) { | 
|             var lastRule = chunkChildren.last(); | 
|   | 
|             if (lastRule.type !== 'Atrule' || | 
|                (lastRule.name !== 'import' && lastRule.name !== 'charset')) { | 
|                 firstAtrulesAllowed = false; | 
|             } | 
|         } | 
|   | 
|         if (specialComments !== 'exclamation') { | 
|             specialComments = false; | 
|         } | 
|   | 
|         output.appendList(chunkChildren); | 
|     } while (!input.isEmpty()); | 
|   | 
|     return { | 
|         ast: ast | 
|     }; | 
| }; |