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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +138 -0
- data/.solargraph.yml +14 -0
- data/.travis.yml +7 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +68 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +61 -0
- data/LICENSE.txt +21 -0
- data/README.md +147 -0
- data/Rakefile +10 -0
- data/bin/console +9 -0
- data/bin/docs +2 -0
- data/bin/setup +8 -0
- data/exe/ginny +23 -0
- data/ginny.gemspec +43 -0
- data/lib/ginny.rb +6 -0
- data/lib/ginny/error.rb +5 -0
- data/lib/ginny/load.rb +45 -0
- data/lib/ginny/mod.rb +32 -0
- data/lib/ginny/models/attr.rb +73 -0
- data/lib/ginny/models/class.rb +94 -0
- data/lib/ginny/models/func.rb +86 -0
- data/lib/ginny/models/param.rb +90 -0
- data/lib/ginny/string/comment.rb +19 -0
- data/lib/ginny/string/indent.rb +52 -0
- data/lib/ginny/symbolize.rb +31 -0
- data/lib/ginny/version.rb +3 -0
- metadata +203 -0
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/docs
ADDED
data/bin/setup
ADDED
data/exe/ginny
ADDED
@@ -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
|
data/ginny.gemspec
ADDED
@@ -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
|
data/lib/ginny.rb
ADDED
data/lib/ginny/error.rb
ADDED
data/lib/ginny/load.rb
ADDED
@@ -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
|
data/lib/ginny/mod.rb
ADDED
@@ -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
|