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

io.reactiverse.es4x.impl.ESModuleIO Maven / Gradle / Ivy

/*
 * Copyright 2018 Red Hat, Inc.
 *
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  and Apache License v2.0 which accompanies this distribution.
 *
 *  The Eclipse Public License is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *
 *  The Apache License v2.0 is available at
 *  http://www.opensource.org/licenses/apache2.0.php
 *
 *  You may elect to redistribute this code under either of these licenses.
 */
package io.reactiverse.es4x.impl;

import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.file.FileSystem;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ESModuleIO {

  private static final Logger LOGGER = LoggerFactory.getLogger(ESModuleIO.class);

  private static final Pattern importDef = Pattern.compile("import (\\* as [a-zA-Z_$][0-9a-zA-Z_$]*|\\{.+?}|[a-zA-Z_$][0-9a-zA-Z_$]*(\\s*,\\s*\\{.+?})?) from ['\"]([0-9a-zA-Z_$@./\\- ]+)['\"];?", Pattern.DOTALL);
  private static final Pattern exportDef = Pattern.compile("\\{(.+?)}", Pattern.DOTALL);
  private static final Pattern aliasDef = Pattern.compile("(\\*|[a-zA-Z_$][0-9a-zA-Z_$]*) as ([a-zA-Z_$][0-9a-zA-Z_$]*)", Pattern.DOTALL);
  private static final Pattern defaultDef = Pattern.compile("[a-zA-Z_$][0-9a-zA-Z_$]*(\\s*,\\s*\\{.+?})?", Pattern.DOTALL);

  private static final Pattern exportPattern = Pattern.compile("export (default|\\{(.+?)})", Pattern.DOTALL);

  private static String replace(String source, Pattern pattern, Function fn) {
      final Matcher m = pattern.matcher(source);
      boolean result = m.find();
      if (result) {
        StringBuilder sb = new StringBuilder(source.length());
        int p = 0;
        do {
          sb.append(source, p, m.start());
          sb.append(fn.apply(m));
          p = m.end();
        } while (m.find());
        sb.append(source, p, source.length());
        return sb.toString();
      }
      return source;
  }

  public static String adapt(String statement) {
    // replace `import` to `require`
    statement = replace(statement, importDef, importMatcher -> {

      LOGGER.debug(importMatcher.group(0));

      String exports = importMatcher.group(1).trim();
      String module = importMatcher.group(2);
      if (importMatcher.groupCount() > 2) {
        module = importMatcher.group(3);
      }

      final StringBuilder sb = new StringBuilder();
      int idx = exports.indexOf(",");
      if (idx > 0 && exports.indexOf("{") > idx) {
        String def = exports.substring(0, idx);
        exports = exports.substring(idx+1).trim();
        sb.append("const " + def + " = require('" + module + "').default;");
      }
      // is it single or multiple
      final Matcher exportMatcher = exportDef.matcher(exports);
      if (exportMatcher.find()) {
        final String[] multi = exportMatcher.group(1).split("\\s*,\\s*");
        for (String single : multi) {
          sb.append(adaptImport(single.trim(), module));
        }
      } else {
        final Matcher aliasMatcher = aliasDef.matcher(exports);
        if (aliasMatcher.find()) {
          sb.append(adaptImport(exports.trim(), module));
        } else {
          final Matcher defaultMatcher = defaultDef.matcher(exports);
          if (defaultMatcher.find()) {
            sb.append("const " + exports + " = require('" + module + "').default;");
          } else {
            sb.append(adaptImport(exports.trim(), module));
          }
        }
      }
      // ensure that the line numbers match
      for (int i = 0; i < exports.length(); i++) {
        if (exports.charAt(i) == '\r' || exports.charAt(i) == '\n') {
          sb.append(exports.charAt(i));
        }
      }

      return sb.toString();
    });

    // replace `export` to `module.exports = `
    return replace(statement, exportPattern, matcher -> {
      final String exports = matcher.group(1);
      // is it single or multiple
      final Matcher exportMatcher = exportDef.matcher(exports);
      final StringBuilder sb = new StringBuilder();

      if (exportMatcher.find()) {
        final String[] multi = exportMatcher.group(1).split("\\s*,\\s*");
        for (String single : multi) {
          sb.append(adaptExport(single.trim())+";");
        }
      } else {
        sb.append("module.exports.default =");
      }

      // ensure that the line numbers match
      for (int i = 0; i < exports.length(); i++) {
        if (exports.charAt(i) == '\r' || exports.charAt(i) == '\n') {
          sb.append(exports.charAt(i));
        }
      }

      return sb.toString();
    });
  }

  private static String adaptImport(final String exports, final String module) {
    final Matcher aliasMatcher = aliasDef.matcher(exports);
    if (aliasMatcher.find()) {
      final String base = aliasMatcher.group(1).trim();
      final String alias = aliasMatcher.group(2).trim();

      if ("*".equals(base)) {
        return "const " + alias + " = require('" + module + "');";
      } else {
        return "const " + alias + " = require('" + module + "')." + base + ";";
      }
    } else {
      return "const " + exports + " = require('" + module + "')." + exports + ";";
    }
  }

  private static String adaptExport(final String exports) {
    final Matcher aliasMatcher = aliasDef.matcher(exports);
    if (aliasMatcher.find()) {
      final String base = aliasMatcher.group(1).trim();
      final String alias = aliasMatcher.group(2).trim();
      return "module.exports." + alias + " = " + base;
    } else {
      return "module.exports." + exports + " = " + exports;
    }
  }

  private final FileSystem fs;

  public ESModuleIO(Vertx vertx) {
    this.fs = vertx.fileSystem();
  }

  public String getParent(String uri) throws URISyntaxException {
    switch (uri) {
      case "jar:":
      case "file:":
        throw new RuntimeException("Cannot get parent of root.");
      default:
        return getParent(new URI(uri));
    }
  }

  public String getParent(URI uri) {
    final String path = uri.getPath();
    int last = path.lastIndexOf('/');
    if (path.length() > last) {
      return uri.getScheme() + ':' + path.substring(0, last);
    }
    throw new RuntimeException("Cannot get parent of root.");
  }

  public boolean exists(URI uri) {

    if (uri == null) {
      return false;
    }

    switch (uri.getScheme()) {
      case "jar":
        return fs.existsBlocking(uri.getPath().substring(1));
      case "file":
        return fs.existsBlocking(uri.getPath());
      default:
        return false;
    }
  }

  public boolean isFile(URI uri) {
    switch (uri.getScheme()) {
      case "jar":
        return fs.propsBlocking(uri.getPath().substring(1)).isRegularFile();
      case "file":
        return fs.propsBlocking(uri.getPath()).isRegularFile();
      default:
        return false;
    }
  }

  public String readFile(URI uri) throws IOException {
    return readFile(uri, false);
  }

  public String readFile(URI uri, boolean main) throws IOException {
    return readFile(uri, main, true);
  }

  public String readFile(URI uri, boolean main, boolean adapt) throws IOException {
    Buffer buffer;

    switch (uri.getScheme()) {
      case "jar":
        buffer = fs.readFileBlocking(uri.getPath().substring(1));
        break;
      case "file":
        buffer = fs.readFileBlocking(uri.getPath());
        break;
      default:
        throw new IOException("Cannot handle scheme [" + uri.getScheme() + "]");
    }

    String content;
    if (main) {
      content = stripShebang(buffer.toString());
    } else {
      content = stripBOM(buffer.toString());
    }
    if (adapt) {
      return adapt(content);
    }
    return content;
  }

  /**
   * Find end of shebang line and slice it off
   * @param content the content to search
   * @return the striped content
   */
  public static String stripShebang(String content) {
    // Remove shebang
    int contLen = content.length();
    if (contLen >= 2) {
      if (content.charAt(0) == '#' && content.charAt(1) == '!') {
        if (contLen == 2) {
          // Exact match
          content = "";
        } else {
          // Find end of shebang line and slice it off
          int i = 2;
          for (; i < contLen; ++i) {
            char code = content.charAt(i);
            if (code == '\n' || code == '\r') {
              break;
            }
          }
          if (i == contLen) {
            content = "";
          } else {
            // Note that this actually includes the newline character(s) in the
            // new output.
            content = content.substring(i);
          }
        }
      }
    }
    return content;
  }

  /**
   * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
   * because the buffer-to-string conversion in `fs.readFileSync()`
   * translates it to FEFF, the UTF-16 BOM.
   * @param content the content to search
   * @return the striped content
   */
  public static String stripBOM(String content) {
    if (content != null && content.length() > 0 && content.charAt(0) == 0xFEFF) {
      content = content.substring(1);
    }
    return content;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy