ginny 0.5.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.
@@ -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