ahnnotate 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +55 -0
- data/LICENSE.txt +21 -0
- data/README.md +118 -0
- data/Rakefile +10 -0
- data/ahnnotate.gemspec +33 -0
- data/bin/console +14 -0
- data/bin/rake +29 -0
- data/bin/setup +8 -0
- data/exe/ahnnotate +14 -0
- data/lib/ahnnotate/cli.rb +116 -0
- data/lib/ahnnotate/column.rb +60 -0
- data/lib/ahnnotate/config.rb +67 -0
- data/lib/ahnnotate/error.rb +8 -0
- data/lib/ahnnotate/facet/models/main.rb +49 -0
- data/lib/ahnnotate/facet/models/module_node.rb +123 -0
- data/lib/ahnnotate/facet/models/processor.rb +91 -0
- data/lib/ahnnotate/facet/models/resolve_active_record_models.rb +42 -0
- data/lib/ahnnotate/facet/models/resolve_class_relationships.rb +67 -0
- data/lib/ahnnotate/facet/models/standin.rb +41 -0
- data/lib/ahnnotate/facet/models.rb +10 -0
- data/lib/ahnnotate/function/format.rb +36 -0
- data/lib/ahnnotate/function/main.rb +32 -0
- data/lib/ahnnotate/function/run.rb +24 -0
- data/lib/ahnnotate/function/tabularize.rb +53 -0
- data/lib/ahnnotate/index.rb +26 -0
- data/lib/ahnnotate/options.rb +48 -0
- data/lib/ahnnotate/rails.rake +44 -0
- data/lib/ahnnotate/railtie.rb +7 -0
- data/lib/ahnnotate/table.rb +44 -0
- data/lib/ahnnotate/tables.rb +55 -0
- data/lib/ahnnotate/version.rb +3 -0
- data/lib/ahnnotate/vfs.rb +69 -0
- data/lib/ahnnotate/vfs_driver/filesystem.rb +68 -0
- data/lib/ahnnotate/vfs_driver/hash.rb +59 -0
- data/lib/ahnnotate/vfs_driver/read_only_filesystem.rb +8 -0
- data/lib/ahnnotate.rb +32 -0
- metadata +202 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
module Ahnnotate
|
2
|
+
module Facet
|
3
|
+
module Models
|
4
|
+
# ModuleNode is named as such since `Class.is_a?(Module) == true`.
|
5
|
+
class ModuleNode
|
6
|
+
# By including ClassMethods this way, I'm including the methods as
|
7
|
+
# instance methods. I'm doing this so that I can compute the table name
|
8
|
+
# on per-class basis.
|
9
|
+
#
|
10
|
+
# It's a bit unfortunate that this class will be used to both (1) keep
|
11
|
+
# track of how classes/modules relate to each other and (2) compute the
|
12
|
+
# table name.
|
13
|
+
include ActiveRecord::ModelSchema::ClassMethods
|
14
|
+
|
15
|
+
# Named to fit the ModelSchema interface. This is basically `Class#name`
|
16
|
+
attr_accessor :name
|
17
|
+
# Named to fit the ModelSchema interface. This is the "outer class"
|
18
|
+
attr_accessor :parent
|
19
|
+
# Named to fit the ModelSchema interface. This is the class that the
|
20
|
+
# current class inherits from. This is computed, whereas
|
21
|
+
# `claimed_superclass` is what is parsed from the source
|
22
|
+
attr_accessor :superclass
|
23
|
+
# Named to fit the ModelSchema interface. This is currently unsupported
|
24
|
+
attr_accessor :table_name_prefix
|
25
|
+
|
26
|
+
attr_writer :claimed_superclass
|
27
|
+
attr_writer :abstract_class
|
28
|
+
attr_writer :is_a_kind_of_activerecord_base
|
29
|
+
attr_accessor :explicit_table_name
|
30
|
+
attr_accessor :is_active_record_base
|
31
|
+
attr_accessor :path
|
32
|
+
|
33
|
+
def initialize(name,
|
34
|
+
parent: nil,
|
35
|
+
is_a_kind_of_activerecord_base: false,
|
36
|
+
claimed_superclass: nil,
|
37
|
+
explicit_table_name: nil,
|
38
|
+
abstract_class: nil)
|
39
|
+
self.name = name
|
40
|
+
self.parent = parent
|
41
|
+
self.is_a_kind_of_activerecord_base = is_a_kind_of_activerecord_base
|
42
|
+
self.claimed_superclass = claimed_superclass
|
43
|
+
self.explicit_table_name = explicit_table_name
|
44
|
+
self.abstract_class = abstract_class
|
45
|
+
end
|
46
|
+
|
47
|
+
# Named to fit the ModelSchema interface
|
48
|
+
def pluralize_table_names
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
def table_name_suffix
|
53
|
+
end
|
54
|
+
|
55
|
+
def is_a_kind_of_activerecord_base?
|
56
|
+
!!@is_a_kind_of_activerecord_base
|
57
|
+
end
|
58
|
+
|
59
|
+
# Named to fit the ModelSchema interface
|
60
|
+
def abstract_class?
|
61
|
+
!!@abstract_class
|
62
|
+
end
|
63
|
+
|
64
|
+
def claimed_superclass
|
65
|
+
@claimed_superclass.to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
# Named to fit the ModelSchema interface. It was originally implemented
|
69
|
+
# in ActiveRecord::Inheritance. I've re-implemented it here via the
|
70
|
+
# documentation.
|
71
|
+
def base_class
|
72
|
+
if superclass.is_active_record_base
|
73
|
+
return self
|
74
|
+
end
|
75
|
+
|
76
|
+
if superclass.abstract_class?
|
77
|
+
return self
|
78
|
+
end
|
79
|
+
|
80
|
+
superclass.base_class
|
81
|
+
end
|
82
|
+
|
83
|
+
# Named to fit the ModelSchema interface. It was originally implemented
|
84
|
+
# in ActiveSupport::Introspection
|
85
|
+
def parents
|
86
|
+
if parent
|
87
|
+
[parent, *parent.parent]
|
88
|
+
else
|
89
|
+
[]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def class_name
|
94
|
+
if @name
|
95
|
+
"#{parent.class_name}::#{@name}"
|
96
|
+
else
|
97
|
+
""
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def table_name
|
102
|
+
if explicit_table_name
|
103
|
+
return @explicit_table_name
|
104
|
+
end
|
105
|
+
|
106
|
+
super
|
107
|
+
end
|
108
|
+
|
109
|
+
def <(other)
|
110
|
+
other == ActiveRecord::Base && is_a_kind_of_activerecord_base?
|
111
|
+
end
|
112
|
+
|
113
|
+
def ==(other)
|
114
|
+
if is_active_record_base && other == ActiveRecord::Base
|
115
|
+
return true
|
116
|
+
end
|
117
|
+
|
118
|
+
super
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Ahnnotate
|
2
|
+
module Facet
|
3
|
+
module Models
|
4
|
+
class Processor < Parser::AST::Processor
|
5
|
+
def call(content)
|
6
|
+
sexp = Parser::CurrentRuby.parse(content)
|
7
|
+
|
8
|
+
@current_class = ModuleNode.new(nil)
|
9
|
+
@classes = [@current_class]
|
10
|
+
|
11
|
+
process(sexp)
|
12
|
+
|
13
|
+
@classes.reject { |klass| klass.class_name == "" }
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_class(node)
|
17
|
+
@current_class = module_node_create(node, parent: @current_class)
|
18
|
+
@classes.push(@current_class)
|
19
|
+
|
20
|
+
super
|
21
|
+
|
22
|
+
@current_class = @current_class.parent
|
23
|
+
end
|
24
|
+
|
25
|
+
alias on_module on_class
|
26
|
+
|
27
|
+
def on_send(node)
|
28
|
+
receiver, method_name, assigned_node = *node
|
29
|
+
|
30
|
+
if receiver == s(:self) && method_name == :table_name=
|
31
|
+
table_name = assigned_node.children.last
|
32
|
+
@current_class.table_name = table_name
|
33
|
+
end
|
34
|
+
|
35
|
+
if receiver == s(:self) && method_name == :abstract_class=
|
36
|
+
abstract_class =
|
37
|
+
if assigned_node.type == :true
|
38
|
+
true
|
39
|
+
else
|
40
|
+
false
|
41
|
+
end
|
42
|
+
@current_class.abstract_class = abstract_class
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# ignore instance method definitions since method definition bodies
|
47
|
+
# can't contain class declarations
|
48
|
+
def on_def(_)
|
49
|
+
end
|
50
|
+
|
51
|
+
# ignore class method definitions since method definition bodies can't
|
52
|
+
# contain class declarations
|
53
|
+
def on_defs(_)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def module_node_create(node, parent:)
|
59
|
+
class_node, superclass_node, _body_node = *node
|
60
|
+
|
61
|
+
if node.type == :module
|
62
|
+
superclass_node = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
class_name = resolve_class_name(class_node)
|
66
|
+
superclass_name = resolve_class_name(superclass_node)
|
67
|
+
|
68
|
+
classlike = ModuleNode.new(class_name.to_s)
|
69
|
+
classlike.claimed_superclass = superclass_name.to_s
|
70
|
+
classlike.parent = parent
|
71
|
+
|
72
|
+
classlike
|
73
|
+
end
|
74
|
+
|
75
|
+
def resolve_class_name(node)
|
76
|
+
outer, name = *node
|
77
|
+
|
78
|
+
if outer.nil?
|
79
|
+
name
|
80
|
+
else
|
81
|
+
"#{resolve_class_name(outer)}::#{name}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def s(type, *children)
|
86
|
+
AST::Node.new(type, children)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Ahnnotate
|
2
|
+
module Facet
|
3
|
+
module Models
|
4
|
+
class ResolveActiveRecordModels
|
5
|
+
include ProcParty
|
6
|
+
|
7
|
+
def call(object_space)
|
8
|
+
tree = {}
|
9
|
+
object_space.each do |_class_name, extracted_class|
|
10
|
+
superclass = extracted_class.superclass
|
11
|
+
|
12
|
+
tree[superclass] ||= Set.new
|
13
|
+
tree[superclass].add(extracted_class)
|
14
|
+
end
|
15
|
+
|
16
|
+
activerecord_family =
|
17
|
+
gather_family(object_space["::ActiveRecord::Base"], tree)
|
18
|
+
|
19
|
+
activerecord_family.each do |individual|
|
20
|
+
individual.is_a_kind_of_activerecord_base = true
|
21
|
+
end
|
22
|
+
|
23
|
+
object_space.values - [object_space[""], object_space["::ActiveRecord::Base"]]
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def gather_family(node, tree)
|
29
|
+
gather_family_helper(node, tree).flatten
|
30
|
+
end
|
31
|
+
|
32
|
+
def gather_family_helper(node, tree)
|
33
|
+
if tree[node].nil?
|
34
|
+
return node
|
35
|
+
end
|
36
|
+
|
37
|
+
[node] + tree[node].map { |child| gather_family_helper(child, tree) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Ahnnotate
|
2
|
+
module Facet
|
3
|
+
module Models
|
4
|
+
class ResolveClassRelationships
|
5
|
+
include ProcParty
|
6
|
+
|
7
|
+
def call(extracted_classes)
|
8
|
+
object_space =
|
9
|
+
extracted_classes
|
10
|
+
.map(&method(:self_and_outer_class))
|
11
|
+
.flatten
|
12
|
+
.compact
|
13
|
+
.uniq
|
14
|
+
.map { |x| [x.class_name, x] }
|
15
|
+
.to_h
|
16
|
+
|
17
|
+
object_space[""] ||= ModuleNode.new(nil)
|
18
|
+
|
19
|
+
object_space["::ActiveRecord::Base"] =
|
20
|
+
ModuleNode.new(
|
21
|
+
"ActiveRecord::Base",
|
22
|
+
parent: object_space[""],
|
23
|
+
abstract_class: nil
|
24
|
+
)
|
25
|
+
object_space["::ActiveRecord::Base"].is_active_record_base = true
|
26
|
+
|
27
|
+
object_space.each do |class_name, extracted_class|
|
28
|
+
possible_namespace_levels = class_name.split("::")[1..-2] || []
|
29
|
+
|
30
|
+
if extracted_class.claimed_superclass == "" || extracted_class.claimed_superclass == nil
|
31
|
+
next
|
32
|
+
end
|
33
|
+
|
34
|
+
(possible_namespace_levels.size + 1).times do
|
35
|
+
class_uri_parts =
|
36
|
+
possible_namespace_levels + [extracted_class.claimed_superclass]
|
37
|
+
|
38
|
+
class_uri = "::#{class_uri_parts.join("::")}"
|
39
|
+
|
40
|
+
if object_space[class_uri]
|
41
|
+
extracted_class.superclass = object_space[class_uri]
|
42
|
+
break
|
43
|
+
end
|
44
|
+
|
45
|
+
possible_namespace_levels.pop
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
object_space
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def self_and_outer_class(extracted_class)
|
55
|
+
if extracted_class.nil?
|
56
|
+
return nil
|
57
|
+
end
|
58
|
+
|
59
|
+
[
|
60
|
+
extracted_class,
|
61
|
+
self_and_outer_class(extracted_class.parent)
|
62
|
+
]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Ahnnotate
|
2
|
+
module Facet
|
3
|
+
module Models
|
4
|
+
class Standin
|
5
|
+
# Lol. It's a little easier in this case to deal with instances here.
|
6
|
+
# I'll need to create many instances to recreate the class structure of
|
7
|
+
# the models in order to most correctly compute the table name from the
|
8
|
+
# model name 🥳
|
9
|
+
include ActiveRecord::ModelSchema::ClassMethods
|
10
|
+
|
11
|
+
# boolean, is this an abstract class
|
12
|
+
attr_writer :abstract_class
|
13
|
+
# the class that the current class inherits from
|
14
|
+
attr_accessor :superclass
|
15
|
+
attr_accessor :base_class
|
16
|
+
# the outer class
|
17
|
+
attr_accessor :parent
|
18
|
+
|
19
|
+
def initialize(abstract_class:, superclass:, base_class:, parent:, is_active_record_base: false)
|
20
|
+
@abstract_class = abstract_class
|
21
|
+
@superclass = superclass
|
22
|
+
@base_class = base_class
|
23
|
+
@parent = parent
|
24
|
+
@is_active_record_base = is_active_record_base
|
25
|
+
end
|
26
|
+
|
27
|
+
def abstract_class?
|
28
|
+
!!@abstract_class
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
if @is_active_record_base && other == ActiveRecord::Base
|
33
|
+
return true
|
34
|
+
end
|
35
|
+
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Ahnnotate
|
2
|
+
module Function
|
3
|
+
class Format
|
4
|
+
attr_reader :comment
|
5
|
+
|
6
|
+
def initialize(comment:)
|
7
|
+
@comment = comment
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(table, content)
|
11
|
+
table.string(comment: comment) + "\n" + strip_schema(content)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def strip_schema(content)
|
17
|
+
matches = pattern.match(content)
|
18
|
+
|
19
|
+
if matches
|
20
|
+
matches["post"]
|
21
|
+
else
|
22
|
+
content
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def pattern
|
27
|
+
@pattern ||=
|
28
|
+
begin
|
29
|
+
newline = /\r?\n\r?/
|
30
|
+
|
31
|
+
/\A#{comment}\s==\sSchema\sInfo#{newline}?(?:^#{comment}[^\n]*$#{newline})*#{newline}(?<post>.*)/m
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Ahnnotate
|
2
|
+
module Function
|
3
|
+
class Main
|
4
|
+
def initialize(root, options, config)
|
5
|
+
@root = root
|
6
|
+
@options = options
|
7
|
+
@config = config
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
if @config["boot"]
|
12
|
+
eval @config["boot"]
|
13
|
+
end
|
14
|
+
|
15
|
+
vfs = Vfs.new(vfs_driver)
|
16
|
+
|
17
|
+
runner = Run.new(@config, vfs)
|
18
|
+
runner.call
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def vfs_driver
|
24
|
+
if @options.fix?
|
25
|
+
VfsDriver::Filesystem.new(root: @root)
|
26
|
+
else
|
27
|
+
VfsDriver::ReadOnlyFilesystem.new(root: @root)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Ahnnotate
|
2
|
+
module Function
|
3
|
+
class Run
|
4
|
+
def initialize(config, vfs)
|
5
|
+
@config = config
|
6
|
+
@vfs = vfs
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
Facet::Models.add(@config, tables_hash, @vfs)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def tables_hash
|
16
|
+
@tables_hash = tables.to_h
|
17
|
+
end
|
18
|
+
|
19
|
+
def tables
|
20
|
+
@tables ||= Tables.new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Ahnnotate
|
2
|
+
module Function
|
3
|
+
class Tabularize
|
4
|
+
def initialize(prefix:, cell_divider:)
|
5
|
+
@prefix = prefix
|
6
|
+
@cell_divider = cell_divider
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(data, column_names)
|
10
|
+
output = StringIO.new
|
11
|
+
minimum_column_lengths = Hash.new { 0 }
|
12
|
+
|
13
|
+
rows = data.map do |row|
|
14
|
+
row_hash = {}
|
15
|
+
|
16
|
+
column_names.each do |c|
|
17
|
+
value = row.public_send(c).to_s
|
18
|
+
|
19
|
+
row_hash[c] = value
|
20
|
+
|
21
|
+
if value.size > minimum_column_lengths[c]
|
22
|
+
minimum_column_lengths[c] = value.size
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
row_hash
|
27
|
+
end
|
28
|
+
|
29
|
+
rows.each do |row|
|
30
|
+
# Note: minimum_column_lengths shouldn't include any of the columns
|
31
|
+
# with a length of zero since they were never explicitly set (to 0)
|
32
|
+
minimum_column_lengths.each.with_index do |(column_name, column_max_length), index|
|
33
|
+
if index == 0
|
34
|
+
output.print(@prefix)
|
35
|
+
end
|
36
|
+
|
37
|
+
if_rightmost_column = index + 1 == minimum_column_lengths.size
|
38
|
+
|
39
|
+
if if_rightmost_column
|
40
|
+
output.puts "#{row[column_name]}"
|
41
|
+
else
|
42
|
+
column_length = row[column_name].size
|
43
|
+
spaces_length = column_max_length - column_length
|
44
|
+
output.print "#{row[column_name]}#{" " * spaces_length}#{@cell_divider}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
output.string.gsub(/ +$/, "")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Ahnnotate
|
2
|
+
class Index
|
3
|
+
attr_accessor :name
|
4
|
+
attr_accessor :columns
|
5
|
+
attr_accessor :comment
|
6
|
+
attr_accessor :unique
|
7
|
+
|
8
|
+
def initialize(**args)
|
9
|
+
args.each do |key, value|
|
10
|
+
public_send("#{key}=", value)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def presentable_columns
|
15
|
+
"(#{columns.join(", ")})"
|
16
|
+
end
|
17
|
+
|
18
|
+
def presentable_unique
|
19
|
+
if unique
|
20
|
+
"UNIQUE"
|
21
|
+
else
|
22
|
+
""
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Ahnnotate
|
2
|
+
class Options
|
3
|
+
def self.attribute_names
|
4
|
+
@attribute_names ||= []
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.attr_writer(*names)
|
8
|
+
attribute_names.push(*names)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.attr_question(*names)
|
13
|
+
names.each do |name|
|
14
|
+
attr_writer(name)
|
15
|
+
|
16
|
+
define_method("#{name}?") do
|
17
|
+
!!instance_variable_get("@#{name}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_question :exit
|
23
|
+
attr_question :fix
|
24
|
+
|
25
|
+
def initialize(**args)
|
26
|
+
args.each do |key, value|
|
27
|
+
public_send("#{key}=", value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
output = StringIO.new
|
33
|
+
|
34
|
+
output.puts "🧐 options:"
|
35
|
+
self.class.attribute_names.each do |attribute_name|
|
36
|
+
output.print "🧐 #{attribute_name}: "
|
37
|
+
|
38
|
+
if instance_variable_defined?("@#{attribute_name}")
|
39
|
+
output.puts "undefined"
|
40
|
+
else
|
41
|
+
output.puts instance_variable_get("@#{attribute_name}").inspect
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
output.string
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
namespace :db do
|
2
|
+
task :migrate do
|
3
|
+
$rake_ahnnotate_config ||= Ahnnotate::Config.load(root: Rails.root)
|
4
|
+
|
5
|
+
if $rake_ahnnotate_config["rake_db_autorun"]
|
6
|
+
Rake::Task["ahnnotate:all"].reenable
|
7
|
+
Rake::Task["ahnnotate:all"].invoke
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
task :rollback do
|
12
|
+
$rake_ahnnotate_config ||= Ahnnotate::Config.load(root: Rails.root)
|
13
|
+
|
14
|
+
if $rake_ahnnotate_config["rake_db_autorun"]
|
15
|
+
Rake::Task["ahnnotate:all"].reenable
|
16
|
+
Rake::Task["ahnnotate:all"].invoke
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
namespace :ahnnotate do
|
22
|
+
desc "Run ahnnotate"
|
23
|
+
task :all do
|
24
|
+
require "ahnnotate/cli"
|
25
|
+
require "shellwords"
|
26
|
+
|
27
|
+
# This should either be `rails` or `rake` (since newer versions of Rails
|
28
|
+
# can call rake tasks with either executable)
|
29
|
+
exe_name = File.basename($0)
|
30
|
+
|
31
|
+
argv = ENV.fetch("COMMENTATE", "--fix")
|
32
|
+
argv = Shellwords.split(argv)
|
33
|
+
|
34
|
+
puts "Commentating models..."
|
35
|
+
|
36
|
+
cli = Ahnnotate::Cli.new(name: "#{exe_name} ahnnotate")
|
37
|
+
cli.run(argv, $rake_ahnnotate_config)
|
38
|
+
|
39
|
+
puts "Done!"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Run rake task `ahnnotate:all`"
|
44
|
+
task ahnnotate: "ahnnotate:all"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Ahnnotate
|
2
|
+
class Table
|
3
|
+
attr_accessor :name
|
4
|
+
attr_accessor :columns
|
5
|
+
attr_accessor :indexes
|
6
|
+
|
7
|
+
def initialize(**args)
|
8
|
+
args.each do |key, value|
|
9
|
+
public_send("#{key}=", value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def string(comment:)
|
14
|
+
tabularizer =
|
15
|
+
Function::Tabularize.new(
|
16
|
+
prefix: "#{comment} ",
|
17
|
+
cell_divider: " "
|
18
|
+
)
|
19
|
+
|
20
|
+
output = StringIO.new
|
21
|
+
output.puts "#{comment} == Schema Info"
|
22
|
+
output.puts comment
|
23
|
+
output.puts "#{comment} Table name: #{@name}"
|
24
|
+
output.puts comment
|
25
|
+
output.print tabularizer.call(columns, [:name, :type, :details])
|
26
|
+
output.puts comment
|
27
|
+
|
28
|
+
if indexes.any?
|
29
|
+
output.puts "#{comment} Indexes:"
|
30
|
+
output.puts comment
|
31
|
+
output.print tabularizer.call(indexes, [:name, :presentable_columns, :presentable_unique, :comment])
|
32
|
+
output.puts comment
|
33
|
+
end
|
34
|
+
|
35
|
+
output.string
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def longest_column_name_length
|
41
|
+
@longest_column_name_length ||= @columns.map(&:name).map(&:size).max
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|