Skip to content

More robust CookieCutter for the nodejs ecosystem. It can be used to template any language!

License

Notifications You must be signed in to change notification settings

kirinnee/CyanPrint

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CyanPrint

Official Site: CyanPrint.dev

Source: GitLab

NPM: npm.js

Yarn: yarnpkg.com


CyanPrint is a scaffolding CLI for people to create templates or blueprints to scaffold commonly used templates and reduce boilerplate code.

Instead of spending time on setting up and configuring project builds, CyanPrint allows you to generate projects from pre-built templates and jump right into development. No need to fuss about making libraries work together - CyanPrint takes care of that for you.

The template file lives within the project itself, hence your template is still testable, even workable on its own.

CyanPrint is an slightly more robust version of CookieCutter in NPM with a fully working online repository for ease of use.

Features

  • Inquirer.js to get data
  • Glob patterns to choose which file to copy
  • Replace templating code with variables in file content and file names using data obtain from user input
  • Inline flagging to decide whether the line should be removed
  • if-else block to decide which block which stay and be removed depending on user input
  • Guid regeneration
  • package.json package choices
  • NPM install after skeleton project is generated
  • Git init after skeleton project is generated
  • Installing templates from Git repositories or local folder
  • Test template generation without installing it
  • Arbitrary Shell command execution
  • Permute possible outcome for testing

Tutorial

Please visit my website for the tutorial!

Everything below is for archive purpose as the documentation on the official site is still in progress!

Basic Concepts

CyanPrint replaces file, folder and content that has snippets of var~name~ with user input, allowing developers to make templates that live within their code.

This allow templates to be able to live on git, while also being friendly and test-able despite being a template.

Your first awesome CyanPrint

Variable Replacement

In this tutorial, we will try to template the file base on the user's name, his/her organization name and his/her email.

  1. In the folder first-CyanPrint, you should have the following file structure:

     first-CyanPrint
     |- cyan.config.js
     |- /Template
        |- .gitignore
  2. Within the Template folder, create a new txt file called var~name.person~.txt

    The template snippet var~name.person~ will be replaced by the value of name.person

    $ cd first-CyanPrint
    $ cd Template
    $ touch var~name.person~.txt
  3. Open var~name.person~.txt and put in the following content:

    Hello var~name.person~ from var~name.organization~!
    Your email is var~email~
    

    The variables var~name.person~, var~name.organization~ and var~email~ will be replaced by the values of name.person, name.organization and email respectively.

  4. Now we edit the cyan.config.js file to as the user for input, and replace the name, organization name and email in the file, and also rename the file to the person's name.

    This is done by setting the values of the variables name.person, name.organization and email.

    cyan.config.js

    module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {       
       return {
            globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
            variable: {
                name:{
                    person: "bob", //put the default value for name instead of bob
                    organization: "none" //put the default value for organization name instead of none
                },
                email: "bob@gmail.com" //put the default value for email instead of bob@gmail.com
            }
        };
    };
  5. Test to see whether your template works

    Go the folder 1 level higher than your root folder and start your bash or shell terminal there or simply:

    $ cd ../../

    Folder structure now:

    ??????? <---- you should be here (your terminal)
    |- /first-CyanPrint
       |- cyan.config.js
       |- /Template
          |- .gitignore
          |- var~name.person~.txt
       
    
  6. Test out your first template! The try command is for you to test your template before publishing/installing them

    $ cyan try first-CyanPrint awesome-project

    This installs your template in the folder called awesome-project on the same folder level as your first-CyanPrint folder

    You should expect to have the following file structure (if your used my values):

     ??????? <---- you should be here (your terminal)
        |- /first-CyanPrint
           |- cyan.config.js
           |- /Template
              |- .gitignore
              |- var~name.person~.txt
        |- /awesome-project
           |- .gitignore
           |- kirinee.txt
    

    and the content of bob.txt (or whatever name you put in the cyan.config):

    Hello kirinee from none!
    Your email is kirinee97@gmail.com
    
  7. Congratulations, your made your first CyanPrint!

Inline Flags in Content

Inline flags are boolean values where you can choose to remove files, folders or lines of code if the flag evaluate to false, and the snippet removed if evaluated to true.

The flag snippet is in the form of flag~flagname~, where flagname is the flag field's name

Continuing from the examples above change the following files: /Template/var~person.name~.txt

Hello var~name.person~ from var~name.organization~!
Your email is var~email~
Your ID is 024e7415-a88e-4a03-9b67-3d769aebf66f flag~show.id~

cyan.config.js

module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {
    return {
        globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
        variable: {
             name:{
                 person: "bob", 
                 organization: "none" 
             },
             email: "bob@gmail.com"
        },
        flags: {
            show:{
                id: true
            }
        }
    };
};

All flags should exist within flags of the returned object.

Try the above code out!

$ cyan try first-CyanPrint template

The newly generated kirinee.txt should have the following content:

Hello var~name.person~ from var~name.organization~!
Your email is var~email~
Your ID is 024e7415-a88e-4a03-9b67-3d769aebf66f 

Now change the flag to false

cyan.config.js

module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {
    return {
        globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
        variable: {
             name:{
                 person: "bob", 
                 organization: "none" 
             },
             email: "bob@gmail.com"
        },
        flags: {
            show:{
                id: false
            }
        }
    };
};

Try it out!

$ cyan try first-CyanPrint template

The newly generated kirinee.txt should have the Guid line removed:

Hello var~name.person~ from var~name.organization~!
Your email is var~email~

Inline Flag in files and folders

You can input flags in folders and file names. Folders with the flags evaluated to false will have their whole folder deleted and files with flags evaluated to false will the whole file deleted.

Added the following files to your folder structure:

??????? <---- you should be here (your terminal)
   |- /first-CyanPrint
      |- cyan.config.js
      |- /Template
         |- .gitignore
         |- var~name.person~.txt
         |- /flag~production~prod
            |- flag~server.one~Server1.txt
            |- flag~server.two~Server2.txt
         |- /flag~development~dev
            |- flag~server.one~Server1.txt
            |- flag~server.two~Server2.txt

And add the following flags to your flag object:

cyan.config.js

module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {
    return {
        globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
        variable: {
             name:{
                 person: "bob", 
                 organization: "none" 
             },
             email: "bob@gmail.com"
        },
        flags: {
            show:{
                id: false
            },
            production: true,
            development: false,
            server:{
                one: true,
                two: false
            }
        }
    };
};

Try it out!

$ cyan try first-CyanPrint template

The folder developement and the file Server2.txt should have been removed, your resulting folder structure should be:

??????? <---- you should be here (your terminal)
   |- /first-CyanPrint
      |- cyan.config.js
      |- /Template
         |- .gitignore
         |- bob.txt
         |- /prod
            |- Server1.txt

If-End Block

Flags can also be use to make if-else block.

Using the previous cyan.config.js, we can use if-end block to choose which part lives based on the flag's value.

Change your var~name.person~.txt file content to the following:

Hello var~name.person~ from var~name.organization~!
Your email is var~email~
Your ID is 024e7415-a88e-4a03-9b67-3d769aebf66f flag~show.id~

if~production~
Production line 1
Production line 2
Production line 3
end~production~

Now, try executing (note, the production flag is now true)

$ cyan try first-CyanPrint template

The generated file should have these content

bob.txt

Hello bob from none!
Your email bob@gmail.com

Production line 1
Production line 2
Production line 3

Now, turn off the production flag

cyan.config.js

module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {
    return {
        globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
        variable: {
             name:{
                 person: "bob", 
                 organization: "none" 
             },
             email: "bob@gmail.com"
        },
        flags: {
            show:{
                id: false
            },
            production: false,
            development: false,
            server:{
                one: true,
                two: false
            }
        }
    };
};

And execute the try command again

$ cyan try first-CyanPrint template

The it should remove the block if-end block

bob.txt

Hello bob from none!
Your email bob@gmail.com

####Caveats Do not alternate your if-end block, or have nested if-end block of the same flag!

NO:

if~a~
if~a~
end~a~
end~a~

NO:

if~a~
if~b~
end~a~
end~b~

YES:

if~a~
if~b~
end~b~
end~a~

Guid Generation

Cyan offers guid generation by replacing existing GUIDs with newly generated guid every time the template is generated. You can choose which GUID to be replaced by simply added the GUID into the GUID array.

cyan.config.js

module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {
    return {
        globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
        variable: {
             name:{
                 person: "bob", 
                 organization: "none" 
             },
             email: "bob@gmail.com"
        },
        flags: {
            show:{
                id: false
            },
            production: false,
            development: false,
            server:{
                one: true,
                two: false
            }
        },
        guid: ["024e7415-a88e-4a03-9b67-3d769aebf66f"]
    };
};

Added the GUID we want to change (the one in var~name.person~.txt) to the guid field in the return object.

Also, remember to change the show.id to true, else we cannot see the newly generated id

Try it out:

$ cyan try first-CyanPrint template

The GUID in bob.txt should have been replaced.

Do note that GUID looks for both ONLY fully-uppercased and fully-lowercased GUID to replace

NPM package.json Flags

CyanPrint has first-class support for NPM's package.json.

Within the flag object, all fields under the packages field will be taken as a name of a package.

Packages with true (whether in devDependencies or depedencies) will remain, whilst those that are evaluated to be false will be removed. Do not CyanPrint will not add entry if it originally does not exist, and the flag is true.

Example (Continuing from above)

Add package.json to Template folder with the following content:

{
  "name": "cyanprint",
  "version": "1.0.0",
  "devDependencies": {
      "chai": "^4.1.2",
      "mocha": "^5.2.0",
      "@types/chai": "^4.1.5"
  },
  "dependencies": {
      "commander": "^2.18.0",
      "glob": "^7.1.3"
  }
}

Now, change the cyanprint to set chai and @types/chai to false, while leaving commander to true. Those not listed in flags will not be affected.

cyan.config.js

module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {
    return {
        globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
        variable: {
             name:{
                 person: "bob", 
                 organization: "none" 
             },
             email: "bob@gmail.com"
        },
        flags: {
            packages:{
                chai: false,
                "@types/chai": false,
                commander: true  
            },
            show:{
                id: false
            },
            production: false,
            development: false,
            server:{
                one: true,
                two: false
            }
        },
        guid: ["024e7415-a88e-4a03-9b67-3d769aebf66f"]
    };
};

We have added the packages object with the relevant boolean values under the flag field in the return object.

Now we try it out:

$ cyan try first-CyanPrint template

The package.json that is generated will have the correct packages with false flag released:

package.json

{
  "name": "cyanprint",
  "version": "1.0.0",
  "devDependencies": {
      "mocha": "^5.2.0"
  },
  "dependencies": {
      "commander": "^2.18.0",
      "glob": "^7.1.3"
  }
}

Auto NPM Install

If your template requires npm i to be ran after the template is generated, simply add a npm flag to true in the return object of the async function you export in cyan.config.js

cyan.config.js

module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {
    return {
        globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
        variable: {
             name:{
                 person: "bob", 
                 organization: "none" 
             },
             email: "bob@gmail.com"
        },
        flags: {
            packages:{
                chai: false,
                "@types/chai": false,
                commander: true  
            },
            show:{
                id: false
            },
            production: false,
            development: false,
            server:{
                one: true,
                two: false
            }
        },
        guid: ["024e7415-a88e-4a03-9b67-3d769aebf66f"],
        npm: true
    };
};

This would run npm i after the template has been generated

AutoInquiring Variable Inputs

Introduction

As the previous example has shown, it is possible replace file, folder and content with small snippets of code inside. However, to make this viable, we should ask the user on what they want to replace the variable with.

CyanPrint uses Inquirer and Chalk underneath to produce interactive questions with the user and getting values, to decide what values to put.

We have our own API, but if it is not flexible enough, you can still use inquirer and chalk:

You can invoke their methods by using the first two argument of the async function exported. Please read their documentation on how to use their methods to obtain value.

module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {       
   //Use chalk
   console.log(chalk.redBright("Red Words"));
   //Use inquirer
   let answers = await inquirer.prompt([]);
   return {
       globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
       variable: {
           name:{
               person: "bob", //put the default value for name instead of bob
               organization: "none" //put the default value for organization name instead of none
           },
           email: "bob@gmail.com" //put the default value for email instead of bob@gmail.com
       }
   };
};

As mentioned above, CyanPrint has a inbuilt API for inquiring variables, and it can be invoked via the autoInquirer object.

Usage

Variable as Input

We will now use autoInquirer to inquire the three basic values from the user.

In your cyan.config.js, change it to the content below:

module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {
    let inputs =  {
         name:{
             person: "bob", //put the default value for name instead of bob
             organization: "none" //put the default value for organization name instead of none
         },
         email: "bob@gmail.com" //put the default value for email instead of bob@gmail.com
    };
    
    inputs = await autoInquirer.InquireInput(inputs); 
     
    return {
        globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
        variable: inputs
    };
};

This requests the user to input a value for each field (name.organization), and if no input was detected it will default the be value that was first set (none).

Try it out!

$ cyan try first-CyanPrint template

Custom Questions for fields

As you would realize from the previous example, each field will be asked as their field name, but capitalized and the dot replace with spaces. name.organization -> Name Organization.

Sometimes, you want to have a more detailed question. You can do this very simply by changing the default value from a string to an array of 2 string, the second being the question.

module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {       
 
    let inputs =  {
         name:{
             person: "bob", 
             organization: ["none", "Please enter the name of your Organization"] 
         },
         email: "bob@gmail.com"
    };
    
    inputs = await autoInquirer.InquireInput(inputs); 
     
    return {
        globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
        variable: inputs
    };
};

Now, the default value for the field name.organization is none and the question asked is Please enter the name of your Organization.

Try it out!

$ cyan try first-CyanPrint template

Inquire Flag Object as Checkbox

You can ask for flag Objects as a list of checkboxes. Each flag will be presented as a checkbox, and it will return true if the user checked it and false if the user did not check it. You can get a feel on how it looks like here.

   module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {       

        let flags = {
            unitTest: "Unit Test",
            e2eTest: "E2E",
            packages: {
                "chai": "Use Chai",
                mocha: "Use Mocha"
            } 
        };
        
        flags = await autoInquirer.InquireAsCheckBox(flags, "Which of these do you want to use?");
        /* sample of return result for flag after await:
            {
                unitTest: true,
                e2eTest: false,
                packages:{
                    chai: true,
                    mocha: true
                }
            }
         */
        return {
            globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
            flags: flags
        };
   };

Inquire Flag Object as Single-Answered MCQ

You can ask for flag Objects as a Multiple Choice Question with a single answer. The user will choose from a list, where each option is the original value of the flag object (which will become a boolean after)

   module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {       

        let flags = {
            ts: "Typescript",
            js: "Javascript",
            flow: "Flow"
        }
        
        flags = await autoInquirer.InquireAsList(flags, "Please choose which language to use");
        /* sample of return result for flag after await:
            {
                ts:true,
                js:false,
                flow:false
            }
         */
        return {
            globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
            flags: flags
        };
   };

Inquire Flag Object as series of Yes-No

This will get the value for each flag object as a Yes-No Question. The question that will be displayed will the value of the flag object, which will be turned into boolean after the question has been answered

   module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {       

        let flags = {
            unitTest: "Do you want Unit Test Framework?",
            e2eTest: "Do you want E2E Test Framework?",
            packages: {
                "chai": "Do you want to use Chai?",
                mocha: "Do you want to use Mocha?"
            } 
        }
        
        flags = await autoInquirer.InquireAsPredicate(flags);
        /* sample of return result for flag after await:
            {
                unitTest: true,
                e2eTest: false,
                packages:{
                    chai: true,
                    mocha: true
                }
            }
         */
        return {
            globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
            flags: flags
        };
   };

Using of Documentation

Documentation is a in-built API to simplify generation of basic documentation and Initialization of Git repository. It is very basic, so if you have complex rules, please do not use this.

Documentation comes with the following choices presented to the user.

  • README.MD
  • CONTRIBUTING.MD
  • One of the 4 Licenses:
    • MIT
    • Apache License 2.0
    • ISC
    • GNU Public License 3.0
  • Git Repository
  • SemVer

Simply offer the choice via autoInquire, and return the object as docs: cyan.config.js

   module.exports = async function(folderName, chalk, inquirer, autoInquirer, autoMapper, execute) {       
        
        let usage = {license: true,readme:true,git:true};
    
       let docs = await autoInquirer.InquireDocument(usage);
        
       return {
           globs: {root:"./Template",pattern: "**/*.*", ignore: ""},
           docs: docs,
           varaible: {}
       };
   };

This will present a checkbox of license, readme and git to the user, and if they check it, it will generate the following documents for them. Do note that README will not be generated, you will have a have a flag~cyan.docs.readme~README.MD file for CyanPrint to decide whether to copy over.

You can access the values of the documentation throughout your project, if you requested for it :

Variable Name Variable Value
varcyan.docs.author Author Value
varcyan.docs.email Email Value
varcyan.docs.description Description Value
varcyan.docs.license License Value
varcyan.docs.name Project name value
Flags Flag Value
flagcyan.docs.readme Whether README is used
flagcyan.docs.semver Whether SemVer is used
flagcyan.docs.git Whether Git is used
flagcyan.docs.contributing Whether Contributing is used
flagcyan.docs.license Whether License is used

Installing your first template

  1. the folder 1 level higher than your root folder and start your bash or shell terminal.

    $ cyan install ./first-CyanPrint <group>
  2. Wait for the installation to complete. Note that group can be left blank. It will install into the Main group.

    For more details on how groups work, read them here

  3. Now you can use the template anywhere by running cyan create <appname> and it will appear as a option in the group you installed to

Publish to git

  1. Simply publish the whole folder as a git repository

  2. Install directly from the git repository:

    $ cyan install <git repository> <group>
  3. Wait for the installation to complete. Note that group can be left blank. It will install into the Main group.

    For more details on how groups work, read them here

  4. Use the template anywhere on your local machine by running cyan create <appname> and it will appear as a option in the group you installed to

Contributing

Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.

Versioning

We use SemVer for versioning. For the versions available, see the tags on this repository.

Authors

  • kirinnee

License

This project is licensed under the MIT - see the LICENSE.md file for details

About

More robust CookieCutter for the nodejs ecosystem. It can be used to template any language!

Resources

License

Stars

Watchers

Forks

Packages

No packages published