Java tutorial
/** * Copyright 2010 Bazaarvoice, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @author J. Ryan Stinnett (ryan.stinnett@bazaarvoice.com) */ package com.bazaarvoice.jless; import com.bazaarvoice.jless.ast.node.Node; import com.bazaarvoice.jless.ast.node.ScopeNode; import com.bazaarvoice.jless.ast.visitor.FlattenNestedRuleSets; import com.bazaarvoice.jless.ast.visitor.Printer; import com.bazaarvoice.jless.exception.LessTranslationException; import com.bazaarvoice.jless.parser.Parser; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import org.apache.commons.io.IOUtils; import org.parboiled.Parboiled; import org.parboiled.ParseRunner; import org.parboiled.ReportingParseRunner; import org.parboiled.errors.ErrorUtils; import org.parboiled.support.DefaultValueStack; import org.parboiled.support.ParsingResult; import org.parboiled.support.ValueStack; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; /** * LessProcessor is JLESS's parsing and translation engine that is used to convert input files that * use the <a href="http://lesscss.org/">LESS</a> styling framework syntax into standard CSS. * This implementation is based on the Ruby version of LESS by Alexis Sellier. * * At this time, the following LESS features are supported: * <ul> * <li>Variables</li> * <li>Mixins</li> * <li>Mixin Arguments</li> * <li>Nesting</li> * </ul> * * The following LESS features are not currently supported: * <ul> * <li>Operations</li> * <li>Accessors</li> * <li>Imports</li> * </ul> * * This implementation does not attempt to generate the same output as the Ruby version. * Translation differences from LESS Ruby include: * <ul> * <li>Equivalent adjacent rule sets are not grouped</li> * <li>Empty rule sets are preserved</li> * <li>Numbers and colors are not reformatted</li> * </ul> * * This list only notes changes in the <em>translation</em> stage. See {@link com.bazaarvoice.jless.parser.Parser} for details * on any changes to the <em>parsing</em> stage. * * @see com.bazaarvoice.jless.parser.Parser */ public class LessProcessor { // Controls whether only parsing or both parsing and translation are performed. private boolean _translationEnabled = true; // Controls whether a compressed version of the output is printed. // There is no performance penalty for enabling compression. private boolean _compressionEnabled = false; public boolean isTranslationEnabled() { return _translationEnabled; } public void setTranslationEnabled(boolean translationEnabled) { _translationEnabled = translationEnabled; } public boolean isCompressionEnabled() { return _compressionEnabled; } public void setCompressionEnabled(boolean compressionEnabled) { _compressionEnabled = compressionEnabled; } public Result process(InputStream input) throws IOException { return process(null, input); } /** * The {@link ScopeNode} from a parent's result is placed on the parser's {@link ValueStack} and joined * together to allow for variable and mixin resolution across scopes. * @return A printable {@link Result} of processing the given input. */ public Result process(Result parent, InputStream input) throws IOException { ValueStack<Node> stack = new DefaultValueStack<Node>(); // Make the scope of each parent result accessible for variable and mixin resolution during parsing ScopeNode parentScope = null; if (parent != null) { parentScope = parent.getScope(); stack.push(parentScope); } // Parse the input ParseRunner<Node> parseRunner = new ReportingParseRunner<Node>( Parboiled.createParser(Parser.class, _translationEnabled).Document(), stack); ParsingResult<Node> result = parseRunner.run(IOUtils.toString(input, "UTF-8")); if (result.hasErrors()) { throw new LessTranslationException( "An error occurred while parsing a LESS input file:\n" + ErrorUtils.printParseErrors(result)); } // Retrieve the processed result ScopeNode scope = (ScopeNode) stack.pop(); // Link the new scope to the last parent for later variable resolution if (parentScope != null) { scope.setParentScope(parentScope); } return new Result(scope); } public static void main(String[] args) { if (args.length == 0) { System.err.println("You must specify an input file."); System.exit(1); } LessProcessor translator = new LessProcessor(); String inputPath = null; for (String arg : args) { if (arg.equals("-c")) { translator.setCompressionEnabled(true); } else { if (inputPath != null) { System.err.println("Only one input file can be used."); System.exit(1); } inputPath = arg; } } try { System.out.println(translator.process(new FileInputStream(inputPath))); } catch (IOException e) { System.err.println("Unable to read input file."); } } public class Result { private final ScopeNode _scope; private final Supplier<String> _toStringSupplier; public Result(ScopeNode scope) { _scope = scope; _toStringSupplier = Suppliers.memoize(new Supplier<String>() { @Override public String get() { // Perform additional translation steps if needed if (_translationEnabled) { _scope.traverse(new FlattenNestedRuleSets()); } // Print the output nodes Printer printer = new Printer(_compressionEnabled); _scope.traverse(printer); return printer.toString(); } }); } public ScopeNode getScope() { return _scope; } @Override public String toString() { return _toStringSupplier.get(); } } }