All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.intuit.karate.core.FeatureParser Maven / Gradle / Ivy

The newest version!
/*
 * The MIT License
 *
 * Copyright 2022 Karate Labs Inc.
 *
 * 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.
 */
package com.intuit.karate.core;

import com.intuit.karate.StringUtils;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 *
 * @author pthomas3
 */
public class FeatureParser extends KarateParserBaseListener {
    
    private static final Logger logger = LoggerFactory.getLogger(FeatureParser.class);
    
    private final ParserErrorListener errorListener = new ParserErrorListener();
    private final Feature feature;
    private final CommonTokenStream tokenStream;
    
    private FeatureParser(Feature feature, InputStream is) {
        this.feature = feature;
        CharStream stream;
        try {
            stream = CharStreams.fromStream(is, StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        KarateLexer lexer = new KarateLexer(stream);
        tokenStream = new CommonTokenStream(lexer);
        KarateParser parser = new KarateParser(tokenStream);
        parser.addErrorListener(errorListener);
        RuleContext tree = parser.feature();
        ParseTreeWalker walker = new ParseTreeWalker();
        walker.walk(this, tree);
    }
    
    protected static void parse(Feature feature) {
        FeatureParser fp = new FeatureParser(feature, feature.getResource().getStream());
        if (fp.errorListener.isFail()) {
            String errorMessage = fp.errorListener.getMessage();
            logger.error("not a valid feature file: {} - {}", feature.getResource().getRelativePath(), errorMessage);
            throw new RuntimeException(errorMessage);
        }
    }
    
    private static int getActualLine(TerminalNode node) {
        int count = 0;
        for (char c : node.getText().toCharArray()) {
            if (c == '\n') {
                count++;
            } else if (!Character.isWhitespace(c)) {
                break;
            }
        }
        return node.getSymbol().getLine() + count;
    }
    
    private static List toTags(int line, List nodes) {
        List tags = new ArrayList();
        for (TerminalNode node : nodes) {
            String text = node.getText();
            if (line == -1) {
                line = getActualLine(node);
            }
            String[] tokens = text.trim().split("\\s+"); // handles spaces and tabs also        
            for (String t : tokens) {
                tags.add(new Tag(line, t));
            }
        }
        return tags;
    }
    
    private static Table toTable(KarateParser.TableContext ctx) {
        List nodes = ctx.TABLE_ROW();
        int rowCount = nodes.size();
        if (rowCount < 1) {
            // if scenario outline found without examples
            return null;
        }
        List> rows = new ArrayList(rowCount);
        List lineNumbers = new ArrayList(rowCount);
        int prevCount = -1;
        for (TerminalNode node : nodes) {
            List tokens = StringUtils.split(node.getText().trim(), '|', true);
            int count = tokens.size();
            if (prevCount != -1 && prevCount != count) {                
                throw new RuntimeException("examples column count mismatch at line: " 
                        + getActualLine(node) 
                        + "\n" + node.getText() + "\n\n"
                        + "rows:" + nodes);
            }
            prevCount = count;
            for (int i = 0; i < count; i++) {
                tokens.set(i, tokens.get(i).trim());
            }
            rows.add(tokens);
            lineNumbers.add(getActualLine(node));
        }
        return new Table(rows, lineNumbers);
    }
    
    public static final String TRIPLE_QUOTES = "\"\"\"";
    
    private static int indexOfFirstText(String s) {
        int pos = 0;
        for (char c : s.toCharArray()) {
            if (!Character.isWhitespace(c)) {
                return pos;
            }
            pos++;
        }
        return 0; // defensive coding
    }
    
    private static String fixDocString(String temp) {
        int quotePos = temp.indexOf(TRIPLE_QUOTES);
        int endPos = temp.lastIndexOf(TRIPLE_QUOTES);
        String raw = temp.substring(quotePos + 3, endPos).replaceAll("\r", "");
        List lines = StringUtils.split(raw, '\n', false);
        StringBuilder sb = new StringBuilder();
        int marginPos = -1;
        Iterator iterator = lines.iterator();
        while (iterator.hasNext()) {
            String line = iterator.next();
            int firstTextPos = indexOfFirstText(line);
            if (marginPos == -1) {
                marginPos = firstTextPos;
            }
            if (marginPos < line.length() && marginPos <= firstTextPos) {
                line = line.substring(marginPos);
            }
            if (iterator.hasNext()) {
                sb.append(line).append('\n');
            } else {
                sb.append(line);
            }
        }
        return sb.toString().trim();
    }
    
    private List collectComments(ParserRuleContext prc) {
        List tokens = tokenStream.getHiddenTokensToLeft(prc.start.getTokenIndex());
        if (tokens == null) {
            return null;
        }
        List comments = new ArrayList(tokens.size());
        for (Token t : tokens) {
            comments.add(StringUtils.trimToNull(t.getText()));
        }
        return comments;
    }
    
    private List toSteps(Scenario scenario, List list) {
        List steps = new ArrayList(list.size());
        int index = 0;
        for (KarateParser.StepContext sc : list) {
            Step step = scenario == null ? new Step(feature, index++) : new Step(scenario, index++);
            step.setComments(collectComments(sc));
            steps.add(step);
            int stepLine = sc.line().getStart().getLine();
            step.setLine(stepLine);
            step.setPrefix(sc.prefix().getText().trim());
            step.setText(sc.line().getText().trim());
            if (sc.docString() != null) {
                String raw = sc.docString().getText();
                step.setDocString(fixDocString(raw));
                step.setEndLine(stepLine + StringUtils.countLineFeeds(raw));
            } else if (sc.table() != null) {
                Table table = toTable(sc.table());
                step.setTable(table);
                step.setEndLine(stepLine + StringUtils.countLineFeeds(sc.table().getText()));
            } else {
                step.setEndLine(stepLine);
            }
        }
        return steps;
    }
    
    @Override
    public void enterFeatureHeader(KarateParser.FeatureHeaderContext ctx) {
        if (ctx.featureTags() != null) {
            feature.setTags(toTags(ctx.featureTags().getStart().getLine(), ctx.featureTags().FEATURE_TAGS()));
        }
        if (ctx.FEATURE() != null) {
            feature.setLine(ctx.FEATURE().getSymbol().getLine());
            if (ctx.featureDescription() != null) {
                StringUtils.Pair pair = StringUtils.splitByFirstLineFeed(ctx.featureDescription().getText());
                feature.setName(pair.left);
                feature.setDescription(pair.right);
            }
        }
    }
    
    @Override
    public void enterBackground(KarateParser.BackgroundContext ctx) {
        Background background = new Background();
        feature.setBackground(background);
        background.setLine(getActualLine(ctx.BACKGROUND()));
        List steps = toSteps(null, ctx.step());
        if (!steps.isEmpty()) {
            background.setSteps(steps);
        }
        if (logger.isTraceEnabled()) {
            logger.trace("background steps: {}", steps);
        }
    }
    
    @Override
    public void enterScenario(KarateParser.ScenarioContext ctx) {
        FeatureSection section = new FeatureSection();
        Scenario scenario = new Scenario(feature, section, -1);
        scenario.setLine(getActualLine(ctx.SCENARIO()));
        section.setScenario(scenario);
        feature.addSection(section);
        if (ctx.tags() != null) {
            scenario.setTags(toTags(-1, ctx.tags().TAGS()));
        }
        if (ctx.scenarioDescription() != null) {
            StringUtils.Pair pair = StringUtils.splitByFirstLineFeed(ctx.scenarioDescription().getText());
            scenario.setName(pair.left);
            scenario.setDescription(pair.right);
        }
        List steps = toSteps(scenario, ctx.step());
        scenario.setSteps(steps);
    }
    
    @Override
    public void enterScenarioOutline(KarateParser.ScenarioOutlineContext ctx) {
        FeatureSection section = new FeatureSection();
        ScenarioOutline outline = new ScenarioOutline(feature, section);
        outline.setLine(getActualLine(ctx.SCENARIO_OUTLINE()));
        section.setScenarioOutline(outline);
        feature.addSection(section);
        if (ctx.tags() != null) {
            outline.setTags(toTags(-1, ctx.tags().TAGS()));
        }
        if (ctx.scenarioDescription() != null) {
            outline.setDescription(ctx.scenarioDescription().getText());
            StringUtils.Pair pair = StringUtils.splitByFirstLineFeed(ctx.scenarioDescription().getText());
            outline.setName(pair.left);
            outline.setDescription(pair.right);
        }
        List steps = toSteps(null, ctx.step());
        outline.setSteps(steps);
        if (logger.isTraceEnabled()) {
            logger.trace("outline steps: {}", steps);
        }
        List examples = new ArrayList(ctx.examples().size());
        outline.setExamplesTables(examples);
        for (KarateParser.ExamplesContext ec : ctx.examples()) {
            Table table = toTable(ec.table());
            ExamplesTable example = new ExamplesTable(outline, table);
            examples.add(example);
            if (ec.tags() != null) {
                example.setTags(toTags(-1, ec.tags().TAGS()));
            }
            if (logger.isTraceEnabled()) {
                logger.trace("example rows: {}", table.getRows());
            }
        }
    }
    
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy