Skip to content

Commit

Permalink
updates
Browse files Browse the repository at this point in the history
  • Loading branch information
yonaskolb committed Apr 8, 2018
1 parent 06c4cae commit 3e99692
Show file tree
Hide file tree
Showing 11 changed files with 263 additions and 54 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2018 Yonas Kolb

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.
110 changes: 97 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,76 @@
# Genesis

[![SPM](https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat)](https://swift.org/package-manager)
[![Git Version](https://img.shields.io/github/release/yonaskolb/Genesis.svg)](https://github.com/yonaskolb/Genesis/releases)
[![Build Status](https://img.shields.io/circleci/project/github/yonaskolb/Genesis.svg?style=flat)](https://circleci.com/gh/yonaskolb/Genesis)
[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/yonaskolb/Genesis/blob/master/LICENSE)

Genesis is a templating and scaffolding tool.

## Providing Options
A Genesis template is a manifest of options and files written in yaml or json. Option values can be provided in [various ways](#providing-options) otherwise you will be interactivly asked for inputs. Template files are written in [Stencil](https://github.com/kylef/Stencil)

- ✅ Create **easy** to read and write templates manifest in easy to read and write yaml or json
- ✅ Write template files in **Stencil**
-**Interactively** generate templates
- ✅ Powerful file **path generation**
- ✅ Seperate **data** from templates in option files
- ✅ Powerful option **configuration** with choices, lists and branching

The very simplest of templates could look like this

```yaml
options:
- name: project
description: The name of the project
question: What is the name of your project?
required: true
type: string
files:
- template: project.stencil
path: "{{ name }}.project
```
And then run like this:
```sh
$ genesis generate template.yml
What is the name of your project? MyProject
Generated files:
MyProject.project
```
Or you can provide the required options via arguments
```sh
$ genesis generate template.yml --options name:MyProject
Generated files:
MyProject.project
```
## Installing
Make sure Xcode 9 is installed first.
### [Mint](https://github.com/yonaskolb/mint)
```sh
$ mint install yonaskolb/genesis
```
## Usage
Run `genesis help` for usage instructions
```
Usage: genesis generate <templatePath> [options]
Options:
-d, --destination <value> Path to the directory where output will be generated. Defaults to the current directory
-h, --help Show help information for this command
-n, --non-interactive Do not prompt for required options
-o, --options <value> Provide option overrides, in the format --options "option1: value 2, option2: value 2.
-p, --option-path <value> Path to a yaml or json file containing options
```

## Providing options
Options will be passed to the template in this order which each level overriding the previous

- environment variables
Expand All @@ -19,25 +87,41 @@ A genesic template is a yaml or json file that includes a list of `options` and
### Options
Options are structured input for the `Stencil` templates. They serve as documentation and allow for Genesis to interactively ask for input.

- [x] `name`: This is the name that is referenced in the template as well as the command line
- [ ] `question`: The question that is aksed when asking for input
- [ ] `description`: An extended description of the option and what it controls
- [ ] `required`: Whether this option is required or not for the template to generate. If it is not provided via the command line, option file, or input, generation will fail
- [ ] `type`: This is the type of option. It defaults to `string` but can be any one of the following:
- **name**: This is the name that is referenced in the template as well as the command line
- **value**: This is the default value that will be used if none are provided
- **question**: The question that is asked when asking for input
- **description**: An extended description of the option and what it does
- **required**: Whether this option is required or not for the template to generate. If it is not provided via the command line, option file, or input, generation will fail
- **type**: This is the type of option. It defaults to `string` but can be any one of the following:
- `string` a simple string
- `boolean` a boolean
- `choice` a string for a list of choices. Requires `choices` to be defined
- `array` an array of other options. If this is a complex type, requires `options` to be defined.
- `choice` a string from a list of choices. Requires `choices` to be defined
- `array` an array of other options. Requires `options` to be defined.

### Files

- [ ] `path`: This is the path the file will be generated at. It can include `Stencil` tags to make it dynamic. This defaults to `template` if present
- [ ] `contents`: A file template string
- [ ] `template`: A path to a file template
- [ ] `context`: An optional context property path that will be passed to the template. If this resolves to an array, a file for each element will be created, using tags in `path` to differentiate them.
- **path**: This is the path the file will be generated at. It can include `Stencil` tags to make it dynamic. This defaults to `template` if present
- **contents**: A file template string
- **template**: A path to a file template
- **context**: An optional context property path that will be passed to the template. If this resolves to an array, a file for each element will be created, using tags in `path` to differentiate them.
- **include**: Whether the file should be written. This is a Stencil if tag without the braces. For example instead of `{% if type == 'framework' %}` you would write `type == 'framework'`

Each file can have a `contents` or `template`. If neither of those are present, the file will be copied exactly as is without any content replacement.
The final path of the file will be based off `path` otherwise `template`.

### Stencil Templates
Each Stencil template will have access to all the options. If the template is with an array it will get access to only that item within the array
Each Stencil template will have access to all the options. If the template is with an array it will get access to only that item within the array. See [Stencil](https://github.com/kylef/Stencil) for more info about tags

```
{% if name %}
name: {{ name }}
{% end if %}
```


## Contributions
Pull requests and issues are welcome

## License

Genesis is licensed under the MIT license. See [LICENSE](LICENSE) for more info.
12 changes: 6 additions & 6 deletions Sources/CLI/GenerateCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class GenerateCommand: Command {

let destinationPath = Key<String>("-d", "--destination", description: "Path to the directory where output will be generated. Defaults to the current directory")

let optionsArgument = Key<String>("-o", "--options", "Provide option overrides, in the format --options \"option1: value 2, option2: value 2.\nOptions are comma delimited, and key value is colon delimited. Any white space is trimmed")
let optionsArgument = Key<String>("-o", "--options", description: "Provide option overrides, in the format --options \"option1: value 2, option2: value 2.")

let nonInteractive = Flag("-n", "--non-interactive", description: "Do not prompt for required options")

Expand All @@ -31,10 +31,10 @@ class GenerateCommand: Command {
let templatePath = Path(self.templatePath.value).absolute()
let destinationPath = self.destinationPath.value.flatMap { Path($0) }?.absolute() ?? Path()

var options: [String: Any] = [:]
var context: Context = [:]

// extract options from env
options = ProcessInfo.processInfo.environment
context = ProcessInfo.processInfo.environment

// extract options from option path
if let optionPath = self.optionPath.value {
Expand All @@ -50,7 +50,7 @@ class GenerateCommand: Command {
}

for (key, value) in dictionary {
options[key] = value
context[key] = value
}
}

Expand All @@ -72,14 +72,14 @@ class GenerateCommand: Command {
.map { ($0[0], $0[1]) }

for (key, value) in optionPairs {
options[key] = value
context[key] = value
}
}

let template = try GenesisTemplate(path: templatePath)
let generator = try TemplateGenerator(template: template, interactive: !nonInteractive.value)

let result = try generator.generate(path: destinationPath, options: options)
let result = try generator.generate(path: destinationPath, context: context)
let filePaths = result.files.map { " \($0.path.string)" }.joined(separator: "\n")
for file in result.files {
let path = destinationPath + file.path
Expand Down
2 changes: 1 addition & 1 deletion Sources/CLI/GenesisCLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class GenesisCLI {
cli = CLI(name: "genesis", version: version, description: "genesis templater", commands: [
generateCommand
])
cli.router = SingleCommandRouter(command: generateCommand)
// cli.router = SingleCommandRouter(command: generateCommand)
}

public func run(arguments: String? = nil) -> Int32 {
Expand Down
9 changes: 8 additions & 1 deletion Sources/Generator/Input.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@ extension Input {
let errorResponse: InputReader<String>.ErrorResponse = { _ in
printError("You must respond with one of the following:\n\(optionsString)")
}
return readObject(prompt: prompt, secure: false, validation: validation, errorResponse: errorResponse)
let value = readObject(prompt: prompt, secure: false, validation: validation, errorResponse: errorResponse)
if options.contains(value) {
return value
} else if let index = Int(value) {
return options[index - 1]
} else {
return value
}
}

}
41 changes: 25 additions & 16 deletions Sources/Generator/TemplateGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public class TemplateGenerator {
self.environment = Environment(loader: FileSystemLoader(paths: [template.path.parent()]), extensions: nil, templateClass: Template.self)
}

public func generate(path: Path, options: [String: Any]) throws -> GenerationResult {
var context: [String: Any] = options
public func generate(path: Path, context: Context) throws -> GenerationResult {
var context: Context = context
return try generateSection(template.section, path: path, context: &context)
}

Expand Down Expand Up @@ -100,34 +100,43 @@ public class TemplateGenerator {

var generatedFiles: [GeneratedFile] = []

func generateFile(_ file: File, path: Path, context: Context) throws {

if let include = file.include {
let expression = "{% if \(include) %}true{% endif %}"
let parsedIf = try environment.renderTemplate(string: expression, context: context)
if parsedIf == "" {
return
}
}
let fileContents: String
switch file.type {
case .contents(let string): fileContents = try environment.renderTemplate(string: string, context: context)
case .template(let path): fileContents = try environment.renderTemplate(name: path, context: context)
}
let replacedPath = try environment.renderTemplate(string: file.path, context: context)
let generatedFile = GeneratedFile(path: Path(replacedPath), contents: fileContents)
generatedFiles.append(generatedFile)
}

for file in section.files {
if let fileContextPath = file.context, let fileContext = context[fileContextPath] {
if let array = fileContext as? [Context] {
for element in array {
generatedFiles.append(try generateFile(file, path: path, context: element))
try generateFile(file, path: path, context: element)
}
} else if let context = fileContext as? Context {
generatedFiles.append(try generateFile(file, path: path, context: context))
try generateFile(file, path: path, context: context)
} else {
generatedFiles.append(try generateFile(file, path: path, context: context))
try generateFile(file, path: path, context: context)
}
} else {
generatedFiles.append(try generateFile(file, path: path, context: context))
try generateFile(file, path: path, context: context)
}
}

return GenerationResult(files: generatedFiles, context: context)
}

func generateFile(_ file: File, path: Path, context: Context) throws -> GeneratedFile {
let fileContents: String
switch file.type {
case .contents(let string): fileContents = try environment.renderTemplate(string: string, context: context)
case .template(let path): fileContents = try environment.renderTemplate(name: path, context: context)
}
let replacedPath = try environment.renderTemplate(string: file.path, context: context)
return GeneratedFile(path: Path(replacedPath), contents: fileContents)
}
}

public enum GeneratorError: Error {
Expand Down
13 changes: 12 additions & 1 deletion Templates/XcodeProject/Target/ApplicationDelegate.swift
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
AppDelegate {{ type }}
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
}
4 changes: 4 additions & 0 deletions Templates/XcodeProject/Target/Header.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//
// {{ name }}.h
// {{ name }}
//
3 changes: 0 additions & 3 deletions Templates/XcodeProject/Target/template.yml

This file was deleted.

5 changes: 5 additions & 0 deletions Templates/XcodeProject/template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,8 @@ files:
- template: Target/ApplicationDelegate.swift
path: "{{ name }}/ApplicationDelegate.swift"
context: targets
include: type == 'application'
- template: Target/Header.h
path: "{{ name }}/Header.h"
context: targets
include: type == 'framework'
Loading

0 comments on commit 3e99692

Please sign in to comment.