ginny 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task(default: :test)
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ginny"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting with your gem easier.
7
+
8
+ require "pry"
9
+ Pry.start
@@ -0,0 +1,2 @@
1
+ # Rebuild project documentation and serve it locally.
2
+ rm -rf .yardoc && yard server -r
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "ginny"
4
+ require "optparse"
5
+ options = {}
6
+
7
+ optparse = OptionParser.new do |opts|
8
+ opts.banner = "Usage: ginny [options] [path]"
9
+ options[:verbose] = false
10
+ opts.on("-v", "--verbose", "Output more information") do
11
+ options[:verbose] = true
12
+ end
13
+ opts.on("-h", "--help", "Display this screen") do
14
+ puts(opts)
15
+ exit(0)
16
+ end
17
+ end
18
+
19
+ optparse.parse!
20
+
21
+ ARGV.each do |f|
22
+ Ginny::Class.create(Ginny.load_file(f)).generate()
23
+ end
@@ -0,0 +1,43 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "ginny/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "ginny"
7
+ spec.version = Ginny::VERSION
8
+ spec.required_ruby_version = ">= 2.3.0"
9
+ spec.description = "Generate Ruby code."
10
+ spec.summary = spec.description
11
+ spec.authors = ["Clay Dunston"]
12
+ spec.email = ["dunstontc@gmail.com"]
13
+ spec.homepage = "https://github.com/tcd/ginny"
14
+ spec.license = "MIT"
15
+
16
+ spec.metadata = {
17
+ "homepage_uri" => spec.homepage,
18
+ "source_code_uri" => spec.homepage,
19
+ "documentation_uri" => "https://www.rubydoc.info/gems/ginny/#{spec.version}",
20
+ "changelog_uri" => "https://github.com/tcd/ginny/blob/master/CHANGELOG.md",
21
+ "yard.run" => "yri", # use "yard" to build full HTML docs.
22
+ }
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
26
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
27
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = ["ginny"]
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_development_dependency "bundler", "~> 2.0"
34
+ spec.add_development_dependency "coveralls", "~> 0.8.23"
35
+ spec.add_development_dependency "minitest", "~> 5.0"
36
+ spec.add_development_dependency "minitest-focus", "~> 1.1"
37
+ spec.add_development_dependency "minitest-reporters", "~> 1.4"
38
+ spec.add_development_dependency "pry", "~> 0.12.2"
39
+ spec.add_development_dependency "rake", "~> 10.0"
40
+ spec.add_development_dependency "simplecov"
41
+
42
+ spec.add_runtime_dependency "dry-inflector", "~> 0.2.0"
43
+ end
@@ -0,0 +1,6 @@
1
+ # Ruby code generator.
2
+ module Ginny
3
+ QUOTE = '"'.freeze
4
+ end
5
+
6
+ Dir.glob(File.join(__dir__, "ginny", "**/*.rb")).each { |file| require file }
@@ -0,0 +1,5 @@
1
+ module Ginny
2
+ # Exceptions raised by Ginny inherit from Error.
3
+ # @abstract
4
+ class Error < StandardError; end
5
+ end
@@ -0,0 +1,45 @@
1
+ require "pathname"
2
+ require "json"
3
+ require "yaml"
4
+
5
+ module Ginny
6
+
7
+ # Load data from a YAML file.
8
+ #
9
+ # @param path [String]
10
+ # @return [Hash<Symbol>]
11
+ def self.load_yaml(path)
12
+ return Ginny.symbolize_keys(YAML.load_file(File.expand_path(path)))
13
+ end
14
+
15
+ # Load data from a JSON file.
16
+ #
17
+ # @param path [String]
18
+ # @return [Hash<Symbol>]
19
+ def self.load_json(path)
20
+ return JSON.parse(File.read(File.expand_path(path)), symbolize_names: true)
21
+ end
22
+
23
+ # @param path [String] Path of the file to determine the extension of.
24
+ # @return [String]
25
+ def self.get_extension(path)
26
+ file = Pathname.new(path)
27
+ return file.extname.downcase
28
+ end
29
+
30
+ # Load data from a YAML or JSON file.
31
+ #
32
+ # @param path [String]
33
+ # @return [Hash<Symbol>]
34
+ def self.load_file(path)
35
+ case Pathname.new(path).extname.downcase
36
+ when ".yml", ".yaml"
37
+ return self.load_yaml(path)
38
+ when ".json"
39
+ return self.load_json(path)
40
+ else
41
+ raise Ginny::Error "invalid file type"
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,32 @@
1
+ module Ginny
2
+
3
+ # Used to generate a [module](https://ruby-doc.org/core-2.6.5/doc/syntax/modules_and_classes_rdoc.html).
4
+ #
5
+ # More accurately, wrap the `body` (first argument) with any following module definitions (additional arguments).
6
+ #
7
+ # @example
8
+ # Ginny.mod("puts('Hello World')", "Level1", "Level2")
9
+ # #=> module Level1
10
+ # module Level2
11
+ # puts('Hello World')
12
+ # end
13
+ # end
14
+ #
15
+ # @param body [String] Name of module namespaces.
16
+ # @param names [String,Array<String>] Name of module namespaces.
17
+ # @return [String]
18
+ def self.mod(body, *names)
19
+ names.flatten!
20
+ count = names.length
21
+ return body unless count.positive?()
22
+ level = 0
23
+ head = []
24
+ tail = []
25
+ names.each do |name|
26
+ head.push("module #{name}".indent(level))
27
+ tail.unshift("end".indent(level))
28
+ level += 2
29
+ end
30
+ return (head + [body&.indent(level)] + tail).compact.join("\n")
31
+ end
32
+ end
@@ -0,0 +1,73 @@
1
+ module Ginny
2
+ # Used to generate an instance variable with getters/setters.
3
+ #
4
+ # [attr]: https://docs.ruby-lang.org/en/master/Module.html#method-i-attr
5
+ # [attr_accessor]: https://docs.ruby-lang.org/en/master/Module.html#method-i-attr_accessor
6
+ # [attr_reader]: https://docs.ruby-lang.org/en/master/Module.html#method-i-attr_reader
7
+ class Attr
8
+
9
+ # Name of the attribute.
10
+ # @return [String]
11
+ attr_accessor :name
12
+ # Description of the attribute. [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) is supported.
13
+ # @return [String]
14
+ attr_accessor :description
15
+ # [Type](https://rubydoc.info/gems/yard/file/docs/GettingStarted.md#Declaring_Types) of the attribute.
16
+ # @return [String]
17
+ attr_accessor :type
18
+ # Default value for the attribute; set in it's Class's `initialize` function.
19
+ # @return [String]
20
+ attr_accessor :default
21
+ # If `true`, an `attr_reader` will be generated for the attribute instead of an `attr_accessor`.
22
+ # @return [Boolean]
23
+ attr_accessor :read_only
24
+
25
+ # @return [void]
26
+ def initialize()
27
+ self.read_only = false
28
+ end
29
+
30
+ # Constructor for an Attr. Use `create`, not `new`.
31
+ #
32
+ # @param args [Hash]
33
+ # @return [Attr]
34
+ def self.create(args = {})
35
+ a = Ginny::Attr.new()
36
+ a.name = args[:name]
37
+ a.description = args[:description]
38
+ a.type = args[:type]
39
+ a.read_only = args[:read_only]
40
+ return a
41
+ end
42
+
43
+ # @param array [Array<Hash>]
44
+ # @return [Array<Ginny::Attr>]
45
+ def self.from_array(array)
46
+ return array.map { |f| self.create(f) }
47
+ end
48
+
49
+ # @return [String]
50
+ def render()
51
+ parts = []
52
+ parts << (@description&.length&.positive? ? @description.comment : nil)
53
+ parts << "@return [#{self.type}]".comment
54
+ parts << "attr_#{self.read_only ? 'reader' : 'accessor'} :#{self.name.downcase}"
55
+ return parts.compact.join("\n").gsub(/\s+$/, "")
56
+ end
57
+
58
+ # Used for documenting attributes that are "declared dynamically via meta-programming".
59
+ # See the documentation on [YARD directives][1] for more info.
60
+ #
61
+ # [1]: https://www.rubydoc.info/gems/yard/file/docs/Tags.md#attribute
62
+ #
63
+ # @return [String]
64
+ def render_dynamic()
65
+ parts = []
66
+ parts << "@!attribute #{self.name.downcase} [#{self.read_only ? 'r' : 'rw'}]".comment
67
+ parts << (@description&.length&.positive? ? @description.indent(2).comment : nil)
68
+ parts << "@return [#{self.type}]".indent(2).comment
69
+ return parts.compact.join("\n").gsub(/\s+$/, "")
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,94 @@
1
+ require "dry/inflector"
2
+
3
+ module Ginny
4
+ # Used to generate a [class](https://ruby-doc.org/core-2.6.5/Class.html)
5
+ class Class
6
+
7
+ # Name of the class.
8
+ # @return [String]
9
+ attr_accessor :name
10
+ # Description of the class. [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) is supported.
11
+ # @return [String]
12
+ attr_accessor :description
13
+ # Name of a class to inherit from. (Ex: `YourNewClass < Parent`)
14
+ # @return [String]
15
+ attr_accessor :parent
16
+ # List of modules to declare the class inside.
17
+ # @return [String]
18
+ attr_accessor :modules
19
+ # An array of {Ginny::Attr}s.
20
+ # @return [Array<Ginny::Attr>]
21
+ attr_accessor :attrs
22
+ # String to write into the body of the class.
23
+ # @return [String]
24
+ attr_accessor :body
25
+ # String to prepend to the name of the generated file.
26
+ # @return [String]
27
+ attr_accessor :file_prefix
28
+
29
+ # @return [void]
30
+ def initialize()
31
+ self.attrs = []
32
+ self.modules = []
33
+ self.file_prefix = ""
34
+ end
35
+
36
+ # Constructor for a Class. Use `create`, not `new`.
37
+ #
38
+ # @param args [Hash<Symbol>]
39
+ # @return [Class]
40
+ def self.create(args = {})
41
+ c = Ginny::Class.new()
42
+ c.name = args[:name]
43
+ c.description = args[:description]
44
+ c.parent = args[:parent]
45
+ c.modules = args[:modules] unless args[:modules].nil?
46
+ c.attrs = Ginny::Attr.from_array(args[:attrs]) if args[:attrs]&.is_a?(Array)
47
+ c.body = args[:body] unless args[:body].nil?
48
+ c.file_prefix = args[:file_prefix] || ""
49
+ return c
50
+ end
51
+
52
+ # @param folder [String]
53
+ # @return [String]
54
+ def generate(folder = ".")
55
+ path = File.join(File.expand_path(folder), self.file_name())
56
+ File.open(path, "a") { |f| f.write(self.render() + "\n") }
57
+ return path
58
+ end
59
+
60
+ # @return [String]
61
+ def render()
62
+ parts = []
63
+ parts << (self.description&.length&.positive? ? self.description.comment.strip : nil)
64
+ parts << (self.parent.nil? ? "class #{self.class_name()}" : "class #{self.class_name()} < #{self.parent}")
65
+ parts << self.render_attributes()
66
+ parts << (self.body&.length&.positive? ? self.body.indent(2) : nil)
67
+ parts << "end"
68
+ if self.modules.length > 0
69
+ body = parts.compact.join("\n").gsub(/\s+$/, "")
70
+ return Ginny.mod(body, self.modules)
71
+ end
72
+ return parts.compact.join("\n").gsub(/\s+$/, "")
73
+ end
74
+
75
+ # @return [String]
76
+ def render_attributes()
77
+ return nil unless self.attrs.length > 0
78
+ return self.attrs.map(&:render).join("\n").indent(2)
79
+ end
80
+
81
+ # @return [String]
82
+ def class_name()
83
+ inflector = Dry::Inflector.new
84
+ return inflector.classify(inflector.underscore(self.name))
85
+ end
86
+
87
+ # @return [String]
88
+ def file_name()
89
+ inflector = Dry::Inflector.new
90
+ return self.file_prefix + inflector.underscore(self.name) + ".rb"
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,86 @@
1
+ module Ginny
2
+ # Used to generate a [method][2].
3
+ #
4
+ # [1]: https://ruby-doc.org/core-2.6.5/Method.html
5
+ # [2]: https://ruby-doc.org/core-2.6.5/doc/syntax/methods_rdoc.html
6
+ class Func
7
+
8
+ # Name of the function.
9
+ # @return [String]
10
+ attr_accessor :name
11
+ # Description of the function. [Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) is supported.
12
+ # @return [String]
13
+ attr_accessor :description
14
+ # Return [type](https://rubydoc.info/gems/yard/file/docs/GettingStarted.md#Declaring_Types) of the function.
15
+ # @return [String]
16
+ attr_accessor :return_type
17
+ # String to write into the body of the function.
18
+ # @return [String]
19
+ attr_accessor :body
20
+ # List of modules to declare the function inside of.
21
+ # @return [String]
22
+ attr_accessor :modules
23
+ # An array of {Ginny::Param}s.
24
+ # @return [Array<Param>]
25
+ attr_accessor :params
26
+
27
+ # @return [void]
28
+ def initialize()
29
+ self.params = []
30
+ self.modules = []
31
+ end
32
+
33
+ # Constructor for a Func. Use `create`, not `new`.
34
+ #
35
+ # @param args [Hash<Symbol>]
36
+ # @return [Ginny::Func]
37
+ def self.create(args = {})
38
+ f = Ginny::Func.new()
39
+ f.name = args[:name]
40
+ f.description = args[:description]
41
+ f.return_type = args[:return_type]
42
+ f.body = args[:body]
43
+ f.modules = args[:modules] unless args[:modules].nil?
44
+ f.params = Ginny::Param.from_array(args[:params]) if args[:params]&.is_a?(Array)
45
+ return f
46
+ end
47
+
48
+ # @return [String]
49
+ def render()
50
+ # return self.render_compact() if self.body.nil? && self.params.length == 0
51
+ parts = []
52
+ parts << self.render_description()
53
+ parts << self.params.map(&:render_doc).join("\n") unless self.params.length == 0
54
+ parts << self.render_return_type()
55
+ parts << "def " + self.name + self.render_params()
56
+ parts << self.body.indent(2) unless self.body.nil?
57
+ parts << "end"
58
+
59
+ body = parts.compact.join("\n").gsub(/\s+$/, "")
60
+
61
+ return Ginny.mod(body, self.modules) if self.modules.length > 0
62
+ return body
63
+ end
64
+
65
+ # @return [String]
66
+ def render_params()
67
+ return "()" if self.params.length == 0
68
+ # if self.params.length >= 5
69
+ # return "(\n" + self.params.map(&:render).join(",\n").indent(2) + "\n)"
70
+ # end
71
+ return "(" + self.params.map(&:render).join(", ") + ")"
72
+ end
73
+
74
+ # @return [String]
75
+ def render_return_type
76
+ type = self.return_type.nil? ? "void" : self.return_type
77
+ return "# @return [#{type}]"
78
+ end
79
+
80
+ # @return [String]
81
+ def render_description()
82
+ return (self.description&.length&.positive? ? self.description.comment.strip : nil)
83
+ end
84
+
85
+ end
86
+ end