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

com.redhat.ceylon.tools.new_.CeylonNewTool Maven / Gradle / Ivy

There is a newer version: 1.3.3
Show newest version
/*
 * Copyright Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the authors tag. All rights reserved.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU General Public License version 2.
 * 
 * This particular file is subject to the "Classpath" exception as provided in the 
 * LICENSE file that accompanied this code.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License,
 * along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */
package com.redhat.ceylon.tools.new_;

import java.io.File;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.redhat.ceylon.common.Constants;
import com.redhat.ceylon.common.Versions;
import com.redhat.ceylon.common.config.CeylonConfig;
import com.redhat.ceylon.common.config.DefaultToolOptions;
import com.redhat.ceylon.common.tool.CeylonBaseTool;
import com.redhat.ceylon.common.tool.Description;
import com.redhat.ceylon.common.tool.Hidden;
import com.redhat.ceylon.common.tool.OptionArgument;
import com.redhat.ceylon.common.tool.Rest;
import com.redhat.ceylon.common.tool.Subtool;
import com.redhat.ceylon.common.tool.Summary;
import com.redhat.ceylon.common.tool.ToolModel;
import com.redhat.ceylon.common.tools.CeylonTool;

@Summary("Generates a new Ceylon project")
@Description("Generates a new project, prompting for information as necessary")
public class CeylonNewTool extends CeylonBaseTool {

    private ToolModel model;
    
    private Project project;
    
    private File from = new File(System.getProperty(Constants.PROP_CEYLON_HOME_DIR), "templates");
    
    private Map rest = new HashMap();
    
    public void setToolModel(ToolModel model) {
        this.model = model;
    }
    
    @Subtool(argumentName="name", classes={Simple.class, HelloWorld.class, JavaInterop.class}, order=2)
    public void setProject(Project project) {
        this.project = project;
    }
    
    @Hidden
    @OptionArgument(argumentName="dir")
    public void setFrom(File from) {
        this.from = from;
    }
    
    @Rest
    public void setRest(List rest) {
        for (String optionArg : rest) {
            if (optionArg.startsWith("--")) {
                int idx = optionArg.indexOf("=");
                String option;
                String arg;
                if (idx == -1) {
                    option = optionArg.substring(2);
                    arg = "";
                } else {
                    option = optionArg.substring(2, idx);
                    arg = optionArg.substring(idx + 1);
                }
                this.rest.put(option, arg);
            }
        }
    }

    @Override
    public void initialize(CeylonTool mainTool) {
    }
    
    @Override
    public void run() throws Exception {
        File fromDir = getFromDir();
        if (!fromDir.exists() || !fromDir.isDirectory()) {
            throw new IllegalArgumentException(Messages.msg("from.nonexistent.or.nondir", fromDir));
        }
        Environment env = buildPromptedEnv();
        List resources = project.getResources(env);
        // Create base dir only once all the prompting has been done
        project.mkBaseDir(getCwd());
        for (Copy copy : resources) {
            copy.run(env);
        }
    }

    private String getProjectName() {
        String projectName = model.getSubtoolModel().getToolLoader().getToolName(project.getClass().getName());
        return projectName;
    }

    private File getFromDir() {
        return new File(applyCwd(from), getProjectName());
    }
    
    private Environment buildPromptedEnv() {
        Environment env = new Environment();
        // TODO Tidy up how we create and what's in this initial environment
        if (project.getDirectory() != null) {
            env.put("base.dir", applyCwd(project.getDirectory()).getAbsolutePath());
        }
        env.put("ceylon.home", System.getProperty(Constants.PROP_CEYLON_HOME_DIR));
        //env.put("ceylon.system.repo", System.getProperty("ceylon.system.repo"));
        env.put("ceylon.version.number", Versions.CEYLON_VERSION_NUMBER);
        env.put("ceylon.version.major", Integer.toString(Versions.CEYLON_VERSION_MAJOR));
        env.put("ceylon.version.minor", Integer.toString(Versions.CEYLON_VERSION_MINOR));
        env.put("ceylon.version.release", Integer.toString(Versions.CEYLON_VERSION_RELEASE));
        env.put("ceylon.version.name", Versions.CEYLON_VERSION_NAME);
        
        Set seenKeys = new HashSet<>();
        List vars = new LinkedList<>(project.getVariables());
        while (!vars.isEmpty()) {
            Variable var = vars.remove(0);
            if (seenKeys.contains(var.getKey())) {
                throw new RuntimeException("Variables for project do not form a tree");
            }
            seenKeys.add(var.getKey());
            // TODO Use the value from rest if there is one, only prompting if 
            // there is not
            // TODO The problem with this is: "How does the user figure out 
            // what option they need to specify on the command line
            // in order to avoid being prompted for it interactively?"
            // Each subtool could provide their own getters and setters
            // but they requires we write a subtool for each project
            // It would be nice if we didn't have to do that, but could just
            // drive the whole thing from a script in the templates dir.
            vars.addAll(0, var.initialize(getProjectName(), env));
        }
        
        String sourceFolder = Constants.DEFAULT_SOURCE_DIR;
        String baseDir = env.get("base.dir");
        if (project.getDirectory() == null) {
            project.setDirectory(new File(baseDir));
        }
        try {
            CeylonConfig config = CeylonConfig.createFromLocalDir(new File(baseDir));
            List srcs = DefaultToolOptions.getCompilerSourceDirs(config);
            if (!srcs.isEmpty()) {
                sourceFolder = srcs.get(0).getPath();
            } else {
                sourceFolder = Constants.DEFAULT_SOURCE_DIR;
            }
        } catch (Exception e) {
            // Ignore any errors
        }
        env.put("source.folder", sourceFolder);
        
        log(env);
        return env;
    }
    
    public Copy substituting(final String cwd, final String src, final String dst) {
        return substituting(cwd, new PathMatcher() {
            @Override
            public boolean matches(Path path) {
                return path.endsWith(src);
            }
        }, dst);
    }

    public Copy substituting(final String cwd, PathMatcher pathMatcher, String dst) {
        return new Copy(new File(getFromDir(), cwd), applyCwd(project.getDirectory()), pathMatcher, dst, true);
    }
    
    private void log(Object msg) {
        if (verbose != null) {
            System.out.println(msg);
        }
    }
    
    public abstract class BaseProject extends Project {

        private final Variable baseDir;
        private final Variable moduleName;
        private final Variable moduleVersion;
        private final Variable eclipseProjectName;
        private final Variable eclipse;
        private final Variable ant;

        public BaseProject(String defBaseDir, String defModName) {
            baseDir = Variable.directory("base.dir", shouldCreateProject() ? defBaseDir : ".");
            moduleName = Variable.moduleName("module.name", defModName);
            moduleVersion = Variable.moduleVersion("module.version", "1.0.0");
            eclipseProjectName = new Variable("eclipse.project.name", null, new PromptedValue("eclipse.project.name", "@[module.name]"));
            eclipse = Variable.yesNo("eclipse", Messages.msg("mnemonic.yes"), eclipseProjectName);
            ant = Variable.yesNo("ant", Messages.msg("mnemonic.yes"));
        }
        
        @OptionArgument
        @Description("Specifies the name for the new module.")
        public void setModuleName(String moduleName) {
            this.moduleName.setVariableValue(new GivenValue(moduleName));
        }
        
        @OptionArgument
        @Description("Specifies the version for the new module.")
        public void setModuleVersion(String moduleVersion) {
            this.moduleVersion.setVariableValue(new GivenValue(moduleVersion));
        }
        
        @OptionArgument
        @Description("Indicates if an Eclipse project should be generated or not.")
        public void setEclipse(boolean eclipse) {
            this.eclipse.setVariableValue(new GivenValue(Boolean.toString(eclipse)));
        }
        
        @OptionArgument
        @Description("Specifies the name for the Eclipse project.")
        public void setEclipseProjectName(String eclipseProjectName) {
            this.eclipseProjectName.setVariableValue(new GivenValue(eclipseProjectName));
        }
        
        @OptionArgument
        @Description("Indicates if an Ant build file should be generated or not.")
        public void setAnt(boolean ant) {
            this.ant.setVariableValue(new GivenValue(Boolean.toString(ant)));
        }
        
        @Override
        public List getVariables() {
            List result = new ArrayList<>();
            if (getDirectory() == null) {
                result.add(baseDir);
            }
            result.add(moduleName);
            result.add(Variable.moduleQuotedName("module.quoted.name", "module.name"));
            result.add(Variable.moduleDir("module.dir", "module.name"));
            result.add(moduleVersion);
            if (shouldCreateProject()) {
                result.add(eclipse);
                result.add(ant);
            }
            return result;
        }

        // Check if the target directory is not a project
        protected boolean shouldCreateProject() {
            // If it has a ".ceylon" folder we assume a project exists
            File ceylonDir = new File(applyCwd(getDirectory()), ".ceylon");
            if (ceylonDir.isDirectory()) {
                return false;
            }
            // If there's no ".ceylon" folder but there is a "source" folder
            // we also assume a project exists
            File sourcesDir = new File(applyCwd(getDirectory()), Constants.DEFAULT_SOURCE_DIR);
            if (sourcesDir.isDirectory()) {
                return false;
            }
            return true;
        }

        @Override
        public List getResources(Environment env) {
            FileSystem fs = FileSystems.getDefault();
            List result = new ArrayList<>();
            result.add(substituting("source",
                    fs.getPathMatcher("glob:**"), 
                    new Template("@[source.folder]/@[module.dir]").eval(env)));
            
            if ("true".equals(env.get("ant"))) {
                result.add(substituting("ant", "build.xml", "."));
            }
            if ("true".equals(env.get("eclipse"))) {
                result.add(substituting("eclipse",
                        fs.getPathMatcher("glob:**"),
                        "."));
            }
            return result;
        }
    }
    
    @Description("Generates a 'Hello World' style project." +
            "\n\n" +
            "Takes a `` argument to indicate in which directory the new project should be created.")
    public class HelloWorld extends BaseProject {

        public HelloWorld() {
            super("helloworld", "com.example.helloworld");
        }
    }
    
    @Description("Generates a very simple empty project" +
            "\n\n" +
            "Takes a `` argument to indicate in which directory the new project should be created.")
    public class Simple extends BaseProject {

        public Simple() {
            super("simple", "com.example.simple");
        }
    }
    
    @Description("Generates a project that is able to use Java legacy code" +
            "\n\n" +
            "Takes a `` argument to indicate in which directory the new project should be created.")
    public class JavaInterop extends BaseProject {

        public JavaInterop() {
            super("javainterop", "com.example.javainterop");
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy