turple 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +105 -0
- data/Guardfile +5 -0
- data/README.md +138 -0
- data/bin/turple +7 -0
- data/lib/turple.rb +145 -0
- data/lib/turple/cli.rb +28 -0
- data/lib/turple/data.rb +118 -0
- data/lib/turple/interpolate.rb +160 -0
- data/lib/turple/template.rb +136 -0
- data/yuyi_menu +8 -0
- metadata +198 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ff3155ba3b5f42ba3db3cf7e997af756f5f3dfe7
|
4
|
+
data.tar.gz: 0f9f60ee592211502b89f85bebf79ed1636d902f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 143ed8335f2747128d212b9b970afdc7779e738f4759a1d0a87937e65db73a96eb6f62d4c33b53e5032e992843da165bcb23e17ec973213764a24cf5ff014e92
|
7
|
+
data.tar.gz: 69ef3ae91f5e8055d62b9e528c5cb8efab4027de22728fe18cc975f402368efe6ab58939db1f775bee71f6698039fee029d9f48ba9fdad7ae2f36fda6474603b
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'activesupport', '~> 4.1'
|
4
|
+
gem 'cli_miami', '~> 0.0'
|
5
|
+
gem 'colorize', '~> 0.7'
|
6
|
+
gem 'coveralls', :require => false
|
7
|
+
gem 'recursive-open-struct', '~> 0.5'
|
8
|
+
gem 'thor', '~> 0.19'
|
9
|
+
|
10
|
+
group :test do
|
11
|
+
gem 'guard', '~> 2.6'
|
12
|
+
gem 'guard-rspec', '~> 4.3', :require => false
|
13
|
+
gem 'rspec', '~> 3.1'
|
14
|
+
gem 'terminal-notifier-guard', '~> 1.5'
|
15
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activesupport (4.2.0)
|
5
|
+
i18n (~> 0.7)
|
6
|
+
json (~> 1.7, >= 1.7.7)
|
7
|
+
minitest (~> 5.1)
|
8
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
9
|
+
tzinfo (~> 1.1)
|
10
|
+
celluloid (0.16.0)
|
11
|
+
timers (~> 4.0.0)
|
12
|
+
cli_miami (0.0.6)
|
13
|
+
term-ansicolor (~> 1.3)
|
14
|
+
coderay (1.1.0)
|
15
|
+
colorize (0.7.5)
|
16
|
+
coveralls (0.7.1)
|
17
|
+
multi_json (~> 1.3)
|
18
|
+
rest-client
|
19
|
+
simplecov (>= 0.7)
|
20
|
+
term-ansicolor
|
21
|
+
thor
|
22
|
+
diff-lcs (1.2.5)
|
23
|
+
docile (1.1.5)
|
24
|
+
ffi (1.9.6)
|
25
|
+
formatador (0.2.5)
|
26
|
+
guard (2.10.5)
|
27
|
+
formatador (>= 0.2.4)
|
28
|
+
listen (~> 2.7)
|
29
|
+
lumberjack (~> 1.0)
|
30
|
+
nenv (~> 0.1)
|
31
|
+
pry (>= 0.9.12)
|
32
|
+
thor (>= 0.18.1)
|
33
|
+
guard-compat (1.2.0)
|
34
|
+
guard-rspec (4.5.0)
|
35
|
+
guard (~> 2.1)
|
36
|
+
guard-compat (~> 1.1)
|
37
|
+
rspec (>= 2.99.0, < 4.0)
|
38
|
+
hitimes (1.2.2)
|
39
|
+
i18n (0.7.0)
|
40
|
+
json (1.8.1)
|
41
|
+
listen (2.8.4)
|
42
|
+
celluloid (>= 0.15.2)
|
43
|
+
rb-fsevent (>= 0.9.3)
|
44
|
+
rb-inotify (>= 0.9)
|
45
|
+
lumberjack (1.0.9)
|
46
|
+
method_source (0.8.2)
|
47
|
+
mime-types (2.4.3)
|
48
|
+
minitest (5.5.0)
|
49
|
+
multi_json (1.10.1)
|
50
|
+
nenv (0.1.1)
|
51
|
+
netrc (0.10.2)
|
52
|
+
pry (0.10.1)
|
53
|
+
coderay (~> 1.1.0)
|
54
|
+
method_source (~> 0.8.1)
|
55
|
+
slop (~> 3.4)
|
56
|
+
rb-fsevent (0.9.4)
|
57
|
+
rb-inotify (0.9.5)
|
58
|
+
ffi (>= 0.5.0)
|
59
|
+
recursive-open-struct (0.5.0)
|
60
|
+
rest-client (1.7.2)
|
61
|
+
mime-types (>= 1.16, < 3.0)
|
62
|
+
netrc (~> 0.7)
|
63
|
+
rspec (3.1.0)
|
64
|
+
rspec-core (~> 3.1.0)
|
65
|
+
rspec-expectations (~> 3.1.0)
|
66
|
+
rspec-mocks (~> 3.1.0)
|
67
|
+
rspec-core (3.1.7)
|
68
|
+
rspec-support (~> 3.1.0)
|
69
|
+
rspec-expectations (3.1.2)
|
70
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
71
|
+
rspec-support (~> 3.1.0)
|
72
|
+
rspec-mocks (3.1.3)
|
73
|
+
rspec-support (~> 3.1.0)
|
74
|
+
rspec-support (3.1.2)
|
75
|
+
simplecov (0.9.1)
|
76
|
+
docile (~> 1.1.0)
|
77
|
+
multi_json (~> 1.0)
|
78
|
+
simplecov-html (~> 0.8.0)
|
79
|
+
simplecov-html (0.8.0)
|
80
|
+
slop (3.6.0)
|
81
|
+
term-ansicolor (1.3.0)
|
82
|
+
tins (~> 1.0)
|
83
|
+
terminal-notifier-guard (1.6.4)
|
84
|
+
thor (0.19.1)
|
85
|
+
thread_safe (0.3.4)
|
86
|
+
timers (4.0.1)
|
87
|
+
hitimes
|
88
|
+
tins (1.3.3)
|
89
|
+
tzinfo (1.2.2)
|
90
|
+
thread_safe (~> 0.1)
|
91
|
+
|
92
|
+
PLATFORMS
|
93
|
+
ruby
|
94
|
+
|
95
|
+
DEPENDENCIES
|
96
|
+
activesupport (~> 4.1)
|
97
|
+
cli_miami (~> 0.0)
|
98
|
+
colorize (~> 0.7)
|
99
|
+
coveralls
|
100
|
+
guard (~> 2.6)
|
101
|
+
guard-rspec (~> 4.3)
|
102
|
+
recursive-open-struct (~> 0.5)
|
103
|
+
rspec (~> 3.1)
|
104
|
+
terminal-notifier-guard (~> 1.5)
|
105
|
+
thor (~> 0.19)
|
data/Guardfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/turple.svg)](http://badge.fury.io/rb/turple)
|
2
|
+
[![Build Status](https://travis-ci.org/brewster1134/turple.svg?branch=master)](https://travis-ci.org/brewster1134/turple)
|
3
|
+
[![Coverage Status](https://coveralls.io/repos/brewster1134/turple/badge.png)](https://coveralls.io/r/brewster1134/turple)
|
4
|
+
|
5
|
+
# Turple
|
6
|
+
Quick project templating, with optional cli wizard support
|
7
|
+
|
8
|
+
Turple can take a custom template and use it to bootstrap projects structures you commonly use instead of **copy/pasting** an old project and **find/replacing** to make it a new project.
|
9
|
+
|
10
|
+
### Usage
|
11
|
+
|
12
|
+
I always make projects the same way. There are a bunch of tools for making this faster, but i didnt like any of them. This is what i like.
|
13
|
+
|
14
|
+
Turple takes any kind of template format you want, a bunch of data, and in*turple*ates it.
|
15
|
+
|
16
|
+
Turple is best used from a command line, but it can be used directly in ruby as well. **CLI FIRST...**
|
17
|
+
|
18
|
+
### CLI
|
19
|
+
|
20
|
+
Turple requires a path to a template, and an optional destination. If no destination is passed, it will put everything in a `turple` folder from your current working directory.
|
21
|
+
|
22
|
+
```sh
|
23
|
+
turple --template /path/to/template --destination my_new_project_name
|
24
|
+
```
|
25
|
+
|
26
|
+
Turple will scan the template, determine what data is needed to process it, and prompt you for any missing data. If you wanted to run turple without the wizard, just throw a `Turplefile` into your destination directory with the nececssary data (even the template if you want)
|
27
|
+
|
28
|
+
### Turplefile
|
29
|
+
|
30
|
+
`Turplefile` files are yaml formatted files that provided various information to turple. Assuming our template requires a single peice of information called `foo`, our destination Turplefile would look something like this.
|
31
|
+
|
32
|
+
```yaml
|
33
|
+
template: /path/to/template
|
34
|
+
data:
|
35
|
+
foo: bar
|
36
|
+
```
|
37
|
+
|
38
|
+
Turple templates also have a Turplefile
|
39
|
+
|
40
|
+
### Turple Templates
|
41
|
+
|
42
|
+
A turple template is simply a directory containing a Turplefile, and any amount of custom folders and files your project template needs. The Turplefile inside a template has different data than a destination file. It has instructions on how to prompt a user for data, and the configuration details on how the template is built. _This example uses the default turple configuration._
|
43
|
+
|
44
|
+
### Configuration
|
45
|
+
|
46
|
+
```yaml
|
47
|
+
name: Foo Project Template
|
48
|
+
configuration:
|
49
|
+
file_ext: turple
|
50
|
+
path_regex: '\[([A-Z_\.]+)\]'
|
51
|
+
path_separator: .
|
52
|
+
content_regex: '<>([a-z_\.]+)<>'
|
53
|
+
content_separator: .
|
54
|
+
data_map:
|
55
|
+
foo: What is the foo called?
|
56
|
+
```
|
57
|
+
|
58
|
+
* `name` is just a friendly name for the template. its optional. we can use the template directory name for that.
|
59
|
+
* `configuration` has some very important details. (again, these are the defaults, so if your template does not have a custom configuration, it uses these values)
|
60
|
+
* `file_ext` is the file extension turple looks for to tell it there is content inside the file that needs processed
|
61
|
+
* `path_regex` this is a string representing a regex match to variable names
|
62
|
+
* `path_separator` this is a string representing a character(s) to seperate variables strung togehter
|
63
|
+
* `content_regex` & `content_separator` are the same as with a path, but to match file contents rather than a path.
|
64
|
+
* `data_map` is a hash that matches the same structure as the data required for a template, but instead provides the details to prompt a user in case a peice of required data is missing.
|
65
|
+
|
66
|
+
## Example Template
|
67
|
+
|
68
|
+
Say you design a template using teh turple default configuration, and you create a file structure like so...
|
69
|
+
```
|
70
|
+
foo_template
|
71
|
+
|__ my_[FOO.BAR]_dir
|
72
|
+
| |
|
73
|
+
| |__ my_[FOO.BAZ]_file.txt.turple
|
74
|
+
|
|
75
|
+
|__ Turplefile
|
76
|
+
```
|
77
|
+
|
78
|
+
and say your `my_[FOO.BAZ]_file.txt.turple` file contains the following
|
79
|
+
```
|
80
|
+
This <>foo.baz<> file is in the <>foo.bar<> folder.
|
81
|
+
```
|
82
|
+
|
83
|
+
With a simple Turplefile containing...
|
84
|
+
```
|
85
|
+
name: Foo Template
|
86
|
+
data_map:
|
87
|
+
foo:
|
88
|
+
bar: What is the foo bar?
|
89
|
+
baz: What is the foo baz?
|
90
|
+
```
|
91
|
+
|
92
|
+
* Notice how the path variables match the `path_regex`
|
93
|
+
* Notice how the separator of the path variables match the `path_separator`
|
94
|
+
* Notice how the content variables match the `content_regex`
|
95
|
+
* Notice how the separator of the content variables match the `content_separator`
|
96
|
+
|
97
|
+
**Let's run Turple!**
|
98
|
+
```sh
|
99
|
+
Saving to: /your/current/directory/turple
|
100
|
+
There is some missing data. You will be prompted to enter each value.
|
101
|
+
What is the foo bar?
|
102
|
+
>>> # enter your value here
|
103
|
+
What is the foo baz?
|
104
|
+
>>> # enter your value here
|
105
|
+
================================================================================
|
106
|
+
!TURPLE SUCCESS!
|
107
|
+
================================================================================
|
108
|
+
Turpleated `Foo Template` to a new project `turple`
|
109
|
+
Paths Turpleated: 2
|
110
|
+
Turpleated in: 1.1ms
|
111
|
+
================================================================================
|
112
|
+
```
|
113
|
+
|
114
|
+
### Ruby
|
115
|
+
You can run turple directly in ruby if needed as well. _This example matches the template from the above example._
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
require 'turple'
|
119
|
+
|
120
|
+
Turple.ate '~/Code/templates/mini_ruby_project', {
|
121
|
+
:foo => {
|
122
|
+
:bar => 'foobar',
|
123
|
+
:baz => 'foobaz'
|
124
|
+
}
|
125
|
+
}, {
|
126
|
+
:destination => '/path/to/your/new/project',
|
127
|
+
}
|
128
|
+
```
|
129
|
+
|
130
|
+
most notably the passing of the destination in the 3rd argument _(the configuration hash)_
|
131
|
+
|
132
|
+
### Development & Testing
|
133
|
+
```shell
|
134
|
+
gem install yuyi
|
135
|
+
yuyi -m https://raw.githubusercontent.com/brewster1134/turple/master/yuyi_menu
|
136
|
+
bundle install
|
137
|
+
bundle exec guard
|
138
|
+
```
|
data/bin/turple
ADDED
data/lib/turple.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'active_support/core_ext/hash/deep_merge'
|
2
|
+
require 'active_support/core_ext/hash/keys'
|
3
|
+
require 'cli_miami'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
class Turple
|
7
|
+
# classes
|
8
|
+
require 'turple/cli'
|
9
|
+
require 'turple/data'
|
10
|
+
require 'turple/interpolate'
|
11
|
+
require 'turple/template'
|
12
|
+
|
13
|
+
# Create CLI Miami presets
|
14
|
+
@@line_size = 80
|
15
|
+
CliMiami.set_preset :error, {
|
16
|
+
:color => :red,
|
17
|
+
:style => :bold
|
18
|
+
}
|
19
|
+
CliMiami.set_preset :prompt, {
|
20
|
+
:color => :blue,
|
21
|
+
:style => :bright
|
22
|
+
}
|
23
|
+
CliMiami.set_preset :header, CliMiami.presets[:prompt].merge({
|
24
|
+
:justify => :center,
|
25
|
+
:padding => @@line_size
|
26
|
+
})
|
27
|
+
CliMiami.set_preset :key, CliMiami.presets[:prompt].merge({
|
28
|
+
:justify => :rjust,
|
29
|
+
:padding => @@line_size / 2,
|
30
|
+
:preset => :prompt,
|
31
|
+
:newline => false
|
32
|
+
})
|
33
|
+
CliMiami.set_preset :value, {
|
34
|
+
:indent => 1
|
35
|
+
}
|
36
|
+
|
37
|
+
# Allows Turple.ate vs Turple.new
|
38
|
+
class << self
|
39
|
+
alias_method :ate, :new
|
40
|
+
end
|
41
|
+
|
42
|
+
@@turpleobject = {
|
43
|
+
:template => '',
|
44
|
+
:data => {},
|
45
|
+
:data_map => {},
|
46
|
+
:configuration => {
|
47
|
+
# default regex for file names to interpolate content of
|
48
|
+
# matches files with an extension of `.turple`
|
49
|
+
# (e.g. foo.txt.turple)
|
50
|
+
:file_ext => 'turple',
|
51
|
+
|
52
|
+
# default regex for path interpolation
|
53
|
+
# make sure to include the path_separator
|
54
|
+
# matches capitalized, dot-notated keys surrounded with single brackets
|
55
|
+
# (e.g. [FOO.BAR])
|
56
|
+
:path_regex => '\[([A-Z_\.]+)\]',
|
57
|
+
|
58
|
+
# default separator for attributes in the path
|
59
|
+
# the separator must exist in the path_regex capture group
|
60
|
+
# (e.g. [FOO.BAR])
|
61
|
+
:path_separator => '.',
|
62
|
+
|
63
|
+
# default regex for content interpolation
|
64
|
+
# make sure to include the content_separator
|
65
|
+
# matches lowercase, dot-notated keys surrounded with `<>`
|
66
|
+
# (e.g. <>foo.bar<>)
|
67
|
+
:content_regex => '<>([a-z_\.]+)<>',
|
68
|
+
|
69
|
+
# default separator for attributes in file contents
|
70
|
+
# the separator must exist in the content_regex capture group
|
71
|
+
# (e.g. <>foo.bar<>)
|
72
|
+
:content_separator => '.'
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
# Get loaded turplefiles contents
|
77
|
+
# @return [Hash]
|
78
|
+
#
|
79
|
+
def self.turpleobject; @@turpleobject; end
|
80
|
+
|
81
|
+
# allows helper accessors for turpleobject
|
82
|
+
#
|
83
|
+
def self.method_missing method
|
84
|
+
self.turpleobject[method] || super
|
85
|
+
end
|
86
|
+
|
87
|
+
# Read yaml turplefile and add contents to the turpleobject
|
88
|
+
#
|
89
|
+
# @param file [String] relative/absolute path to a turplefile
|
90
|
+
# @return [Hash]
|
91
|
+
#
|
92
|
+
def self.load_turplefile turplefile_path
|
93
|
+
# return false if file doesnt exist
|
94
|
+
turplefile_path = File.expand_path turplefile_path
|
95
|
+
return false unless File.exists? turplefile_path
|
96
|
+
|
97
|
+
self.turpleobject = YAML.load File.read turplefile_path
|
98
|
+
end
|
99
|
+
|
100
|
+
# Add additional data to the collective turplefile
|
101
|
+
# @param hash [Hash] any hash of data to be merged into existing turplefile data
|
102
|
+
# @return [Hash] merged turplefile data with symbolized keys
|
103
|
+
def self.turpleobject= hash
|
104
|
+
@@turpleobject.deep_merge! hash.deep_symbolize_keys
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def initialize template_path, data_hash, configuration_hash
|
110
|
+
template_path = File.expand_path template_path
|
111
|
+
data_hash = Turple.data.deep_merge data_hash
|
112
|
+
data_map_hash = Turple.data_map
|
113
|
+
configuration_hash = Turple.configuration.deep_merge configuration_hash
|
114
|
+
@destination_path = configuration_hash[:destination]
|
115
|
+
|
116
|
+
if configuration_hash[:cli]
|
117
|
+
S.ay 'Saving to: ', :preset => :prompt, :newline => false
|
118
|
+
S.ay @destination_path
|
119
|
+
end
|
120
|
+
|
121
|
+
# Initialize components
|
122
|
+
@template = Turple::Template.new template_path, configuration_hash
|
123
|
+
@data = Turple::Data.new @template.required_data, data_hash, data_map_hash
|
124
|
+
@interpolate = Turple::Interpolate.new @template, @data, @destination_path
|
125
|
+
|
126
|
+
output_summary if configuration_hash[:cli]
|
127
|
+
end
|
128
|
+
|
129
|
+
def output_summary
|
130
|
+
S.ay '=' * @@line_size, :prompt
|
131
|
+
S.ay '!TURPLE SUCCESS!', :preset => :header
|
132
|
+
S.ay '=' * @@line_size, :prompt
|
133
|
+
|
134
|
+
S.ay 'Turpleated ', :newline => false, :indent => 2
|
135
|
+
S.ay @template.name, :newline => false, :preset => :prompt
|
136
|
+
S.ay ' to a new project ', :newline => false
|
137
|
+
S.ay @interpolate.project_name, :preset => :prompt
|
138
|
+
|
139
|
+
S.ay 'Paths Turpleated:', :key
|
140
|
+
S.ay Dir[File.join(@destination_path, '**/*')].count.to_s, :value
|
141
|
+
S.ay 'Turpleated in:', :key
|
142
|
+
S.ay (@interpolate.time * 1000).round(1).to_s + 'ms', :value
|
143
|
+
S.ay '=' * @@line_size, :prompt
|
144
|
+
end
|
145
|
+
end
|
data/lib/turple/cli.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'cli_miami'
|
2
|
+
require 'thor'
|
3
|
+
|
4
|
+
class Turple::Cli < Thor
|
5
|
+
desc 'ate', 'Interpolate your template!'
|
6
|
+
option :template, :type => :string, :desc => 'Path to a turple template.'
|
7
|
+
option :destination, :type => :string, :default => File.join(Dir.pwd, 'turple'), :desc => 'Path to save interpolated template to.'
|
8
|
+
def ate
|
9
|
+
destination = File.expand_path options['destination']
|
10
|
+
|
11
|
+
# load destination turplefile if it exists
|
12
|
+
Turple.load_turplefile File.join(destination, 'Turplefile')
|
13
|
+
|
14
|
+
# update turpleobject object with cli options
|
15
|
+
Turple.turpleobject = {
|
16
|
+
template: (options['template'] || Turple.template rescue nil),
|
17
|
+
configuration: {
|
18
|
+
destination: destination,
|
19
|
+
cli: true
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
# initialize turple
|
24
|
+
Turple.ate Turple.template, Turple.data, Turple.configuration
|
25
|
+
end
|
26
|
+
|
27
|
+
default_task :ate
|
28
|
+
end
|
data/lib/turple/data.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'cli_miami'
|
2
|
+
require 'recursive-open-struct'
|
3
|
+
|
4
|
+
class Turple::Data
|
5
|
+
def data; RecursiveOpenStruct.new @provided_data; end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def initialize required_data, provided_data, data_map
|
10
|
+
@provided_data = provided_data
|
11
|
+
|
12
|
+
data_map = build_data_map required_data, Turple.data_map.deep_merge(data_map)
|
13
|
+
missing_data = get_missing_data required_data, @provided_data, data_map
|
14
|
+
|
15
|
+
# if there is missing data, prompt user to enter it in
|
16
|
+
unless missing_data.empty?
|
17
|
+
S.ay 'There is some missing data. You will be prompted to enter each value.', :color => :white, :bgcolor => :blue
|
18
|
+
prompt_for_data missing_data
|
19
|
+
end
|
20
|
+
|
21
|
+
# add provided data to turpleobject
|
22
|
+
Turple.turpleobject = {
|
23
|
+
:data => @provided_data
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
# populate missing data map values to match required data
|
28
|
+
# uses a collection of the parent keys as the value
|
29
|
+
#
|
30
|
+
# @param required_data [Hash] hash of required data
|
31
|
+
# @param data_map [Hash] hash of existing data map
|
32
|
+
#
|
33
|
+
# @return [Hash] complete data map hash
|
34
|
+
#
|
35
|
+
def build_data_map required_data, data_map
|
36
|
+
build_data_map_keys(required_data).deep_merge data_map
|
37
|
+
end
|
38
|
+
|
39
|
+
# create a new hash with the values replaced with an array of parent keys
|
40
|
+
#
|
41
|
+
# @param hash [Hash] hash to process
|
42
|
+
# @param keys [Array] initial array of keys (used internally for recursive functionlity)
|
43
|
+
#
|
44
|
+
# @return [Hash] mirrored hash with array of parent keys instead of the intial value
|
45
|
+
#
|
46
|
+
def build_data_map_keys hash, keys = []
|
47
|
+
# go through each key-value pair
|
48
|
+
hash.each do |key, val|
|
49
|
+
# if the value is a Hash, recurse and add the key to the array of parents
|
50
|
+
if val.is_a? Hash
|
51
|
+
build_data_map_keys(val, keys.push(key))
|
52
|
+
# remove last parent when we're done with this pair
|
53
|
+
keys.pop
|
54
|
+
else
|
55
|
+
# if the value is not a Hash, set the value to parents + current key
|
56
|
+
hash[key] = keys + [key]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# remove provided data from required data
|
62
|
+
# create a new hash with data map values
|
63
|
+
#
|
64
|
+
# @param required_data [Hash] hash of all the data neccessary to interpolate a template
|
65
|
+
# @param provided_data [Hash] hash of all the data collected from turplefile
|
66
|
+
# @param data_map [Hash] hash of descriptions of required data
|
67
|
+
#
|
68
|
+
# @return [Hash] hash of missing data with data map descriptions (or nested keys)
|
69
|
+
#
|
70
|
+
def get_missing_data required_data, provided_data, data_map
|
71
|
+
required_data.keys.inject({}) do |diff, k|
|
72
|
+
# if the hashes dont match on a particular key...
|
73
|
+
if required_data[k] != provided_data[k]
|
74
|
+
if required_data[k].is_a?(Hash) && provided_data[k].is_a?(Hash)
|
75
|
+
diff[k] = get_missing_data(required_data[k], provided_data[k], data_map[k])
|
76
|
+
else
|
77
|
+
# set the key to false if it doesnt exist in the 2nd hash
|
78
|
+
unless provided_data[k]
|
79
|
+
# if data map value exists, use it
|
80
|
+
diff[k] = data_map[k]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
diff.delete k if diff[k].nil? || diff[k].empty?
|
85
|
+
diff
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# merges user-input missing data into the provided data
|
90
|
+
#
|
91
|
+
# @param missing_data [Hash] hash of missing data
|
92
|
+
#
|
93
|
+
# @return [Hash] complete provided data hash
|
94
|
+
#
|
95
|
+
def prompt_for_data missing_data
|
96
|
+
@provided_data.deep_merge! prompt_for_data_keys(missing_data)
|
97
|
+
end
|
98
|
+
|
99
|
+
# loop through the missing data hash and prompt user to enter said data
|
100
|
+
#
|
101
|
+
# @param missing_data [Hash] hash of missing data
|
102
|
+
#
|
103
|
+
# @return [Hash] complete missing data hash
|
104
|
+
#
|
105
|
+
def prompt_for_data_keys missing_data
|
106
|
+
missing_data.each do |key, value|
|
107
|
+
if value.is_a? Hash
|
108
|
+
missing_data[key] = prompt_for_data_keys value
|
109
|
+
else
|
110
|
+
value = value.join(' | ') if value.is_a? Array
|
111
|
+
A.sk value, :prompt do |response|
|
112
|
+
missing_data[key] = response
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
missing_data
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'active_support/core_ext/hash/keys'
|
2
|
+
require 'date'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
class Turple::Interpolate
|
6
|
+
attr_reader :destination, :time, :project_name
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def initialize template, data, destination
|
11
|
+
@template = template.path
|
12
|
+
@data = data.data
|
13
|
+
@destination = destination
|
14
|
+
@configuration = template.configuration
|
15
|
+
@tmp_dir = Dir.mktmpdir
|
16
|
+
@project_name = File.basename @destination
|
17
|
+
|
18
|
+
# make sure the destination exists
|
19
|
+
FileUtils.mkdir_p @destination
|
20
|
+
|
21
|
+
start_timer = Time.now
|
22
|
+
|
23
|
+
# copy the template to tmp, and set the new path to process
|
24
|
+
create_tmp_project!
|
25
|
+
|
26
|
+
# interpolate directory and copy to destination
|
27
|
+
process_template! @tmp_project
|
28
|
+
|
29
|
+
# save a turplefile to the destination
|
30
|
+
create_turplefile!
|
31
|
+
|
32
|
+
end_timer = Time.now
|
33
|
+
@time = end_timer - start_timer
|
34
|
+
end
|
35
|
+
|
36
|
+
# Copy template to tmp dir and get the new path
|
37
|
+
#
|
38
|
+
def create_tmp_project!
|
39
|
+
FileUtils.cp_r @template, @tmp_dir
|
40
|
+
|
41
|
+
@tmp_project = File.join(@tmp_dir, File.basename(@template))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Collect paths to interpolate
|
45
|
+
# Only process files and empty directories
|
46
|
+
#
|
47
|
+
# @param template_path [String] valid path to a root template directory
|
48
|
+
#
|
49
|
+
def process_template! template_path
|
50
|
+
Find.find template_path do |path|
|
51
|
+
# don't process root dir
|
52
|
+
next if path == template_path
|
53
|
+
|
54
|
+
# if path is a file...
|
55
|
+
# or path is an empty directory
|
56
|
+
if File.file?(path) || Dir.entries(path).size <= 2
|
57
|
+
process_path! path
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Interpolate a path
|
63
|
+
#
|
64
|
+
# @param path [String] a valid path of a template asset
|
65
|
+
#
|
66
|
+
def process_path! path
|
67
|
+
# start the new_path out matching the original path
|
68
|
+
new_path = path.dup
|
69
|
+
|
70
|
+
# interpolate file contents
|
71
|
+
file_ext_regex = /\.#{Regexp.escape(@configuration[:file_ext])}$/
|
72
|
+
if path =~ file_ext_regex
|
73
|
+
process_contents! path
|
74
|
+
end
|
75
|
+
|
76
|
+
# interpolate path
|
77
|
+
path_regex = Regexp.new @configuration[:path_regex]
|
78
|
+
if path =~ path_regex
|
79
|
+
new_path = path.gsub path_regex do
|
80
|
+
# Extract interpolated values into symbols
|
81
|
+
methods = $1.downcase.split(@configuration[:path_separator]).map(&:to_sym)
|
82
|
+
|
83
|
+
# Call each method on the data
|
84
|
+
methods.inject(@data){ |data, method| data.send(method.to_sym) }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Remove the turple file extension from the file name
|
89
|
+
new_path.sub! file_ext_regex, ''
|
90
|
+
|
91
|
+
# replace the tmp dir path, with the destination path
|
92
|
+
new_path.sub! @tmp_project, @destination
|
93
|
+
|
94
|
+
copy_path! path, new_path
|
95
|
+
end
|
96
|
+
|
97
|
+
# Open a file, interpolate its contents, and overwrite it with the new contents
|
98
|
+
#
|
99
|
+
# @param file_path [String] valid path to a file with contents needing interpolated
|
100
|
+
# @return [File]
|
101
|
+
#
|
102
|
+
def process_contents! file_path
|
103
|
+
contents = File.read file_path
|
104
|
+
new_contents = process_string! contents
|
105
|
+
|
106
|
+
# Overwrite the original file with the processed file
|
107
|
+
File.open file_path, 'w' do |f|
|
108
|
+
f.write new_contents
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def process_string! string
|
113
|
+
content_regex = Regexp.new @configuration[:content_regex]
|
114
|
+
string.gsub content_regex do
|
115
|
+
# Extract interpolated values into symbols
|
116
|
+
methods = $1.downcase.split(@configuration[:content_separator]).map(&:to_sym)
|
117
|
+
|
118
|
+
# Call each method on data
|
119
|
+
methods.inject(@data){ |data, method| data.send(method.to_sym) }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Copies a modified path asset to the destination
|
124
|
+
#
|
125
|
+
# @param path [String] path to the asset in the tmp directory
|
126
|
+
# @param new_path [String] path to the new asset to create/copy
|
127
|
+
#
|
128
|
+
def copy_path! path, new_path
|
129
|
+
# if path is an empty directory, just create the directory
|
130
|
+
if File.directory? path
|
131
|
+
FileUtils.mkdir_p new_path
|
132
|
+
|
133
|
+
# if path is a file, make sure its directory exists and copy the file to it
|
134
|
+
elsif File.file? path
|
135
|
+
FileUtils.mkdir_p File.dirname(new_path)
|
136
|
+
FileUtils.cp_r path, new_path
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Saves the complete turplefile data to the newly created project
|
141
|
+
#
|
142
|
+
# @return [File]
|
143
|
+
#
|
144
|
+
def create_turplefile!
|
145
|
+
# get new template name based on the first directory of the destination
|
146
|
+
turplefile_path = File.join(@destination, 'Turplefile')
|
147
|
+
turplefile_object = Turple.turpleobject.deep_merge({
|
148
|
+
template: @template,
|
149
|
+
:created_on => Date.today.to_s
|
150
|
+
})
|
151
|
+
|
152
|
+
# convert object to yaml
|
153
|
+
turplefile_contents = turplefile_object.deep_stringify_keys.to_yaml
|
154
|
+
|
155
|
+
# Overwrite the original file if it exists with the processed file
|
156
|
+
File.open turplefile_path, 'w' do |f|
|
157
|
+
f.write turplefile_contents
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'active_support/core_ext/hash/deep_merge'
|
2
|
+
require 'cli_miami'
|
3
|
+
require 'find'
|
4
|
+
|
5
|
+
class Turple::Template
|
6
|
+
attr_accessor :path, :required_data, :configuration, :name
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def initialize path, configuration
|
11
|
+
@path = path
|
12
|
+
@configuration = configuration
|
13
|
+
|
14
|
+
# validate template path
|
15
|
+
unless valid_path?
|
16
|
+
if @configuration[:cli]
|
17
|
+
prompt_for_path
|
18
|
+
else
|
19
|
+
raise S.ay "Invalid Path `#{@path}`", :error
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# load template turplefile after validating path
|
24
|
+
Turple.load_turplefile File.join(@path, 'Turplefile')
|
25
|
+
|
26
|
+
# validate configuration after loading turplefile
|
27
|
+
valid_configuration?
|
28
|
+
|
29
|
+
# set data variables after validating path and configuration
|
30
|
+
@required_data = scan_for_data @path
|
31
|
+
@name = Turple.turpleobject[:name] || File.basename(@path)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Scan a path and determine the required data needed to interpolate it
|
35
|
+
# @param template_path [String] path to a template file
|
36
|
+
# @return [Hash] a hash of all the data needed
|
37
|
+
#
|
38
|
+
def scan_for_data template_path
|
39
|
+
required_data = {}
|
40
|
+
|
41
|
+
Find.find template_path do |path|
|
42
|
+
if File.file?(path)
|
43
|
+
|
44
|
+
# process file paths
|
45
|
+
path_regex = Regexp.new @configuration[:path_regex]
|
46
|
+
if path =~ path_regex
|
47
|
+
capture_groups = path.scan(path_regex).flatten
|
48
|
+
|
49
|
+
capture_groups.each do |group|
|
50
|
+
group_attributes = group.split(@configuration[:path_separator])
|
51
|
+
group_attributes_hash = group_attributes.reverse.inject(true) { |value, key| { key.downcase.to_sym => value } }
|
52
|
+
|
53
|
+
required_data.deep_merge! group_attributes_hash
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# process file contents
|
58
|
+
content_regex = Regexp.new @configuration[:content_regex]
|
59
|
+
if path =~ /\.#{Regexp.escape(@configuration[:file_ext])}$/
|
60
|
+
capture_groups = File.read(path).scan(content_regex).flatten
|
61
|
+
|
62
|
+
capture_groups.each do |group|
|
63
|
+
group_attributes = group.split(@configuration[:content_separator])
|
64
|
+
group_attributes_hash = group_attributes.reverse.inject(true) { |value, key| { key.downcase.to_sym => value } }
|
65
|
+
|
66
|
+
required_data.deep_merge! group_attributes_hash
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# check that the template requires data
|
73
|
+
if required_data.empty?
|
74
|
+
raise S.ay 'No Required Data - Nothing found to interpolate. Make sure your configuration matches your template.', :error
|
75
|
+
end
|
76
|
+
|
77
|
+
return required_data
|
78
|
+
end
|
79
|
+
|
80
|
+
# check that the path is a valid template
|
81
|
+
# @return [Boolean]
|
82
|
+
#
|
83
|
+
def valid_path?
|
84
|
+
File.exists?(@path) && File.exists?(File.join(@path, 'Turplefile'))
|
85
|
+
end
|
86
|
+
|
87
|
+
# prompt the user for a template path until a vaid one is entered
|
88
|
+
# @return [String] valid template path
|
89
|
+
#
|
90
|
+
def prompt_for_path
|
91
|
+
until valid_path?
|
92
|
+
A.sk 'Enter a path to a Turple Template', :preset => :prompt, :readline => true do |response|
|
93
|
+
@path = File.expand_path response
|
94
|
+
end
|
95
|
+
end
|
96
|
+
@path
|
97
|
+
end
|
98
|
+
|
99
|
+
# check the configuration is valid
|
100
|
+
# @return [Boolean]
|
101
|
+
#
|
102
|
+
def valid_configuration?
|
103
|
+
# check that a configuration exists
|
104
|
+
if @configuration.empty?
|
105
|
+
raise S.ay 'No Configuration Found', :error
|
106
|
+
end
|
107
|
+
|
108
|
+
# check that a configuration values are valid
|
109
|
+
#
|
110
|
+
# make sure the string is a valid extension with no period's in it
|
111
|
+
if !@configuration[:file_ext].is_a?(String) ||
|
112
|
+
@configuration[:file_ext] =~ /\./
|
113
|
+
raise S.ay "`file_ext` is invalid. See README for requirements.", :error
|
114
|
+
end
|
115
|
+
|
116
|
+
if !@configuration[:path_separator].is_a?(String)
|
117
|
+
raise S.ay "`path_separator` is invalid. See README for requirements.", :error
|
118
|
+
end
|
119
|
+
|
120
|
+
if !@configuration[:content_separator].is_a?(String)
|
121
|
+
raise S.ay "`content_separator` is invalid. See README for requirements.", :error
|
122
|
+
end
|
123
|
+
|
124
|
+
# make sure it contains the path separator in the capture group
|
125
|
+
if !(@configuration[:path_regex] =~ /\(.*#{Regexp.escape(@configuration[:path_separator])}.*\)/)
|
126
|
+
raise S.ay "`path_regex` invalid. See README for requirements.", :error
|
127
|
+
end
|
128
|
+
|
129
|
+
# make sure it contains the path separator in the capture group
|
130
|
+
if !(@configuration[:content_regex] =~ /\(.*#{Regexp.escape(@configuration[:content_separator])}.*\)/)
|
131
|
+
raise S.ay "`content_regex` invalid. See README for requirements.", :error
|
132
|
+
end
|
133
|
+
|
134
|
+
return true
|
135
|
+
end
|
136
|
+
end
|
data/yuyi_menu
ADDED
metadata
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: turple
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ryan Brewster
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: cli_miami
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: colorize
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.7'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.7'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: coveralls
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.7'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: recursive-open-struct
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.5'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.5'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: thor
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.19'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.19'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: guard
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.6'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.6'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: guard-rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '4.3'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '4.3'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rspec
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.1'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.1'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: terminal-notifier-guard
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '1.5'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '1.5'
|
153
|
+
description:
|
154
|
+
email: brewster1134@gmail.com
|
155
|
+
executables: []
|
156
|
+
extensions: []
|
157
|
+
extra_rdoc_files: []
|
158
|
+
files:
|
159
|
+
- ".gitignore"
|
160
|
+
- ".rspec"
|
161
|
+
- ".travis.yml"
|
162
|
+
- Gemfile
|
163
|
+
- Gemfile.lock
|
164
|
+
- Guardfile
|
165
|
+
- README.md
|
166
|
+
- bin/turple
|
167
|
+
- lib/turple.rb
|
168
|
+
- lib/turple/cli.rb
|
169
|
+
- lib/turple/data.rb
|
170
|
+
- lib/turple/interpolate.rb
|
171
|
+
- lib/turple/template.rb
|
172
|
+
- yuyi_menu
|
173
|
+
homepage: https://github.com/brewster1134/turple
|
174
|
+
licenses:
|
175
|
+
- MIT
|
176
|
+
metadata: {}
|
177
|
+
post_install_message:
|
178
|
+
rdoc_options: []
|
179
|
+
require_paths:
|
180
|
+
- lib
|
181
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
182
|
+
requirements:
|
183
|
+
- - ">="
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: '0'
|
186
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
187
|
+
requirements:
|
188
|
+
- - ">="
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: '0'
|
191
|
+
requirements: []
|
192
|
+
rubyforge_project:
|
193
|
+
rubygems_version: 2.2.2
|
194
|
+
signing_key:
|
195
|
+
specification_version: 4
|
196
|
+
summary: Quick Project Templating
|
197
|
+
test_files: []
|
198
|
+
has_rdoc:
|