noe 1.0.0 → 1.1.0

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 (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")