turple 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,3 @@
1
+ .yardoc
2
+ coverage
3
+ tmp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format documentation
3
+ --require spec_helper
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ install: bundle install --without development
3
+ script: bundle exec rspec
4
+ rvm:
5
+ - 1.9.3-p551
6
+ - 2.1.5
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
@@ -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)
@@ -0,0 +1,5 @@
1
+ guard :rspec, cmd: 'bundle exec rspec' do
2
+ watch(%r{^spec/lib/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
@@ -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
+ ```
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+
5
+ require 'turple'
6
+
7
+ Turple::Cli.start ARGV
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,8 @@
1
+ sources:
2
+ yuyi: https://github.com/brewster1134/Yuyi-Rolls/archive/master.zip
3
+ rolls:
4
+ bundler:
5
+ ruby:
6
+ versions:
7
+ - 2.1.2
8
+ ruby_gems:
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: