new 0.1.1 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +13 -5
  2. data/bin/new +0 -2
  3. data/lib/new.rb +125 -52
  4. data/lib/new/cli.rb +514 -67
  5. data/lib/new/source.rb +110 -0
  6. data/lib/new/task.rb +67 -24
  7. data/lib/new/validation.rb +191 -0
  8. metadata +75 -132
  9. data/.gitignore +0 -6
  10. data/.new +0 -23
  11. data/.rspec +0 -2
  12. data/Gemfile +0 -16
  13. data/Gemfile.lock +0 -82
  14. data/Guardfile +0 -16
  15. data/LICENSE.txt +0 -22
  16. data/README.md +0 -128
  17. data/lib/new/core.rb +0 -7
  18. data/lib/new/dsl.rb +0 -42
  19. data/lib/new/interpolate.rb +0 -106
  20. data/lib/new/project.rb +0 -34
  21. data/lib/new/template.rb +0 -67
  22. data/lib/new/version.rb +0 -54
  23. data/spec/fixtures/custom/.new +0 -16
  24. data/spec/fixtures/custom/tasks/custom_bar_task/custom_bar_task.rb +0 -3
  25. data/spec/fixtures/custom/templates/custom_bar_template/.new +0 -3
  26. data/spec/fixtures/custom/templates/custom_bar_template/custom_bar.txt +0 -0
  27. data/spec/fixtures/project/.new +0 -4
  28. data/spec/fixtures/project/.new_cli_release_spec +0 -2
  29. data/spec/fixtures/tasks/custom_bar_task/custom_bar_task.rb +0 -1
  30. data/spec/fixtures/tasks/foo_task/Gemfile +0 -5
  31. data/spec/fixtures/tasks/foo_task/foo_task.rb +0 -7
  32. data/spec/fixtures/templates/custom_bar_template/.gitkeep +0 -0
  33. data/spec/fixtures/templates/foo_template/.new +0 -1
  34. data/spec/fixtures/templates/foo_template/[FOO.BAR].txt.erb +0 -1
  35. data/spec/fixtures/templates/foo_template/nested_[FOO.BAR]/foo.txt.erb +0 -1
  36. data/spec/lib/new/cli_spec.rb +0 -107
  37. data/spec/lib/new/interpolate_spec.rb +0 -43
  38. data/spec/lib/new/project_spec.rb +0 -33
  39. data/spec/lib/new/task_spec.rb +0 -39
  40. data/spec/lib/new/template_spec.rb +0 -59
  41. data/spec/lib/new/version_spec.rb +0 -26
  42. data/spec/lib/new_spec.rb +0 -19
  43. data/spec/spec_helper.rb +0 -46
  44. data/tasks/gem/.gemspec.erb +0 -4
  45. data/tasks/gem/README.md +0 -36
  46. data/tasks/gem/gem.rb +0 -170
  47. data/tasks/gem/gem_spec.rb +0 -138
  48. data/templates/js/.bowerrc +0 -3
  49. data/templates/js/.gitignore +0 -3
  50. data/templates/js/.new.erb +0 -11
  51. data/templates/js/CHANGELOG.md +0 -3
  52. data/templates/js/Gemfile +0 -2
  53. data/templates/js/Guardfile +0 -7
  54. data/templates/js/LICENSE-MIT.erb +0 -22
  55. data/templates/js/README.md.erb +0 -41
  56. data/templates/js/bower.json.erb +0 -11
  57. data/templates/js/demo/[PROJECT.FILENAME]_demo.coffee +0 -0
  58. data/templates/js/demo/[PROJECT.FILENAME]_demo.sass +0 -0
  59. data/templates/js/demo/index.html.erb +0 -12
  60. data/templates/js/lib/README.md +0 -2
  61. data/templates/js/package.json +0 -5
  62. data/templates/js/spec/[PROJECT.FILENAME]_spec.coffee.erb +0 -1
  63. data/templates/js/spec/[PROJECT.FILENAME]_spec.sass +0 -0
  64. data/templates/js/spec/index.html.erb +0 -35
  65. data/templates/js/src/[PROJECT.FILENAME].coffee.erb +0 -18
  66. data/templates/js/src/[PROJECT.FILENAME].sass +0 -0
  67. data/templates/js/testem.yml +0 -23
  68. data/templates/js/yuyi_menu +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 49de7a525567990df4a73895129d9016fd6cb025
4
- data.tar.gz: a7187f5f73de9b3ccc126306e8243c8ad3377cde
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZjNlNjUyMWY0NjY3ZjBjZThkZTM0MjQwODU1YTllYTNhNjYwZDBlZA==
5
+ data.tar.gz: !binary |-
6
+ NmQ4NmQ4NTUwZWU3ZTAwNmFkZTJkMjUzOTYwN2Y0M2FjMDc2MzExZg==
5
7
  SHA512:
6
- metadata.gz: d2ee8c15e70e71526ba0681e8d81686e06c80eab051470938e26d89d3dbe6d37ea64ed996bb3da5baf065a48df299aadfddd7b80af946726112b21358d9f8221
7
- data.tar.gz: f2784493ad68df62dc988f31d98b5045c1fdebf39b8f1442f3d975903d1d9470e9de1ca43444bb335d9d25d1e3f870da53a7c07c75848e852a802fcc02a01f76
8
+ metadata.gz: !binary |-
9
+ NmE3NjBmODFiNDMxODkzNTFmYTAzMjA5YTlhMzEwZWQ4NDU4MmI0MzhiOTJm
10
+ NzczOGVjOWY2Yzk2NzQ5NTI5YThhMGJkZDBiN2U3MzJkNTA4MDFjYTQ1N2Fk
11
+ NjM2MzZkZDFhYTUwZDQ3MzFiY2RlZjZjMGQwZmZhZGE4MDlmM2Q=
12
+ data.tar.gz: !binary |-
13
+ MzlhMWQ4OWFjYmNhMTQ5YzYxMWNiM2I4OGVlNDA3ZmRkNzViMzM5ODU4ZTYz
14
+ MTU0NmIyMGU3YTgwNmFlOGVlNTQ0M2Y5YTA1ZGZlMmU2YmZkODcyMWQ4YzRl
15
+ MDI2NzE4ODRlZDJjZDFiNDI1NWY0NjIzMzcxZjZmMDc2ZDFjNDc=
data/bin/new CHANGED
@@ -1,6 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  $: << File.dirname(__FILE__) + '/../lib'
3
-
4
3
  require 'new'
5
-
6
4
  New::Cli.start ARGV
data/lib/new.rb CHANGED
@@ -1,71 +1,144 @@
1
+ require 'active_support/core_ext/hash/deep_merge'
1
2
  require 'yaml'
2
3
 
4
+ # Allow true/false to respond to Boolean class
5
+ module Boolean; end
6
+ class TrueClass; include Boolean; end
7
+ class FalseClass; include Boolean; end
8
+
3
9
  class New
4
- VERSION = '0.0.1'
5
- DEFAULT_DIR = File.expand_path('../..', __FILE__)
6
- CUSTOM_DIR = File.expand_path('~/.new')
7
- TASKS_DIR_NAME = 'tasks'
8
- TEMPLATES_DIR_NAME = 'templates'
9
- CONFIG_FILE = '.new'
10
-
11
- # List all the available tasks
12
- #
13
- def self.tasks
14
- custom_tasks | default_tasks
15
- end
10
+ require 'new/validation'
11
+
12
+ require 'new/cli'
13
+ require 'new/source'
14
+ require 'new/task'
15
+
16
+ HOME_DIRECTORY = ENV['HOME']
17
+ PROJECT_DIRECTORY = Dir.pwd
18
+ NEWFILE_NAME = 'Newfile'
16
19
 
17
- # List all the available templates
18
20
  #
19
- def self.templates
20
- custom_templates | default_templates
21
- end
21
+ # CLASS METHODS
22
+ #
23
+ class << self
24
+ @@cli = false
25
+ @@verbose = false
26
+ @@new_object = {
27
+ :sources => {
28
+ :default => 'brewster1134/new-tasks'
29
+ }
30
+ }
22
31
 
23
- def self.default_templates
24
- @default_templates ||= get_list TEMPLATES_DIR_NAME, :default
25
- end
32
+ # access the current new object
33
+ def new_object; @@new_object; end
26
34
 
27
- def self.custom_templates
28
- @custom_templates ||= get_list TEMPLATES_DIR_NAME, :custom
29
- end
35
+ # set cli to true when initialized via cli
36
+ def set_cli; @@cli = true; end
30
37
 
31
- def self.default_tasks
32
- @default_tasks ||= get_list TASKS_DIR_NAME, :default
33
- end
38
+ # set verbose to true when set via cli
39
+ def set_verbose; @@verbose = true; end
40
+ def verbose; @@verbose; end
34
41
 
35
- def self.custom_tasks
36
- @custom_tasks ||= get_list TASKS_DIR_NAME, :custom
37
- end
42
+ # Load Newfile in home & project directory
43
+ #
44
+ def load_newfiles
45
+ load_newfile File.join(HOME_DIRECTORY, NEWFILE_NAME)
46
+ load_newfile File.join(PROJECT_DIRECTORY, NEWFILE_NAME)
47
+ end
48
+
49
+ # Merge symbolized hash data into global new object
50
+ #
51
+ # @param hash [Hash] A hash to be merged into existing data
52
+ #
53
+ # @return [Hash] New merged data
54
+ #
55
+ def new_object= hash
56
+ @@new_object.deep_merge! hash.deep_symbolize_keys
57
+ end
58
+
59
+ private
60
+
61
+ # Load newfile contents into the global new object
62
+ #
63
+ # @param newfile_path [String] Path to a valid Newfile
64
+ #
65
+ # @return [Hash] Hash of Newfile yaml contents
66
+ #
67
+ def load_newfile newfile_path
68
+ # check file exists
69
+ return false unless File.file? newfile_path
38
70
 
39
- def self.custom_config
40
- @custom_config ||= YAML.load(File.open(File.join(CUSTOM_DIR, CONFIG_FILE))).deep_symbolize_keys! rescue {}
71
+ # load Newfile yaml into global new_object hash
72
+ self.new_object = YAML.load File.read newfile_path
73
+ end
41
74
  end
42
75
 
43
76
  private
44
77
 
45
- def self.get_list dir, filter
46
- case filter
47
- when :default
48
- Dir[File.join(DEFAULT_DIR, dir, '**')]
49
- when :custom
50
- Dir[File.join(CUSTOM_DIR, dir, '**')]
51
- end.map{ |d| File.basename(d).to_sym }
52
- end
53
- end
78
+ def initialize version, changelog, *skip_tasks
79
+ # load newfiles and sources
80
+ New.load_newfiles unless @@cli
81
+ New::Source.load_sources
54
82
 
55
- # core
56
- require 'new/core'
83
+ # update options with new attributes
84
+ @@new_object[:version] = version
85
+ @@new_object[:changelog] = changelog
57
86
 
58
- # modules
59
- require 'new/dsl'
60
- require 'new/interpolate'
61
- require 'new/version'
87
+ # create new options to pass to task
88
+ new_options = @@new_object.dup
89
+ new_options.delete(:sources)
90
+ new_options.delete(:tasks)
62
91
 
63
- # classes
64
- require 'new/cli'
65
- require 'new/project'
66
- require 'new/template'
67
- require 'new/task'
92
+ new_tasks = []
93
+ @@new_object[:tasks].each do |task_name, task_options|
94
+ # skip tasks
95
+ next if skip_tasks.include? task_name.to_s
68
96
 
69
- class New
70
- extend New::Dsl
97
+ S.ay "Preparing `#{task_name}`", :header
98
+
99
+ # dupe task options
100
+ new_task_options = task_options ? task_options.dup : {}
101
+
102
+ # lookup and add task to array
103
+ task = New::Source.find_task task_name, new_task_options.delete(:source)
104
+ new_tasks << task
105
+
106
+ # add task options
107
+ new_options[:task_options] = new_task_options
108
+
109
+ # set options
110
+ task.options = new_options.dup
111
+
112
+ # validate task before running anything
113
+ S.ay 'Validating Options: ', :highlight_key
114
+ task.validate
115
+ S.ay 'OK', :highlight_value
116
+
117
+ # verify tasks
118
+ S.ay 'Verifying Task Dependencies: ', :highlight_key
119
+ task.verify
120
+ S.ay 'OK', :highlight_value
121
+ S.ay
122
+ end
123
+
124
+ # write new Newfile with new version
125
+ new_newfile = YAML.load(File.read(File.join(PROJECT_DIRECTORY, NEWFILE_NAME)))
126
+ new_newfile['version'] = version
127
+ File.open File.join(PROJECT_DIRECTORY, NEWFILE_NAME), 'w+' do |f|
128
+ f.write new_newfile.to_yaml
129
+ end
130
+
131
+ new_tasks.each do |task|
132
+ S.ay "Running `#{task.name}`", :header
133
+ task.run
134
+ end
135
+
136
+ # release summary
137
+ S.ay
138
+ S.ay 'Version ', :newline => false
139
+ S.ay "#{@@new_object[:version]}", :preset => :header, :newline => false
140
+ S.ay ' of ', :newline => false
141
+ S.ay "#{@@new_object[:name]}", :preset => :header, :newline => false
142
+ S.ay ' successfully released!'
143
+ end
71
144
  end
data/lib/new/cli.rb CHANGED
@@ -1,94 +1,541 @@
1
+ require 'active_support/core_ext/hash/reverse_merge'
2
+ require 'active_support/core_ext/hash/keys'
3
+ require 'cli_miami'
4
+ require 'listen'
5
+ require 'pp'
6
+ require 'semantic'
1
7
  require 'thor'
2
8
  require 'yaml'
3
9
 
10
+ CliMiami.set_preset :error, :color => :red
11
+ CliMiami.set_preset :fine_print, :color => :cyan
12
+ CliMiami.set_preset :header, :color => :green
13
+ CliMiami.set_preset :highlight_key, :indent => 2, :newline => false, :padding => 30, :justify => :rjust
14
+ CliMiami.set_preset :highlight_value, :color => :blue, :style => :bright, :indent => 1
15
+ CliMiami.set_preset :instruction, :color => :yellow, :indent => 2
16
+ CliMiami.set_preset :list_item, :indent => 2
17
+ CliMiami.set_preset :prompt, :color => :yellow, :style => :bold
18
+
19
+ # create a horizontal line
20
+ HR = -> {S.ay('-' * 32 , :header)}
21
+
4
22
  class New::Cli < Thor
5
- desc '[TEMPLATE] [NAME]', "Create a new project with a given template (#{New.templates.join(' ')})"
6
- # option :name, required: true
7
- def method_missing method, *args
8
- if New.templates.include? method.to_sym
9
- # Split args that look like options (i.e start with - or --) into a separate array
10
- positional_args, opts = Thor::Options.split args
11
-
12
- # extract name from args
13
- name = positional_args[0]
14
- raise Thor::RequiredArgumentMissingError unless name
15
-
16
- # Add all options here
17
- # Make sure to include required options up above as well so they show in the help menu
18
- parser = Thor::Options.new(
19
- # name: Thor::Option.new(:name, required: true, type: :string)
20
- )
21
-
22
- # The options hash is frozen in #initialize so you need to merge and re-assign
23
- self.options = options.merge(parser.parse(opts)).freeze
24
-
25
- # Dispatch the command
26
- project method, name
27
- else
28
- super
23
+ desc 'init', 'Create a Newfile for your project'
24
+ option :name, :type => :string, :aliases => ['-n'], :default => '', :desc => 'Your project name'
25
+ option :version, :type => :string, :aliases => ['-v'], :default => '', :desc => 'Your project\'s current version'
26
+ option :tasks, :type => :array, :aliases => ['-t'], :default => [], :desc => 'Tasks to run when releasing your project'
27
+ def init
28
+ New.load_newfiles
29
+ New::Source.load_sources
30
+
31
+ # initialize empty newfile object
32
+ newfile_object = {
33
+ :tasks => {}
34
+ }
35
+
36
+ # get valid name
37
+ name = @options['name']
38
+ until !name.empty?
39
+ name = A.sk 'Project Name:', :prompt
40
+ end
41
+ newfile_object[:name] = name
42
+
43
+ # get valid version
44
+ version = Semantic::Version.new(@options['version']) rescue nil
45
+ until !version.to_s.empty?
46
+ begin
47
+ response = A.sk 'Current Project Version:', :prompt
48
+ version = Semantic::Version.new response
49
+ rescue
50
+ S.ay "`#{response}` is not a valid semantic version (e.g. 1.2.3)", :error
51
+ end
52
+ end
53
+ newfile_object[:version] = version.to_s
54
+
55
+ # get tasks
56
+ tasks_list = []
57
+ @options['tasks'].each do |task|
58
+ tasks_list << New::Source.find_task(task)
59
+ end
60
+
61
+ # remove any empty tasks in case the user specified an invalid task
62
+ tasks_list.compact!
63
+
64
+ # if no tasks are specified, show all available tasks
65
+ if tasks_list.empty?
66
+ S.ay
67
+ self.tasks :show_source => true, :load_newfiles => false, :load_sources => false
68
+
69
+ S.ay 'Add multiple tasks by pressing ENTER after each one', :instruction
70
+ S.ay 'Enter tasks in the order you want them to run', :instruction
71
+ S.ay 'Enter both the source and the task (e.g. source#task)', :instruction
72
+ S.ay 'Enter an empty value to finish', :instruction
73
+
74
+ added_task = nil
75
+ until added_task == '' && !tasks_list.empty?
76
+ S.ay
77
+ added_task = A.sk 'Add a task:', :prompt
78
+
79
+ # if a task is entered, verify it exists
80
+ unless added_task.empty?
81
+
82
+ # find task
83
+ task = New::Source.find_task added_task
84
+
85
+ # add task to array
86
+ if task
87
+ tasks_list << task
88
+ end
89
+ end
90
+ end
91
+
92
+ S.ay
93
+ end
94
+
95
+ # output the summary so far (no task options entered yet)
96
+ padding = 10
97
+ S.ay 'Name:', :preset => :highlight_key, :padding => padding
98
+ S.ay name, :preset => :highlight_value
99
+ S.ay 'Version:', :preset => :highlight_key, :padding => padding
100
+ S.ay version.to_s, :preset => :highlight_value
101
+ S.ay 'Tasks:', :preset => :highlight_key, :padding => padding
102
+
103
+ # output first task without a padding so it looks nicer
104
+ tasks_dup = tasks_list.dup
105
+ first_task = tasks_dup.shift
106
+ S.ay "#{first_task.source.name}##{first_task.name}", :highlight_value
107
+ tasks_dup.each do |task|
108
+ S.ay "#{task.source.name}##{task.name}", :preset => :highlight_value, :indent => padding + 3
109
+ end
110
+ S.ay
111
+
112
+ tasks_list.each do |task|
113
+ HR.call
114
+ S.ay 'OK, now lets set options for', :highlight_key
115
+ S.ay task.name.to_s.upcase, :highlight_value
116
+ HR.call
117
+ S.ay
118
+
119
+ newfile_object[:tasks][task.name] = {}
120
+ task.class_options.each do |option_name, option_settings|
121
+ S.ay option_name.to_s, :preset => :highlight_key, :padding => 0
122
+ S.ay option_settings[:description], :highlight_value
123
+
124
+ # show default
125
+ default = option_settings[:default]
126
+ if default && !option_settings[:required]
127
+ default = case default
128
+ when Array then default.join(', ')
129
+ when Hash then default.keys.join(', ')
130
+ else default.to_s
131
+ end
132
+
133
+ S.ay "default: #{default}", :preset => :fine_print, :indent => option_name.length + 3
134
+ end
135
+
136
+ # GET USER INPUT FOR ARRAY TYPE
137
+ #
138
+ option_type = option_settings[:type]
139
+ case
140
+
141
+ # collect array option type values
142
+ when option_type == Array
143
+ # cast type onto all user input values (default is String)
144
+ klass = option_settings[:validation] || String
145
+
146
+ # collect array elements from the user
147
+ option_value = nil
148
+ until option_value
149
+ begin
150
+ option_value = get_array_from_user(klass)
151
+ option_value = New::Task.validate_option(option_name, option_settings, option_value)
152
+ rescue
153
+ option_value = nil
154
+ end
155
+ end
156
+
157
+ # collect hash option type values
158
+ when option_type == Hash
159
+ # loop through the expected keys from the validation and get users input
160
+ option_value = nil
161
+ until option_value
162
+ begin
163
+ option_value = get_hash_from_user(option_settings[:validation])
164
+ option_value = New::Task.validate_option(option_name, option_settings, option_value)
165
+ rescue
166
+ option_value = nil
167
+ end
168
+ end
169
+
170
+ # collect non array/hash option type value
171
+ else
172
+ option_value = nil
173
+ until option_value
174
+ A.sk '', :newline => false, :preset => :prompt do |response|
175
+ option_value = New::Task.validate_option(option_name, option_settings, response) rescue nil
176
+ end
177
+ end
178
+ end
179
+
180
+ newfile_object[:tasks][task.name][option_name] = option_value
181
+ S.ay
182
+ end
183
+ end
184
+
185
+ # write project Newfile
186
+ project_newfile = File.join New::PROJECT_DIRECTORY, New::NEWFILE_NAME
187
+ File.open project_newfile, 'w+' do |f|
188
+ f.write newfile_object.deep_stringify_keys.to_yaml
29
189
  end
190
+
191
+ # Success Message
192
+ S.ay "A `#{'Newfile'.green}` was successfully created for your project `#{name.to_s.green}`"
193
+ S.ay 'Open the file to verify the values are correct, and make any neccessary modifications.'
194
+ S.ay "You are now ready to run `#{'new release'.green}` to release your software into the wild!"
195
+ S.ay
30
196
  end
31
197
 
32
- desc 'init', 'Set up your home directory folder for storing custom templates and default configuration'
33
- def init
34
- if Dir.exists? New::CUSTOM_DIR
35
- New.say 'Home folder already exists.', type: :warn
36
- else
37
- # create folders
38
- New.say 'Creating home folder.', type: :success
39
- FileUtils.mkdir_p File.join(New::CUSTOM_DIR, New::TASKS_DIR_NAME)
40
- FileUtils.mkdir_p File.join(New::CUSTOM_DIR, New::TEMPLATES_DIR_NAME)
41
-
42
- # create config file
43
- New.say 'Creating default configuration file.', type: :success
44
- File.open File.join(New::CUSTOM_DIR, New::CONFIG_FILE), 'w' do |f|
45
- f.write New::Template::CUSTOM_CONFIG_TEMPLATE.deep_stringify_keys.to_yaml
198
+ desc 'tasks', 'List all available tasks'
199
+ option :sources, :type => :boolean, :aliases => ['-s'], :default => false, :desc => 'Show/Hide task sources'
200
+ def tasks args = {}
201
+ # merge into default options
202
+ options = {
203
+ :show_source => (@options['sources'] || false),
204
+ :load_newfiles => true,
205
+ :load_sources => true
206
+ }.merge(args)
207
+
208
+ New.load_newfiles if options[:load_newfiles]
209
+ if options[:load_sources]
210
+ S.ay
211
+ S.ay 'Fetching sources...', :header
212
+ S.ay "Use the #{'green'.green} value for defining task sources in your Newfile", :indent => 2 if options[:show_source]
213
+ S.ay
214
+ New::Source.load_sources
215
+ end
216
+
217
+ New::Source.sources.each do |source_name, source|
218
+ # determine the widest task & add some padding
219
+ longest_task_length = source.tasks.keys.map(&:length).max
220
+
221
+ S.ay source_name.to_s, :indent => 2, :newline => false, :style => :underline
222
+ S.ay source.path, :highlight_value
223
+
224
+ source.tasks.each do |task_name, task|
225
+ if options[:show_source]
226
+ padding = longest_task_length + source_name.to_s.length + 2
227
+ S.ay "#{source_name}##{task_name}", :preset => :header, :newline => false, :indent => 2, :padding => padding, :justify => :ljust
228
+ else
229
+ padding = longest_task_length + 2
230
+ S.ay task_name.to_s, :preset => :header, :newline => false, :indent => 2, :padding => padding, :justify => :ljust
231
+ end
232
+ S.ay task.description, :indent => 2
46
233
  end
47
234
 
48
- New.say "Edit `#{File.join(New::CUSTOM_DIR, New::CONFIG_FILE)}` with your custom configuration details.", type: :warn
235
+ S.ay
49
236
  end
50
237
  end
51
238
 
52
- desc 'release', 'Release your new code (Run from within a project directory!)'
239
+ desc 'release', 'Release a new version of your project'
240
+ option :verbose, :type => :boolean, :aliases => ['-v'], :default => false, :desc => 'Verbose mode'
241
+ option :skip, :type => :array, :aliases => ['-s'], :default => [], :desc => 'Tasks to skip for this release'
53
242
  def release
54
- project_config_file = File.join(Dir.pwd, New::CONFIG_FILE)
55
- raise unless File.exists? project_config_file
243
+ New.set_verbose if @options['verbose']
244
+ New.set_cli
245
+ New.load_newfiles
56
246
 
57
- # get project config file
58
- project_config = YAML.load(File.open(project_config_file)).deep_symbolize_keys!
247
+ # request the version to bump
248
+ S.ay
249
+ S.ay 'Releasing a new version of: ', :highlight_key
250
+ S.ay New.new_object[:name], :highlight_value
251
+ S.ay 'What do you want to bump: ', :highlight_key
252
+ S.ay "[#{'Mmp'.green}] (#{'M'.green}ajor / #{'m'.green}inor / #{'p'.green}atch)", :preset => :highlight_value, :color => :white
253
+ version = Semantic::Version.new New.new_object[:version]
254
+ version_bump_part = nil
255
+ until version_bump_part
256
+ S.ay 'Current Version:', :highlight_key
257
+ A.sk version.to_s, :highlight_value do |response|
258
+ version_bump_part = case response
259
+ when 'M'
260
+ version.major += 1
261
+ version.minor = 0
262
+ version.patch = 0
263
+ when 'm'
264
+ version.minor += 1
265
+ version.patch = 0
266
+ when 'p'
267
+ version.patch += 1
268
+ else
269
+ S.ay 'You must choose from [Mmp]', :error
270
+ nil
271
+ end
272
+ end
273
+ end
274
+ S.ay 'New Version: ', :highlight_key
275
+ S.ay version.to_s, :highlight_value
276
+ S.ay
59
277
 
60
- # extract tasks from configuration
61
- tasks = (project_config[:tasks] || {}).keys.map(&:to_sym)
278
+ # collect a list of changes in this version
279
+ changelog = get_changelog_from_user
280
+ S.ay
62
281
 
63
- tasks.each do |task|
64
- # require custom task and initialize it
65
- begin
66
- if New.custom_tasks.include? task
67
- require "#{New::CUSTOM_DIR}/#{New::TASKS_DIR_NAME}/#{task}/#{task}"
68
- else
69
- require "#{New::DEFAULT_DIR}/#{New::TASKS_DIR_NAME}/#{task}/#{task}"
282
+ # show tasks
283
+ S.ay "#{'Running Tasks:'.green} (in order)"
284
+ skip_tasks = @options['skip'].map(&:to_sym)
285
+ New.new_object[:tasks].keys.each do |task|
286
+ if skip_tasks.include?(task)
287
+ S.ay "#{task.to_s} (skipped)", :preset => :fine_print, :indent => 4
288
+ else
289
+ S.ay task.to_s, :indent => 2
290
+ end
291
+ end
292
+ S.ay
293
+
294
+ New.new version.to_s, changelog, @options['skip']
295
+ end
296
+
297
+ desc 'version', 'Show the current version'
298
+ def version
299
+ New.load_newfiles
300
+ S.ay New.new_object[:name], :highlight_key
301
+ S.ay New.new_object[:version], :highlight_value
302
+ end
303
+
304
+ desc 'test', 'Run task tests from sources'
305
+ option :watch, :type => :boolean, :aliases => ['-w'], :desc => 'Watch local tasks for changes and run tests'
306
+ option :source, :type => :string, :aliases => ['-s'], :desc => 'Source name'
307
+ option :task, :type => :string, :aliases => ['-t'], :desc => 'Task name'
308
+ def test
309
+ spec_paths = []
310
+ watch_dirs = []
311
+
312
+ New.load_newfiles
313
+ New::Source.load_sources
314
+
315
+ # create a hash with a single source if one is passed
316
+ sources = if @options['source']
317
+ source_name = @options['source'].to_sym
318
+ source_hash = {}
319
+ source_hash[source_name] = New::Source.sources[source_name]
320
+ source_hash
321
+ else
322
+ New::Source.sources
323
+ end
324
+
325
+ # collect specs to run/watch
326
+ sources.each do |source_name, source|
327
+ next unless source
328
+
329
+ # create a hash with a single task if one is passed
330
+ tasks = @options['task'] ? [source.tasks[options['task'].to_sym]] : source.tasks
331
+ tasks = if @options['task']
332
+ task_name = @options['task'].to_sym
333
+ task_hash = {}
334
+ task_hash[task_name] = source.tasks[task_name]
335
+ task_hash
336
+ else
337
+ source.tasks
338
+ end
339
+
340
+ tasks.each do |task_name, task|
341
+ next unless task.path
342
+
343
+ spec_path = File.join(File.dirname(task.path), "#{task_name}_task_spec.rb")
344
+
345
+ # if the source/task has a spec file, and the source is local, watch the task directory for changes and run the spec whenever anything changes
346
+ if File.file?(spec_path) && File.directory?(source.path)
347
+ # find task directory in original path, not the sourcerer tmp directory
348
+ original_task_dir_path = File.dirname(Dir[File.join(source.path, '**', File.basename(task.path))][0])
349
+
350
+ watch_dirs << original_task_dir_path
351
+ spec_paths << spec_path
70
352
  end
71
- rescue LoadError
72
- New.say "No task '#{task}' found!", type: :fail
73
- next
74
353
  end
75
- New.say ">>> Initializing #{task.to_s.humanize} Task...", type: :warn
76
- "New::Task::#{task.to_s.classify}".constantize.new project_config
354
+ end
355
+
356
+ # run tests
357
+ if !spec_paths.empty?
358
+ # S.ay "Running tests for `#{task_name}` task in `#{source_name}` source...", :warn
359
+ Kernel::system "bundle exec rspec #{spec_paths.join(' ')}"
360
+ end
361
+
362
+ # watch tests
363
+ # if watch files are found, start a listener to run the spec
364
+ if @options['watch'] && !watch_dirs.empty?
365
+ listener = Listen.to *watch_dirs do |modified, added, removed|
366
+ all = modified + added + removed
367
+
368
+ # find sibling spec file from modified file
369
+ spec_path = all.collect{ |file| Dir[File.join(File.dirname(file), '*_spec.rb')] }.flatten.first
370
+
371
+ Kernel::system "bundle exec rspec #{spec_path}"
372
+ end
373
+ listener.start
374
+ Kernel::sleep
77
375
  end
78
376
  end
79
377
 
80
- private
378
+ no_commands do
379
+ def get_changelog_from_user
380
+ S.ay 'Lets add some items to the changelog', :header
381
+ S.ay 'Add multiple entries by pressing ENTER after each one', :instruction
382
+ S.ay 'Enter an empty value to finish', :instruction
81
383
 
82
- def project template, name
83
- if Dir.exists? File.join(Dir.pwd, name)
84
- New.say "A project named #{name} already exists...", type: :warn
85
- New.say " Overwrite it? [Yn]", type: :warn
86
- exit if STDIN.gets.chomp! != 'Y'
384
+ user_changelog = []
385
+ user_response = nil
87
386
 
88
- # Delete existing project
89
- FileUtils.rm_rf File.join(Dir.pwd, name)
387
+ # add entries to the changelog until an empty string is entered
388
+ until user_response == '' && !user_changelog.empty?
389
+ A.sk user_changelog.compact.join("\n"), :prompt do |response|
390
+ if response.empty?
391
+ user_response = ''
392
+ next
393
+ end
394
+
395
+ user_changelog << user_response = response
396
+ end
397
+ end
398
+
399
+ user_changelog
90
400
  end
91
401
 
92
- New::Project.new template, name
402
+ def get_array_from_user validation = String
403
+ # start to build the array of user values
404
+ user_array = []
405
+
406
+ # convert array to hash of Strings
407
+ if validation.is_a? Array
408
+ validation_hash = {}
409
+ validation.each do |v|
410
+ validation_hash[v.to_sym] = String
411
+ end
412
+ validation = validation_hash
413
+ end
414
+
415
+ if validation.is_a? Hash
416
+ S.ay 'We need to collect a list of complex objects', :header
417
+
418
+ user_response = nil
419
+ until user_response == 'n'
420
+ S.ay "#{user_array.length} objects created: ", :indent => 2, :newline => false
421
+ S.ay user_array.join(', '), :highlight_value
422
+ A.sk 'Press ENTER to create another object. Enter `n` to stop.', :prompt do |response|
423
+ if response == 'n'
424
+ user_response = 'n'
425
+ next
426
+ end
427
+
428
+ user_response = get_hash_from_user(validation, false)
429
+ user_array << user_response
430
+ end
431
+ end
432
+
433
+ else
434
+ S.ay "We need to collect a list of #{validation}s", :header
435
+ S.ay 'Add multiple values by pressing ENTER after each one', :instruction
436
+ S.ay 'Enter an empty value to finish', :instruction
437
+
438
+ # add to the array until an empty string is entered
439
+ user_response = nil
440
+ until user_response == ''
441
+ A.sk user_array.compact.join(', '), :prompt do |response|
442
+
443
+ # if the option is valid, pass through the empty string to end entering values
444
+ if response.empty?
445
+ user_response = ''
446
+ next
447
+ end
448
+
449
+ user_response = New::Task.validate_class(response, validation) rescue nil
450
+ user_array << user_response
451
+ end
452
+ end
453
+ end
454
+
455
+ return user_array.compact
456
+ end
457
+
458
+ def get_hash_from_user validation = {}, allow_custom_keys = true
459
+ # start to build the hash of user values
460
+ user_hash = {}
461
+
462
+ # convert array to hash of Strings
463
+ if validation.is_a? Array
464
+ validation_hash = {}
465
+ validation.each do |v|
466
+ validation_hash[v.to_sym] = String
467
+ end
468
+ validation = validation_hash
469
+ end
470
+
471
+ # get user values for required validation keys
472
+ validation.each do |key, klass|
473
+ S.ay 'We need to collect some required values', :header
474
+
475
+ user_response = nil
476
+ until user_response
477
+
478
+ # do not allow nested arrays/hashes
479
+ # these should be declared as their own option
480
+ if klass == Array || klass == Hash
481
+ S.ay 'Hash options cannot have nested Arrays or Hashes. They should be declared as their own option.', :error
482
+ exit
483
+ end
484
+
485
+ A.sk "Enter a VALUE for `#{key}`", :prompt do |response|
486
+ # make sure validation keys have a value
487
+ if response == ''
488
+ user_response = nil
489
+ next
490
+ end
491
+
492
+ begin
493
+ user_response = New::Task.validate_class(response, klass)
494
+ user_hash[key] = user_response
495
+ S.ay user_hash
496
+ rescue
497
+ user_response = nil
498
+ end
499
+ end
500
+ end
501
+ end
502
+
503
+ if allow_custom_keys
504
+ # Allow users to enter custom keys AND values
505
+ S.ay 'You can add custom keys & values if you want', :header
506
+ S.ay 'Add multiple key/value pairs by pressing ENTER after each one', :instruction
507
+ S.ay 'Enter an empty key to finish', :instruction
508
+
509
+ user_key_response = nil
510
+ until user_key_response == ''
511
+ A.sk 'Enter a KEY name', :prompt do |key_response|
512
+ # exit loop if user is done entering info
513
+ if key_response == ''
514
+ user_key_response = ''
515
+ next
516
+ end
517
+
518
+ user_value_response = nil
519
+ until user_value_response
520
+ A.sk "Enter a VALUE for `#{key_response}`", :prompt do |value_response|
521
+ # make sure value exists for user created key
522
+ if value_response == ''
523
+ user_value_response = nil
524
+ next
525
+ end
526
+
527
+ # create key/value pair
528
+ user_hash[key_response.to_sym] = user_value_response = value_response
529
+ S.ay user_hash
530
+ end
531
+ end
532
+ end
533
+ end
534
+ end
535
+
536
+ return user_hash
537
+ end
93
538
  end
539
+
540
+ default_task :release
94
541
  end