prigner 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/CHANGELOG +248 -0
  2. data/COPYING +21 -0
  3. data/README.mkd +90 -0
  4. data/Rakefile +211 -0
  5. data/bin/prign +65 -0
  6. data/lib/prigner/builder.rb +51 -0
  7. data/lib/prigner/cli/copy.rb +0 -0
  8. data/lib/prigner/cli/list.rb +54 -0
  9. data/lib/prigner/cli/new.rb +96 -0
  10. data/lib/prigner/cli.rb +102 -0
  11. data/lib/prigner/extensions.rb +67 -0
  12. data/lib/prigner/model.rb +97 -0
  13. data/lib/prigner/project.rb +71 -0
  14. data/lib/prigner/template.rb +148 -0
  15. data/lib/prigner.rb +186 -0
  16. data/prigner.gemspec +120 -0
  17. data/share/templates/ruby/default/models/CHANGELOG +13 -0
  18. data/share/templates/ruby/default/models/COPYING +21 -0
  19. data/share/templates/ruby/default/models/README.rdoc +4 -0
  20. data/share/templates/ruby/default/models/module.rb +5 -0
  21. data/share/templates/ruby/default/specfile +16 -0
  22. data/share/templates/ruby/gem/models/CHANGELOG +13 -0
  23. data/share/templates/ruby/gem/models/COPYING +21 -0
  24. data/share/templates/ruby/gem/models/README.mkd +5 -0
  25. data/share/templates/ruby/gem/models/Rakefile +213 -0
  26. data/share/templates/ruby/gem/models/empty_test.rb +16 -0
  27. data/share/templates/ruby/gem/models/gemspec +67 -0
  28. data/share/templates/ruby/gem/models/module.rb +71 -0
  29. data/share/templates/ruby/gem/specfile +20 -0
  30. data/test/builder_test.rb +38 -0
  31. data/test/fixtures/model.rb.erb +12 -0
  32. data/test/fixtures/templates/shared/ruby/default/README.mkd +2 -0
  33. data/test/fixtures/templates/shared/ruby/default/models/README.mkd +2 -0
  34. data/test/fixtures/templates/shared/ruby/default/models/Rakefile +7 -0
  35. data/test/fixtures/templates/shared/ruby/default/models/empty_test.rb +21 -0
  36. data/test/fixtures/templates/shared/ruby/default/models/module.rb +6 -0
  37. data/test/fixtures/templates/shared/ruby/default/specfile +18 -0
  38. data/test/fixtures/templates/user/bash/default/specfile +0 -0
  39. data/test/fixtures/templates/user/ruby/program/models/README.erb +2 -0
  40. data/test/fixtures/templates/user/ruby/program/models/cli.rb.erb +5 -0
  41. data/test/fixtures/templates/user/ruby/program/models/module.rb.erb +6 -0
  42. data/test/fixtures/templates/user/ruby/program/models/program.rb.erb +3 -0
  43. data/test/fixtures/templates/user/ruby/program/specfile +18 -0
  44. data/test/fixtures/templates/user/vim/default/specfile +0 -0
  45. data/test/fixtures/templates/user/vim/plugin/specfile +0 -0
  46. data/test/fixtures/templates/user/vim/syntax/specfile +0 -0
  47. data/test/helpers.rb +22 -0
  48. data/test/model_test.rb +82 -0
  49. data/test/project_test.rb +46 -0
  50. data/test/spec_test.rb +64 -0
  51. data/test/template_test.rb +88 -0
  52. metadata +142 -0
@@ -0,0 +1,54 @@
1
+ # Copyright (c) 2010 Codigorama, Hallison Batista
2
+
3
+ require "prigner"
4
+
5
+ program = :prign
6
+ command = File.basename(__FILE__, ".rb")
7
+ @options = {}
8
+
9
+ def templates_by_namespace(indent)
10
+ templates = Prigner::Template.all
11
+ message = []
12
+ templates.map do |namespace, list|
13
+ message << "* #{namespace}"
14
+ list.map do |template|
15
+ message << ("#{indent}> %-16s %s" % [template.name, template.spec.description])
16
+ end
17
+ end
18
+ message.join("\n")
19
+ end
20
+
21
+ def show(message)
22
+ puts "#{Prigner::Version}"
23
+ puts "\n#{message}"
24
+ end
25
+
26
+ begin
27
+
28
+ ARGV.options do |arguments|
29
+
30
+ arguments.summary_indent = " "
31
+ arguments.summary_width = 24
32
+ arguments.banner = <<-end_banner.gsub /^[ ]{4}/, ''
33
+ #{Prigner::Version}
34
+
35
+ List all templates.
36
+
37
+ Usage:
38
+ #{program} #{command}
39
+
40
+ end_banner
41
+
42
+ if ARGV.empty?
43
+ show(templates_by_namespace(arguments.summary_indent))
44
+ exit 0
45
+ else
46
+ arguments.parse!
47
+ end
48
+ end
49
+
50
+ rescue => error
51
+ puts "#{program}: #{command}: #{error.message} (#{error.class})"
52
+ exit 1
53
+ end
54
+
@@ -0,0 +1,96 @@
1
+ # Copyright (c) 2010 Hallison Batista
2
+
3
+ require "prigner"
4
+
5
+ program = :prign
6
+ command = File.basename(__FILE__, ".rb")
7
+
8
+ begin
9
+ ARGV.options do |arguments|
10
+
11
+ arguments.summary_indent = " "
12
+ arguments.summary_width = 24
13
+ arguments.banner = <<-end_banner.gsub /^[ ]{6}/, ''
14
+ #{Prigner::Version}
15
+
16
+ Usage:
17
+ #{program} #{command} <namespace>[:template] <path> [options]
18
+
19
+ Templates:
20
+ #{Prigner::CLI.templates.sort.join("\n" + arguments.summary_indent)}
21
+
22
+ end_banner
23
+
24
+ unless ARGV.empty?
25
+ name = ARGV.shift
26
+ path = ARGV.shift unless name.nil?
27
+
28
+ template = Prigner::Template.load(*name.split(":"))
29
+
30
+ if template
31
+ arguments.banner = <<-end_banner.gsub /^[ ]{10}/, ''
32
+ #{Prigner::Version}
33
+
34
+ Usage:
35
+ #{program} #{command} #{template.mask} <path> [options]
36
+
37
+ end_banner
38
+
39
+ if template.options
40
+ arguments.separator "Options:"
41
+
42
+ template.options.members.map do |name|
43
+ option = template.options[name]
44
+ arguments.on("-#{name[0..0]}", "--#{name}", nil, option.description) do
45
+ option.enabled = true
46
+ end
47
+ end
48
+ end
49
+
50
+ else
51
+ raise RuntimeError, "unable to load template '#{name}'"
52
+ end
53
+ end
54
+
55
+ unless path
56
+ puts arguments
57
+ exit 0
58
+ end
59
+
60
+ arguments.parse!
61
+
62
+ project = Prigner::Project.new(path)
63
+ builder = Prigner::Builder.new(project, template)
64
+
65
+ status = Prigner::CLI::Status.new
66
+
67
+ status.setup do
68
+ state :success, :created
69
+ state :failure, :fail
70
+ state :error, :error
71
+ format :title, "\n* %-74s"
72
+ format :info, " %-69s [%7s]"
73
+ end
74
+
75
+ puts Prigner::Version
76
+
77
+ status.start "Creating project path" do
78
+ builder.make_project_path
79
+ end
80
+
81
+ status.start "Creating directories" do
82
+ builder.make_project_directories
83
+ end
84
+
85
+ status.start "Writing files" do
86
+ builder.make_project_files
87
+ end
88
+
89
+ end
90
+
91
+ rescue => error
92
+ puts "#{program}: #{command}: #{error.message} (#{error.class})"
93
+ puts "Try '#{program} #{command} -h' or '#{program} #{command} --help' for more information."
94
+ exit 1
95
+ end
96
+
@@ -0,0 +1,102 @@
1
+ # == Command-Line Interface
2
+ #
3
+ # This module look a command placed in CLI directory and run it.
4
+ module Prigner::CLI
5
+
6
+ require "rbconfig"
7
+
8
+ # Ruby VM installed in OS.
9
+ def self.ruby
10
+ File.join(*::RbConfig::CONFIG.values_at("bindir", "ruby_install_name")) +
11
+ ::RbConfig::CONFIG["EXEEXT"]
12
+ end
13
+
14
+ # The CLI path.
15
+ def self.path
16
+ "#{Prigner::ROOT}/lib/prigner/cli"
17
+ end
18
+
19
+ # List all templates as commands.
20
+ def self.templates
21
+ Prigner::Template.all.map do |namespace, templates|
22
+ templates.map{ |template| template.mask }
23
+ end.flatten
24
+ end
25
+
26
+ # List of commands placed in <tt>lib/prigner/cli/</tt>.
27
+ def self.commands
28
+ Dir["#{path}/*.rb"].map do |source|
29
+ File.basename(source, ".rb")
30
+ end.sort
31
+ end
32
+
33
+ # Source command placed in CLI directory.
34
+ def self.source(command)
35
+ "#{path}/#{command}.rb"
36
+ end
37
+
38
+ # Look command in *CLI* directory and execute (by exec).
39
+ def self.run(*args)
40
+ command = args.shift if commands.include? args.first
41
+ raise RuntimeError, "unknown command '#{args.first}'" unless command
42
+ rubyopt = "-I#{Prigner::ROOT}/lib"
43
+ exec ruby, rubyopt, source(command), *args
44
+ end
45
+
46
+ class Status
47
+
48
+ attr_reader :formats
49
+
50
+ attr_reader :count
51
+
52
+ attr_reader :states
53
+
54
+ attr_reader :current
55
+
56
+ def initialize
57
+ @states = {
58
+ :success => :done,
59
+ :failure => :fail,
60
+ :error => :err
61
+ }
62
+ @formats = {
63
+ :title => "\n* %-74s",
64
+ :info => "\n %-70s [%4s]"
65
+ }
66
+ @count = {}
67
+ @states.keys.each{|k| @count[k] = 0 }
68
+ end
69
+
70
+ def setup(&block)
71
+ instance_eval(&block)
72
+ end
73
+
74
+ def state(key, value)
75
+ return unless @states.has_key?key
76
+ @states[key] = value
77
+ @count[key] = 0
78
+ end
79
+
80
+ def format(key, format)
81
+ @formats[key] = "#{format}\n" if @formats.has_key?key
82
+ end
83
+
84
+ def increment!
85
+ @count[@current] += 1
86
+ end
87
+
88
+ def start(title, &block)
89
+ printf @formats[:title], title
90
+ hash = yield block
91
+ hash.each do |message, status|
92
+ @current = status ? :success : :failure
93
+ printf @formats[:info], message, @states[@current]
94
+ increment!
95
+ end
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+
102
+
@@ -0,0 +1,67 @@
1
+ class Symbol
2
+
3
+ # Method for comparison between symbols.
4
+ def <=>(other)
5
+ self.to_s <=> other.to_s
6
+ end
7
+
8
+ end
9
+
10
+ class Hash
11
+
12
+ alias has? has_key?
13
+
14
+ # Only symbolize all keys, including all key in sub-hashes.
15
+ def symbolize_keys
16
+ return self.clone if self.empty?
17
+ self.inject({}) do |hash, (key, value)|
18
+ hash[key.to_sym] = if value.kind_of? Hash
19
+ value.symbolize_keys
20
+ else
21
+ value
22
+ end
23
+ hash
24
+ end
25
+ end
26
+
27
+ # Set instance variables by key and value only if object respond
28
+ # to access method for variable.
29
+ def instance_variables_set_to(object)
30
+ collect do |variable, value|
31
+ object.instance_variable_set("@#{variable}", value) if object.respond_to? variable
32
+ end
33
+ object
34
+ end
35
+
36
+ # Convert to Struct including all values that are Hash class.
37
+ def to_struct
38
+ keys = self.keys.sort
39
+ members = keys.map{ |key| key.to_sym }
40
+ Struct.new(*members).new(*keys.map do |key|
41
+ if self[key].kind_of? Hash
42
+ self[key].to_struct
43
+ else
44
+ self[key]
45
+ end
46
+ end) unless self.empty?
47
+ end
48
+
49
+ end
50
+
51
+ class Struct
52
+
53
+ # Public bind.
54
+ def binding
55
+ super
56
+ end
57
+
58
+ end
59
+
60
+ class Pathname
61
+
62
+ def chpath(source, path)
63
+ self.path.gsub(%r{#{source}}, path).to_path
64
+ end
65
+
66
+ end
67
+
@@ -0,0 +1,97 @@
1
+ # == Model
2
+ #
3
+ # This class implements several methods for load a model file of template.
4
+ #
5
+ # Basically behaviour as parser based in a ERB file placed in +models+
6
+ # directory of a Template.
7
+ #
8
+ # Example:
9
+ #
10
+ # # model: jedi/knight
11
+ # class <%=name.capitalize%>
12
+ # <%for attribute in attributes%>
13
+ # attr_accessor :<%=attribute%>
14
+ # <%end%>
15
+ # def initialize(<%=attributes.join(",")%>)
16
+ # <%for attribute in attributes%>
17
+ # @<%=attribute%> = <%attribute%>
18
+ # <%end%>
19
+ # end
20
+ #
21
+ # def to_s
22
+ # <%for attribute in attributes%>
23
+ # "\n<%=attribute.capitalize%>: #{<%=attribute%>}"
24
+ # <%end%>
25
+ # end
26
+ # end
27
+ #
28
+ # <%=name%> = <%=name.capitalize%>.new("Yoda", "Jedi Grand Master")
29
+ # # end model
30
+ #
31
+ # $> model = Pringer::Model.new "jedi/knight", {
32
+ # :name => "knight",
33
+ # :attributes => [ :name, :position ]
34
+ # }
35
+ # $> model.build!
36
+ # $> model.contents
37
+ # => class Knight
38
+ # =>
39
+ # => attr_accessor :name
40
+ # => attr_accessor :position
41
+ # =>
42
+ # => def initialize(name,position)
43
+ # =>
44
+ # => @name = name
45
+ # => @position = position
46
+ # =>
47
+ # => end
48
+ # =>
49
+ # => def to_s
50
+ # =>
51
+ # => "\nName: #{name}"
52
+ # => "\nPosition: #{position}"
53
+ # =>
54
+ # => end
55
+ # => end
56
+ # =>
57
+ # => knight = Knight.new("Yoda", "Jedi Grand Master")
58
+ #
59
+ class Prigner::Model
60
+
61
+ # Model path file.
62
+ attr_reader :path
63
+
64
+ # Parsed contents.
65
+ attr_reader :contents
66
+
67
+ # Value bindings.
68
+ attr_accessor :binder
69
+
70
+ # Result file written
71
+ attr_reader :file_written
72
+
73
+ # Initializes a model passing all attributes by Hash.
74
+ def initialize(path, binder = {})
75
+ @path = Pathname.new(path)
76
+ @binder = (binder.kind_of? Hash) ? binder.to_struct : binder
77
+ end
78
+
79
+ # Build model contents.
80
+ def build!
81
+ require "erb"
82
+ @contents = ::ERB.new(@path.read).result(binder.binding)
83
+ end
84
+
85
+ # Write contents into file.
86
+ def write(file)
87
+ @file_written = file
88
+ file = Pathname.new(file)
89
+ file.dirname.mkpath
90
+ file.open "w+" do |output|
91
+ output << self.build!
92
+ end
93
+ self
94
+ end
95
+
96
+ end
97
+
@@ -0,0 +1,71 @@
1
+ # == Project
2
+ #
3
+ # This class is a simple implementation of a tree of directories. It is used
4
+ # in model files.
5
+ class Prigner::Project
6
+
7
+ # Project name.
8
+ attr_reader :name
9
+
10
+ alias project name
11
+
12
+ # Project files.
13
+ attr_reader :path
14
+
15
+ # Project timestamp
16
+ attr_reader :timestamp
17
+
18
+ # Initialize a new project directory path.
19
+ def initialize(path)
20
+ @path = File.expand_path(path =~ /^\/.*/ ? path : "#{Dir.pwd}/#{path}")
21
+ @name = File.basename(@path)
22
+ @timestamp = DateTime.now
23
+ end
24
+
25
+ def name_splited
26
+ @name.split(/[-_]/)
27
+ end
28
+
29
+ def namespace(joiner = "::")
30
+ name_splited.join(joiner)
31
+ end
32
+
33
+ def upper_camel_case_namespace(joiner = "::")
34
+ name_splited.map{ |str| str.capitalize }.join(joiner)
35
+ end
36
+
37
+ def lower_camel_case_namespace(joiner = "::")
38
+ return name if name_splited.size == 0
39
+ name_splited[1..-1].map do |str|
40
+ str.capitalize
41
+ end.unshift(name_splited.first).join(joiner)
42
+ end
43
+
44
+ def upper_case_namespace(joiner = "::")
45
+ upper_camel_case_namespace(joiner).upcase
46
+ end
47
+
48
+ def lower_case_namespace(joiner = "::")
49
+ upper_camel_case_namespace(joiner).downcase
50
+ end
51
+
52
+ def upper_camel_case_name
53
+ upper_camel_case_namespace(nil)
54
+ end
55
+
56
+ alias class_name upper_camel_case_name
57
+
58
+ def lower_camel_case_name
59
+ lower_camel_case_namespace(nil)
60
+ end
61
+
62
+ def user
63
+ Etc.getpwnam(Etc.getlogin)
64
+ end
65
+
66
+ def author
67
+ self.user.gecos.split(",").first
68
+ end
69
+
70
+ end # Project
71
+
@@ -0,0 +1,148 @@
1
+ # == Project Template
2
+ #
3
+ # This class implements several methods for load a template for a new project.
4
+ # Basically, a template is based in an YAML file for +specfile+ and a directory
5
+ # containing all files to be parsed.
6
+ #
7
+ # The template tree is based in following structure:
8
+ #
9
+ # namespace
10
+ # `-- template
11
+ # |-- models
12
+ # `-- specfile
13
+ #
14
+ # See Spec class for more information about mandatory attributes for draw your
15
+ # project using +specfile+.
16
+ class Prigner::Template
17
+
18
+ # Namespace of template.
19
+ attr_reader :namespace
20
+
21
+ # Name of template.
22
+ attr_reader :name
23
+
24
+ # List of models.
25
+ attr_reader :models
26
+
27
+ # List of directories that will created in project tree.
28
+ attr_reader :directories
29
+
30
+ # List of options (see Spec#options).
31
+ attr_reader :options
32
+
33
+ # Path to template.
34
+ attr_reader :path
35
+
36
+ # Specifications
37
+ attr_reader :spec
38
+
39
+ # Initialize a template using a path.
40
+ #
41
+ # Example:
42
+ #
43
+ # template = Prigner::Template.new "path/to/template"
44
+ #
45
+ # The template initialization will search the +specfile+ in path passed as
46
+ # argument (<tt>path/to/template/specfile</tt>) for Spec attributes.
47
+ def initialize(path)
48
+ @path = Pathname.new(path).tap{ |check| check.stat }
49
+ @namespace = @path.parent.basename.to_s
50
+ @name = @path.basename.to_s
51
+ initialize_specfile
52
+ initialize_options
53
+ initialize_directories
54
+ initialize_models
55
+ rescue Exception => error
56
+ raise RuntimeError, error.message
57
+ end
58
+
59
+ # Mask for presentation of template.
60
+ def mask
61
+ @name == "default" ? @namespace : "#{@namespace}:#{@name}"
62
+ end
63
+
64
+ # Load template from shared directories. The shared path set the home user
65
+ # directory and Prigner::Template shared files.
66
+ def self.load(namespace, template = :default)
67
+ shared_path.map do |source|
68
+ path = "#{source}/#{namespace}/#{template}"
69
+ return new(path) if File.exist? path
70
+ end
71
+ nil
72
+ end
73
+
74
+ # Look at user home and template shared path.
75
+ def self.shared_path
76
+ user_home_templates = File.join(user_home_basedir, "templates")
77
+ [ user_home_templates, "#{Prigner::ROOT}/share/templates" ]
78
+ end
79
+
80
+ # User home.
81
+ def self.user_home
82
+ File.expand_path "~"
83
+ rescue
84
+ if File::ALT_SEPARATOR then
85
+ "C:/"
86
+ else
87
+ "/"
88
+ end
89
+ end
90
+
91
+ # User home base directory for Prigner files.
92
+ def self.user_home_basedir
93
+ File.join(user_home, ".prigner")
94
+ end
95
+
96
+ # Return all template paths placed in shared user or in project base
97
+ # directory.
98
+ def self.all_template_paths
99
+ shared_path.map do |source|
100
+ Dir.glob("#{source}/*/*")
101
+ end.flatten.compact
102
+ end
103
+
104
+ # All templates grouped by namespace.
105
+ def self.all
106
+ all_template_paths.map do |path|
107
+ new(path)
108
+ end.inject({}) do |group, template|
109
+ group[template.namespace] ||= []
110
+ group[template.namespace] << template
111
+ group
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ # Return the specfile placed in template path.
118
+ def specfile
119
+ Dir["#{@path}/[Ss]pecfile"].first
120
+ end
121
+
122
+ # Load +specfile+ placed in template path.
123
+ def initialize_specfile
124
+ @spec = Prigner::Spec.load(specfile)
125
+ end
126
+
127
+ # Initialize options.
128
+ def initialize_options
129
+ @options = @spec.options.inject({}) do |options, (name, desc)|
130
+ options[name] = { :enabled => nil, :description => desc }
131
+ options
132
+ end.to_struct if @spec.options
133
+ end
134
+
135
+ def initialize_directories
136
+ @directories = @spec.directories
137
+ end
138
+
139
+ def initialize_models
140
+ @models = @spec.files.inject({}) do |models, (source, file)|
141
+ model = Prigner::Model.new(@path.join("models", source))
142
+ models[model] = file ? file : source
143
+ models
144
+ end if @spec.files
145
+ end
146
+
147
+ end
148
+