noe 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/CHANGELOG.md +27 -0
  2. data/Gemfile +2 -0
  3. data/LICENCE.md +22 -0
  4. data/README.md +111 -45
  5. data/Rakefile +18 -20
  6. data/bin/noe +1 -1
  7. data/lib/noe.rb +7 -21
  8. data/lib/noe/commons.rb +23 -0
  9. data/lib/noe/config.yaml +2 -2
  10. data/lib/noe/ext/array.rb +18 -0
  11. data/lib/noe/ext/hash.rb +48 -0
  12. data/lib/noe/go.rb +158 -40
  13. data/lib/noe/install.rb +13 -7
  14. data/lib/noe/list.rb +34 -10
  15. data/lib/noe/loader.rb +67 -0
  16. data/lib/noe/main.rb +15 -15
  17. data/lib/noe/prepare.rb +121 -0
  18. data/lib/noe/show_spec.rb +45 -0
  19. data/lib/noe/template.rb +84 -4
  20. data/noe.gemspec +186 -30
  21. data/spec/ext/hash/methodize_spec.rb +30 -0
  22. data/spec/noe_spec.rb +4 -0
  23. data/spec/spec_helper.rb +0 -2
  24. data/spec/template/entry/infer_wlang_dialect_spec.rb +31 -0
  25. data/tasks/gem.rake +44 -0
  26. data/tasks/spec_test.rake +61 -0
  27. data/tasks/unit_test.rake +56 -0
  28. data/tasks/yard.rake +36 -0
  29. data/templates/ruby/CHANGELOG.md +9 -1
  30. data/templates/ruby/README.md +47 -10
  31. data/templates/ruby/noespec.yaml +195 -26
  32. data/templates/ruby/short.yaml +33 -0
  33. data/templates/ruby/src/Gemfile +2 -0
  34. data/templates/ruby/src/LICENCE.md +22 -0
  35. data/templates/ruby/src/Manifest.txt +11 -0
  36. data/templates/ruby/src/README.md +6 -1
  37. data/templates/ruby/src/Rakefile +16 -36
  38. data/templates/ruby/src/__lower__.gemspec +178 -23
  39. data/templates/ruby/src/lib/__lower__.rb +5 -2
  40. data/templates/ruby/src/lib/__lower__/loader.rb +65 -0
  41. data/templates/ruby/src/spec/spec_helper.rb +0 -2
  42. data/templates/ruby/src/tasks/gem.rake +44 -0
  43. data/templates/ruby/src/tasks/spec_test.rake +61 -0
  44. data/templates/ruby/src/tasks/unit_test.rake +56 -0
  45. data/templates/ruby/src/tasks/yard.rake +36 -0
  46. metadata +349 -79
  47. data/LICENCE.txt +0 -20
  48. data/lib/noe/create.rb +0 -77
  49. data/templates/ruby/src/LICENCE.txt +0 -20
@@ -1,10 +1,10 @@
1
1
  module Noe
2
2
  class Main
3
3
  #
4
- # Instantiate a project template using a .noespec file.
4
+ # Instantiate a project template using a .noespec file
5
5
  #
6
6
  # SYNOPSIS
7
- # #{program_name} #{command_name} [options] [SPEC_FILE]
7
+ # #{program_name} #{command_name} [--dry-run] [--force|--interactive|--add-only|--safe-override] [SPEC_FILE]
8
8
  #
9
9
  # OPTIONS
10
10
  # #{summarized_options}
@@ -14,18 +14,18 @@ module Noe
14
14
  # given as first argument. If no spec file is specified, Noe expects
15
15
  # one .noespec file to be present in the current directory and uses it.
16
16
  #
17
- # This command is generally used immediately after invoking 'create',
17
+ # This command is generally used immediately after invoking 'prepare',
18
18
  # on an almost empty directory. By default it safely fails if any file
19
- # or directory would be overriden by the instantiation process. This
20
- # safe behavior can be bypassed through the --force and --add-only
21
- # options.
19
+ # would be overriden by the instantiation process. This safe behavior
20
+ # can be bypassed through the --force, --add-only, --interactive and
21
+ # --safe-override options.
22
22
  #
23
23
  # TYPICAL USAGE
24
24
  #
25
25
  # When a fresh new project is created, this command is typically used
26
- # with the following scenario
26
+ # within the following scenario
27
27
  #
28
- # noe create --ruby hello_world
28
+ # noe prepare --template=ruby hello_world
29
29
  # cd hello_world
30
30
  # edit hello_world.noespec
31
31
  # noe go
@@ -41,17 +41,29 @@ module Noe
41
41
  # rm README.md hello_world.gemspec
42
42
  # noe go --add-only
43
43
  #
44
+ # If you want to regenerate some files by controlling what will be
45
+ # overriden:
46
+ #
47
+ # noe go --interactive
48
+ #
49
+ # If you want to regenerate some files according to the template
50
+ # manifest:
51
+ #
52
+ # noe go --safe-override
53
+ #
44
54
  class Go < Quickl::Command(__FILE__, __LINE__)
45
55
  include Noe::Commons
46
56
 
47
- # Dry-run mode ?
48
57
  attr_reader :dry_run
49
58
 
50
- # Force mode ?
51
59
  attr_reader :force
60
+ alias :force? :force
52
61
 
53
- # Only make additions ?
54
62
  attr_reader :adds_only
63
+ alias :adds_only? :adds_only
64
+
65
+ attr_reader :safe_override
66
+ alias :safe_override? :safe_override
55
67
 
56
68
  # Install options
57
69
  options do |opt|
@@ -60,70 +72,171 @@ module Noe
60
72
  "Say what would be done but don't do it"){
61
73
  @dry_run = true
62
74
  }
75
+ opt.separator ""
76
+ opt.separator "File overriding control: "
63
77
  @force = false
64
78
  opt.on('--force', '-f',
65
79
  "Force overriding on all existing files"){
66
80
  @force = true
67
81
  }
82
+ @interactive = false
83
+ opt.on('--interactive', '-i',
84
+ "Request the user to take a decision"){
85
+ @interactive = true
86
+ @highline = HighLine.new
87
+ }
68
88
  @adds_only = false
69
89
  opt.on('--add-only', '-a',
70
90
  "Only make additions, do not override any existing file"){
71
91
  @adds_only = true
72
92
  }
93
+ @safe_override = false
94
+ opt.on('--safe-override', '-s',
95
+ "Follow safe-override information provided by the manifest"){
96
+ @safe_override = true
97
+ }
98
+ Commons.add_common_options(opt)
99
+ end
100
+
101
+ # Checks if the interactive mode is enabled. If yes, highline is prepared
102
+ # as a side effect.
103
+ def interactive?
104
+ if @interactive and not(@highline)
105
+ begin
106
+ require "highline"
107
+ @highline = HighLine.new
108
+ rescue LoadError
109
+ raise Quickl::Exit.new(1), "Highline is required for interactive mode, try 'gem install highline'"
110
+ end
111
+ else
112
+ @interactive
113
+ end
73
114
  end
74
115
 
75
- def build_one(entry, variables)
116
+ def say(what, color)
117
+ if @highline
118
+ @highline.say(@highline.color(what, color))
119
+ else
120
+ puts what
121
+ end
122
+ end
123
+
124
+ def choose(&block)
125
+ @highline.choose(&block)
126
+ end
127
+
128
+ # Checks if one is a file and the other a directory or the inverse
129
+ def kind_clash?(entry, relocated)
130
+ (entry.file? and File.directory?(relocated)) or
131
+ (entry.directory? and File.file?(relocated))
132
+ end
133
+
134
+ def build_one_directory(entry, variables)
76
135
  relocated = entry.relocate(variables)
77
136
  todo = []
78
137
 
79
- # The file already exists, we should maybe do something
138
+ skipped = false
139
+ if File.exists?(relocated)
140
+ # file exists already exists, check what can be done!
141
+ if kind_clash?(entry, relocated)
142
+ if interactive?
143
+ say("File #{relocated} conflicts with directory to create", :red)
144
+ choose do |menu|
145
+ menu.prompt = "What do we do?"
146
+ menu.index = :letter
147
+ menu.choice(:abord) { raise Quickl::Exit.new(1), "Noe aborted!" }
148
+ menu.choice(:remove){ todo << Rm.new(entry, variables) }
149
+ end
150
+ elsif force?
151
+ todo << Rm.new(entry, variables)
152
+ else
153
+ raise Quickl::Exit.new(2), "Noe aborted: file #{relocated} already exists.\n"\
154
+ "Use --force to override or --interactive for more options."
155
+ end
156
+ else
157
+ # file exists and is already a folder; we simply do nothing
158
+ # because there is no dangerous action here
159
+ skipped = true
160
+ end
161
+ end
162
+
163
+ # Create the directory unless it has been explicitely skipped
164
+ unless skipped
165
+ todo << MkDir.new(entry, variables)
166
+ end
167
+
168
+ todo
169
+ end
170
+
171
+ def build_one_file(entry, variables)
172
+ relocated = entry.relocate(variables)
173
+ todo = []
174
+
175
+ skipped = false
80
176
  if File.exists?(relocated)
81
- if force
82
- unless entry.directory? and File.directory?(relocated)
177
+ # name clash, the file exists
178
+ if adds_only?
179
+ # file exists and we are only allowed to add new things
180
+ # we just do nothing
181
+ skipped = true
182
+ elsif interactive? and kind_clash?(entry, relocated)
183
+ say("Directory #{relocated} conflicts with file to create", :red)
184
+ choose do |menu|
185
+ menu.prompt = "What do we do?"
186
+ menu.index = :letter
187
+ menu.choice(:abord) { raise Quickl::Exit.new(1), "Noe aborted!" }
188
+ menu.choice(:remove){ todo << Rm.new(entry, variables) }
189
+ menu.choice(:skip) { skipped = true }
190
+ end
191
+ elsif interactive?
192
+ say("File #{relocated} already exists", :red)
193
+ choose do |menu|
194
+ would = entry.safe_override? ? :override : :skip
195
+ menu.prompt = "What do we do? (safe-override would #{would})"
196
+ menu.index = :letter
197
+ menu.choice(:abord) { raise Quickl::Exit.new(1), "Noe aborted!" }
198
+ menu.choice(:override){ todo << Rm.new(entry, variables) }
199
+ menu.choice(:skip) { skipped = true }
200
+ end
201
+ elsif safe_override?
202
+ if entry.safe_override?
83
203
  todo << Rm.new(entry, variables)
204
+ else
205
+ skipped = true
84
206
  end
85
- elsif adds_only
86
- return todo
207
+ elsif force?
208
+ todo << Rm.new(entry, variables)
87
209
  else
88
- raise Noe::Error, "Noe aborted: file #{relocated} already exists.\n"\
89
- "Use --force to override."
210
+ raise Quickl::Exit.new(2), "Noe aborted: file #{relocated} already exists.\n"\
211
+ "Use --force to override or --interactive for more options."
90
212
  end
91
213
  end
92
214
 
93
- # Create directories
94
- if entry.directory? and not(File.exists?(relocated))
95
- todo << MkDir.new(entry, variables)
96
-
97
- # Create files
98
- elsif entry.file?
215
+ # Add file instantiation unless skipped
216
+ unless skipped
99
217
  todo << FileInstantiate.new(entry, variables)
100
-
101
218
  end
219
+
102
220
  todo
103
221
  end
104
222
 
105
223
  def execute(args)
106
224
  raise Quickl::Help if args.size > 1
107
225
 
108
- # Find spec file
109
- spec_file = if args.size == 1
110
- valid_read_file!(args.first)
111
- else
112
- spec_files = Dir['*.noespec']
113
- if spec_files.size > 1
114
- raise Noe::Error, "Ambiguous request, multiple specs: #{spec_files.join(', ')}"
115
- end
116
- spec_files.first
117
- end
118
-
119
226
  # Load spec now
227
+ spec_file = find_noespec_file(args)
120
228
  spec = YAML::load(File.read(spec_file))
121
229
  template = template(spec['template-info']['name'])
122
- variables = spec['variables']
230
+ template.merge_spec(spec)
231
+ variables = template.variables
123
232
 
124
233
  # Build what has to be done
125
234
  commands = template.collect{|entry|
126
- build_one(entry, variables)
235
+ if entry.file?
236
+ build_one_file(entry, variables)
237
+ elsif entry.directory?
238
+ build_one_directory(entry, variables)
239
+ end
127
240
  }.flatten
128
241
 
129
242
  # let's go now
@@ -144,6 +257,10 @@ module Noe
144
257
  @entry, @variables = entry, variables
145
258
  end
146
259
 
260
+ def template
261
+ entry.template
262
+ end
263
+
147
264
  def relocated
148
265
  entry.relocate(variables)
149
266
  end
@@ -178,7 +295,8 @@ module Noe
178
295
 
179
296
  def run
180
297
  File.open(relocated, 'w') do |out|
181
- dialect = "wlang/active-string"
298
+ dialect = entry.wlang_dialect
299
+ variables.methodize!
182
300
  out << WLang::file_instantiate(entry.realpath, variables, dialect)
183
301
  end
184
302
  end
@@ -2,7 +2,7 @@ require 'fileutils'
2
2
  module Noe
3
3
  class Main
4
4
  #
5
- # Install default configuration and template.
5
+ # Install default configuration and template (not required).
6
6
  #
7
7
  # SYNOPSIS
8
8
  # #{program_name} #{command_name} [--force] [FOLDER]
@@ -11,12 +11,17 @@ module Noe
11
11
  # #{summarized_options}
12
12
  #
13
13
  # DESCRIPTION
14
- # This command will install Noe's default configuration under
15
- # FOLDER/.noerc and a default ruby template under FOLDER/.noe.
16
- # Unless stated otherwise, FOLDER is user's home.
14
+ # This command will install Noe's default configuration under FOLDER/.noerc
15
+ # and a default ruby template under FOLDER/.noe. Unless stated otherwise,
16
+ # FOLDER is user's home.
17
17
  #
18
- # If FOLDER/.noerc already exists, the comand safely fails.
19
- # Use --force to override existing configuration.
18
+ # If FOLDER/.noerc already exists, the comand safely fails. Use --force to
19
+ # override existing configuration.
20
+ #
21
+ # TIP
22
+ # Installing default templates and configuration is NOT required. Noe uses
23
+ # their internal representation by default. Use 'noe install' only if you
24
+ # plan to create your own templates or want to tune default ones.
20
25
  #
21
26
  class Install < Quickl::Command(__FILE__, __LINE__)
22
27
  include Noe::Commons
@@ -31,6 +36,7 @@ module Noe
31
36
  "Force overriding on all existing files"){
32
37
  @force = true
33
38
  }
39
+ Commons.add_common_options(opt)
34
40
  end
35
41
 
36
42
  def execute(args)
@@ -79,7 +85,7 @@ module Noe
79
85
  puts " * cat #{noerc_file}"
80
86
  puts " * ls -lA #{noe_folder}"
81
87
  puts " * noe list"
82
- puts " * noe create hello_world"
88
+ puts " * noe prepare hello_world"
83
89
  puts
84
90
  puts "Thank you for using Noe (v#{Noe::VERSION}), enjoy!"
85
91
  end
@@ -1,13 +1,13 @@
1
1
  module Noe
2
2
  class Main
3
3
  #
4
- # List available templates.
4
+ # List available templates
5
5
  #
6
6
  # SYNOPSIS
7
7
  # #{program_name} #{command_name}
8
8
  #
9
9
  # DESCRIPTION
10
- # This command list project templates found in the templates folder.
10
+ # This command lists project templates found in the templates folder.
11
11
  # The later is checked as a side effect.
12
12
  #
13
13
  # TIP
@@ -16,20 +16,44 @@ module Noe
16
16
  class List < Quickl::Command(__FILE__, __LINE__)
17
17
  include Noe::Commons
18
18
 
19
+ options do |opt|
20
+ Commons.add_common_options(opt)
21
+ end
22
+
23
+ def max(i,j)
24
+ i > j ? i : j
25
+ end
26
+
19
27
  def execute(args)
20
28
  unless args.empty?
21
29
  raise Quickl::InvalidArgument, "Needless argument: #{args.join(', ')}"
22
30
  end
23
31
 
24
- puts "Templates located in: #{templates_dir}"
25
- Dir[File.join(templates_dir, '**')].collect do |tpl_dir|
32
+ tpls = Dir[File.join(templates_dir, '**')].collect{|tpldir| Template.new(tpldir)}
33
+ columns = [:name, :version, :layouts, :summary]
34
+ data = [ columns ] + tpls.collect{|tpl|
26
35
  begin
27
- tpl = Template.new(tpl_dir)
28
- puts " * %-#{25}s %s" % [ "#{tpl.name} (v#{tpl.version})" , tpl.description ]
29
- tpl
30
- rescue => ex
31
- puts " * %-#{25}s %s" % [File.basename(tpl_dir), ex.message]
32
- nil
36
+ columns.collect{|col|
37
+ if col == :layouts
38
+ (tpl.send(col) - ["noespec"]).to_a.join(',')
39
+ else
40
+ tpl.send(col).to_s.strip
41
+ end
42
+ }
43
+ rescue Exception => ex
44
+ [ tpl.name, "", "", "Template error: #{ex.message}" ]
45
+ end
46
+ }
47
+ lengths = data.inject([0,0,0,0]){|memo,columns|
48
+ (0..3).collect{|i| max(memo[i], columns[i].to_s.length)}
49
+ }
50
+ puts "Templates available in #{templates_dir}"
51
+ data.each_with_index do |line,i|
52
+ current = (config.default == line[0])
53
+ puts (current ? " -> " : " ") +
54
+ "%-#{lengths[0]}s %-#{lengths[1]}s %-#{lengths[2]}s %-#{lengths[3]}s" % line
55
+ if i==0
56
+ puts "-"*lengths.inject(0){|memo,i| memo+i+3}
33
57
  end
34
58
  end
35
59
  end
@@ -0,0 +1,67 @@
1
+ module Noe
2
+ #
3
+ # This module provides tools to load stdlib and gem dependencies.
4
+ #
5
+ module Loader
6
+
7
+ #
8
+ # This method allows requiring dependencies with some flexibility.
9
+ #
10
+ # Implemented algorithm makes greedy choices about the environment:
11
+ # 1. It first attempts a simple <code>Kernel.require(name)</code> before
12
+ # anything else (even bypassing version requirement)
13
+ # 2. If step 1 fails with a LoadError then it falls back requiring the
14
+ # gem with specified version (defaults to >= 0) and retries step 1.
15
+ # 3. If step 2 fails with a NameError, 'rubygems' are required and step
16
+ # 2 is retried.
17
+ # 4. If step 3. fails, the initial LoadError is reraised.
18
+ #
19
+ # Doing so ensures flexibility for the users of the library by not making
20
+ # wrong assumptions about their environment. Testing the library is also
21
+ # made easier, as illustrated in the examples below. Please note that this
22
+ # method is useful to load external dependencies of your code only, not
23
+ # .rb files of your own library.
24
+ #
25
+ # Examples:
26
+ #
27
+ # # Require something from the standard library
28
+ # Noe::Loader.require('fileutils')
29
+ #
30
+ # # Require a gem without specifing any particular version
31
+ # Noe::Loader.require('highline')
32
+ #
33
+ # # Require a gem, specifing a particular version
34
+ # Noe::Loader.require('foo', "~> 1.6")
35
+ #
36
+ # # Twist the load path to use version of foo you've recently
37
+ # # forked (bypass the version requirement)
38
+ # $LOAD_PATH.unshift ... # or ruby -I...
39
+ # Noe::Loader.require('highline', "~> 1.6")
40
+ #
41
+ # Learn more about this pattern:
42
+ # - http://weblog.rubyonrails.org/2009/9/1/gem-packaging-best-practices
43
+ # - https://gist.github.com/54177
44
+ #
45
+ def require(name, version = nil)
46
+ Kernel.require name.to_s
47
+ rescue LoadError
48
+ begin
49
+ gem name.to_s, version || ">= 0"
50
+ rescue NameError
51
+ if $VERBOSE
52
+ Kernel.warn "#{__FILE__}:#{__LINE__}: warning: requiring rubygems myself, "\
53
+ " you should use 'ruby -rubygems' instead. "\
54
+ "See https://gist.github.com/54177"
55
+ end
56
+ require "rubygems"
57
+ gem name.to_s, version || ">= 0"
58
+ end
59
+ Kernel.require name.to_s
60
+ end
61
+ module_function :require
62
+
63
+ end # module Loader
64
+ end # module Noe
65
+ Noe::Loader.require("wlang", "~> 0.10.0")
66
+ Noe::Loader.require("quickl", "~> 0.2.0")
67
+ Noe::Loader.require("highline", "~> 1.6.0")