dbtodoc 0.1.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/.DS_Store +0 -0
- data/.rspec +3 -0
- data/README.md +31 -0
- data/Rakefile +8 -0
- data/bin/.DS_Store +0 -0
- data/bin/dbtodoc +42 -0
- data/dbtodoc.gemspec +38 -0
- data/lib/dbtodoc/definition.rb +116 -0
- data/lib/dbtodoc/doc/csv.rb +29 -0
- data/lib/dbtodoc/doc/excel.rb +156 -0
- data/lib/dbtodoc/i18n.rb +38 -0
- data/lib/dbtodoc/schema.rb +59 -0
- data/lib/dbtodoc/version.rb +5 -0
- data/lib/dbtodoc.rb +97 -0
- data/sig/dbtodoc.rbs +4 -0
- metadata +62 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6f435503e31f05aaa1598c8d7fee82519cc38ecc36b5ce0e4d3f783948579543
|
|
4
|
+
data.tar.gz: 10177a6e91718a7c8e4da15bc5db59e0351f6e6ba35719e272e431a0e8e119fd
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c7408cbe931b98cc51d0850fce98a8150094f141a04838afb5f2f877c59e033d11ad2a39cc65d8c61f157bd38a8837b06ae4e976d71acedd47b99db9c749eea1
|
|
7
|
+
data.tar.gz: 804cf971c9051fcdb8602f87001140659ea04524af9aea2e039315cc8cc48a1ebdd92f3fa5ea444b07543b025c42fe60d13d215bf3b1ed53098791a2f610bced
|
data/.DS_Store
ADDED
|
Binary file
|
data/.rspec
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Dbtodoc
|
|
2
|
+
|
|
3
|
+
TODO: Delete this and the text below, and describe your gem
|
|
4
|
+
|
|
5
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/dbtodoc`. To experiment with that code, run `bin/console` for an interactive prompt.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
|
10
|
+
|
|
11
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
12
|
+
|
|
13
|
+
$ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
|
14
|
+
|
|
15
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
16
|
+
|
|
17
|
+
$ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
TODO: Write usage instructions here
|
|
22
|
+
|
|
23
|
+
## Development
|
|
24
|
+
|
|
25
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
26
|
+
|
|
27
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
28
|
+
|
|
29
|
+
## Contributing
|
|
30
|
+
|
|
31
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/dbtodoc.
|
data/Rakefile
ADDED
data/bin/.DS_Store
ADDED
|
Binary file
|
data/bin/dbtodoc
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
|
|
5
|
+
require 'optparse'
|
|
6
|
+
class ArgsParser
|
|
7
|
+
def self.parse!(args)
|
|
8
|
+
options = {
|
|
9
|
+
type: 'excel'
|
|
10
|
+
}
|
|
11
|
+
opt_parser = OptionParser.new do |opts|
|
|
12
|
+
opts.banner = "Usage: dbtodoc [options]"
|
|
13
|
+
|
|
14
|
+
opts.on('-t', '--type TYPE', "指定输出类型,默认值:excel,可选值:schema, csv, excel") do |o|
|
|
15
|
+
options[:type] = o
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
opts.on('-h', '--help', "显示帮助\n例:dbtodoc -t csv /rails/root/path") do
|
|
19
|
+
puts opts
|
|
20
|
+
exit
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
# 解析命令行参数
|
|
24
|
+
opt_parser.parse!(args)
|
|
25
|
+
options
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# 解析命令行参数
|
|
30
|
+
options = ArgsParser.parse!(ARGV)
|
|
31
|
+
# 获取目标目录
|
|
32
|
+
path = ARGV.first || '.'
|
|
33
|
+
# 确保目录存在
|
|
34
|
+
unless File.exist?(path)
|
|
35
|
+
puts "#{path}: 文件或目录不存在"
|
|
36
|
+
exit 1
|
|
37
|
+
end
|
|
38
|
+
options[:path] = path
|
|
39
|
+
|
|
40
|
+
require 'dbtodoc'
|
|
41
|
+
|
|
42
|
+
Dbtodoc.start(options)
|
data/dbtodoc.gemspec
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/dbtodoc/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "dbtodoc"
|
|
7
|
+
spec.version = Dbtodoc::VERSION
|
|
8
|
+
spec.authors = ["shuhjx"]
|
|
9
|
+
spec.email = ["shuh_jx@163.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "将 Rails 项目的数据库 schema 导出为文档"
|
|
12
|
+
spec.description = "将 Rails 项目的数据库 schema 导出为文档,支持 schema, csv, excel 格式"
|
|
13
|
+
spec.homepage = "https://github.com/shuhjx/dbtodoc"
|
|
14
|
+
spec.required_ruby_version = ">= 2.6.0"
|
|
15
|
+
|
|
16
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
17
|
+
|
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
20
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
21
|
+
|
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
24
|
+
spec.files = Dir.chdir(__dir__) do
|
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
26
|
+
(File.expand_path(f) == __FILE__) ||
|
|
27
|
+
f.start_with?(*%w[test/ spec/ features/ .git .github appveyor Gemfile])
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
spec.executable = 'dbtodoc'
|
|
31
|
+
spec.require_paths = ["lib"]
|
|
32
|
+
|
|
33
|
+
# Uncomment to register a new dependency of your gem
|
|
34
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
|
35
|
+
|
|
36
|
+
# For more information and examples about making a new gem, check out our
|
|
37
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
38
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
require 'active_record'
|
|
2
|
+
require_relative './i18n.rb'
|
|
3
|
+
|
|
4
|
+
module Dbtodoc
|
|
5
|
+
module Definition
|
|
6
|
+
def self.included(klass)
|
|
7
|
+
klass.extend(ClassMethods)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
def define(info = {}, &block)
|
|
12
|
+
new.define(info, &block)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def define(info, &block)
|
|
17
|
+
before_define()
|
|
18
|
+
instance_eval(&block)
|
|
19
|
+
ensure
|
|
20
|
+
after_define()
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def index(column_names, **options)
|
|
24
|
+
@table_columns.each do |row|
|
|
25
|
+
next if !column_names.include?(row[:column_name])
|
|
26
|
+
row[:index_name] = options[:name]
|
|
27
|
+
break if column_names.size == 1
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def i18n
|
|
32
|
+
@_i18n ||= Dbtodoc::I18n.new(File.join(Dir.pwd, 'config/locales/*.yml'))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def database
|
|
36
|
+
@_database ||= ActiveRecord::Base.connection.current_database
|
|
37
|
+
end
|
|
38
|
+
private
|
|
39
|
+
# 写入文档
|
|
40
|
+
def write_to_doc(table_name, table_columns)
|
|
41
|
+
raise 'Please override write_to_doc method!'
|
|
42
|
+
end
|
|
43
|
+
def before_define
|
|
44
|
+
#TODO 执行scheam.rb前
|
|
45
|
+
end
|
|
46
|
+
def after_define
|
|
47
|
+
#TODO 执行scheam.rb后
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def add_row(column_name, type, **options)
|
|
51
|
+
# puts "table: #{@table_name}, column: #{column_name}, sql_type: #{type}"
|
|
52
|
+
default = options[:default]
|
|
53
|
+
default = default.call if default.respond_to?(:call)
|
|
54
|
+
comment = options[:comment] || i18n.column_name(@table_name, column_name)
|
|
55
|
+
@table_columns << {
|
|
56
|
+
column_name: column_name,
|
|
57
|
+
comment: comment,
|
|
58
|
+
type: type,
|
|
59
|
+
null: options[:null],
|
|
60
|
+
primary_key: options[:primary_key],
|
|
61
|
+
default: default,
|
|
62
|
+
description: nil,
|
|
63
|
+
sample_data: nil,
|
|
64
|
+
index_name: nil
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def add_primary_key_row(options)
|
|
69
|
+
id_options = options[:id]
|
|
70
|
+
return if id_options == false
|
|
71
|
+
id_options ||= {type: :bigint}
|
|
72
|
+
primary_key = options[:primary_key] || :id
|
|
73
|
+
id_options = {type: id_options} unless id_options.is_a?(Hash)
|
|
74
|
+
type = id_options[:type] || :bigint
|
|
75
|
+
id_options[:limit] = type == :bigint ? 8 : nil # 默认bigint
|
|
76
|
+
id_options[:null] = false
|
|
77
|
+
id_options[:comment] ||= 'ID'
|
|
78
|
+
id_options[:primary_key] = true
|
|
79
|
+
send(type, primary_key, **id_options)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def create_table(table_name, **options, &block)
|
|
83
|
+
@table_name = table_name
|
|
84
|
+
@table_columns = [] #||= Hash.new { |hash, key| hash[key] = [] }
|
|
85
|
+
add_primary_key_row(options)
|
|
86
|
+
block.call(self)
|
|
87
|
+
# 写入文档
|
|
88
|
+
write_to_doc(table_name, @table_columns)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def add_foreign_key(from_table, to_table, **options)
|
|
92
|
+
#TODO 外键
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def method_missing(name, *args, &block)
|
|
96
|
+
type = case name
|
|
97
|
+
when :bigint
|
|
98
|
+
ActiveRecord::Type.registry.lookup(:big_integer)
|
|
99
|
+
else
|
|
100
|
+
ActiveRecord::Type.registry.lookup(name.to_sym) rescue nil
|
|
101
|
+
end
|
|
102
|
+
if type
|
|
103
|
+
column = args[0]
|
|
104
|
+
options = args[1] || {}
|
|
105
|
+
# 调用type_to_sql方法
|
|
106
|
+
sql_type = ActiveRecord::Base.connection.schema_creation.send(:type_to_sql, type.type, **options)
|
|
107
|
+
add_row(column, sql_type, **options)
|
|
108
|
+
else
|
|
109
|
+
puts '------------'
|
|
110
|
+
puts "name: #{name}, args: #{args}"
|
|
111
|
+
puts '------------'
|
|
112
|
+
super
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'csv'
|
|
2
|
+
module Dbtodoc
|
|
3
|
+
module Doc
|
|
4
|
+
module Csv
|
|
5
|
+
DOC_HEADER = %w(表名 字段名 类型 NULL許可 默认値 日本語名 説明 样本数据 索引名)
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
def before_define
|
|
9
|
+
file_path = File.join(Dir.pwd, "tmp/#{database}.csv")
|
|
10
|
+
@csv = File.open(file_path, 'w:utf-8') # 以写入模式打开文件,覆盖原有内容
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def after_define
|
|
14
|
+
@csv.close if @csv # 确保文件关闭
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def write_to_doc(table_name, table_columns)
|
|
18
|
+
#以追加模式打开文件
|
|
19
|
+
row = CSV.generate_line(DOC_HEADER)
|
|
20
|
+
@csv.write(row)
|
|
21
|
+
table_columns.each do |rows|
|
|
22
|
+
row = CSV.generate_line([table_name, rows[:column_name], rows[:type], rows[:null], rows[:default], rows[:comment], rows[:description], rows[:sample_data], rows[:index_name]])
|
|
23
|
+
@csv.write(row)
|
|
24
|
+
end
|
|
25
|
+
@csv.write("\n")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
require 'rubyXL'
|
|
2
|
+
require 'rubyXL/convenience_methods'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
module Dbtodoc
|
|
5
|
+
module Doc
|
|
6
|
+
module Excel
|
|
7
|
+
DOC_HEADER = %w(フィールド名 型 NULL許可 デフォルト値 日本語名 説明 サンプルデータ 索引名)
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
def before_define
|
|
11
|
+
@file_path = File.join(Dir.pwd, "tmp/#{database}.xlsx")
|
|
12
|
+
File.delete(@file_path) if File.exist?(@file_path) # 如果文件存在,则删除
|
|
13
|
+
@workbook = RubyXL::Workbook.new # 创建新的 Excel 工作簿
|
|
14
|
+
@worksheet = @workbook.worksheets.first # 获取第一个工作表
|
|
15
|
+
@max_row = 0 # 最大行索引
|
|
16
|
+
@col_max_widths = Hash.new { |h, k| h[k] = 0 } # 列索引 => 最大宽度
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def after_define
|
|
20
|
+
# 设置列宽(Excel 列宽单位 ≈ 字符数 + 一些边距)
|
|
21
|
+
@col_max_widths[0] = 1 # 第一列(索引列)宽度设为 1 * 1.5 + 2 = 3.5
|
|
22
|
+
@col_max_widths.each do |col_index, width|
|
|
23
|
+
# 限制最大宽度(避免过宽),例如不超过 50
|
|
24
|
+
adjusted_width = [width * 1.5 + 2, 50].min # +2 为边距
|
|
25
|
+
@worksheet.change_column_width(col_index, adjusted_width)
|
|
26
|
+
end
|
|
27
|
+
# 保存Excel文件
|
|
28
|
+
@workbook.write @file_path
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# 实现将数据库 schema 导出到 Excel 文件的方法
|
|
32
|
+
def write_to_doc(table_name, table_columns)
|
|
33
|
+
# 写入2行空行
|
|
34
|
+
add_blank_row(2)
|
|
35
|
+
# 写入表名
|
|
36
|
+
add_table_name_row(table_name)
|
|
37
|
+
# 写入表头
|
|
38
|
+
add_header_row
|
|
39
|
+
# 写入数据
|
|
40
|
+
table_columns.each_with_index do |rows, index|
|
|
41
|
+
a_row = [rows[:column_name].to_s, rows[:type], rows[:null], rows[:default], rows[:comment], rows[:description], rows[:sample_data], rows[:index_name]]
|
|
42
|
+
add_column_row(a_row, rows[:primary_key] ? :id_column : :column)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def add_table_name_row(table_name)
|
|
47
|
+
[table_name, i18n.table_name(table_name), *Array.new(DOC_HEADER.size - 2, nil)].each_with_index do |value, col|
|
|
48
|
+
@col_max_widths[col+1] = [@col_max_widths[col+1], value.to_s.length].max if value
|
|
49
|
+
cell = @worksheet.add_cell(@max_row, col + 1, value)
|
|
50
|
+
# 设置背景颜色(蓝色)
|
|
51
|
+
set_cell_style(cell, :table_name)
|
|
52
|
+
end
|
|
53
|
+
@max_row += 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def add_blank_row(count = 1)
|
|
57
|
+
count.times do
|
|
58
|
+
@worksheet.add_cell(@max_row, 0, nil)
|
|
59
|
+
@max_row += 1
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def add_header_row
|
|
64
|
+
DOC_HEADER.each_with_index do |value, col|
|
|
65
|
+
@col_max_widths[col+1] = [@col_max_widths[col+1], value.to_s.length].max if value
|
|
66
|
+
cell = @worksheet.add_cell(@max_row, col + 1, value)
|
|
67
|
+
# 设置背景颜色(浅绿色)
|
|
68
|
+
set_cell_style(cell, :header)
|
|
69
|
+
end
|
|
70
|
+
@max_row += 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def add_column_row(cols, cell_type = :column)
|
|
74
|
+
cols.each_with_index do |value, col|
|
|
75
|
+
@col_max_widths[col+1] = [@col_max_widths[col+1], value.to_s.length].max if value
|
|
76
|
+
cell = @worksheet.add_cell(@max_row, col + 1, value)
|
|
77
|
+
# 设置背景颜色(白色)
|
|
78
|
+
set_cell_style(cell, cell_type)
|
|
79
|
+
end
|
|
80
|
+
@max_row += 1
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# 辅助方法:设置单元格背景颜色
|
|
84
|
+
def set_cell_style(cell, cell_type)
|
|
85
|
+
@h_style_index ||= Hash.new { |h, k| h[k] = create_style_index(k) }
|
|
86
|
+
style_index = @h_style_index[cell_type]
|
|
87
|
+
|
|
88
|
+
# 应用样式到单元格
|
|
89
|
+
cell.style_index = style_index
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def create_style_index(cell_type)
|
|
93
|
+
rgb_color = case cell_type
|
|
94
|
+
when :header then '4EE257'
|
|
95
|
+
when :table_name then '000090'
|
|
96
|
+
when :column then 'FFFFFF'
|
|
97
|
+
when :id_column then 'CCFFCC'
|
|
98
|
+
else raise "Unknown cell type: #{cell_type}"
|
|
99
|
+
end
|
|
100
|
+
fill = RubyXL::Fill.new
|
|
101
|
+
pattern_fill = RubyXL::PatternFill.new
|
|
102
|
+
pattern_fill.pattern_type = 'solid'
|
|
103
|
+
pattern_fill.fg_color = RubyXL::Color.new(rgb: rgb_color)
|
|
104
|
+
pattern_fill.bg_color = RubyXL::Color.new(rgb: rgb_color)
|
|
105
|
+
fill.pattern_fill = pattern_fill
|
|
106
|
+
# 添加到工作簿的样式表
|
|
107
|
+
@workbook.stylesheet.fills << fill
|
|
108
|
+
# 获取新的 fill_id
|
|
109
|
+
fill_id = @workbook.stylesheet.fills.size - 1
|
|
110
|
+
|
|
111
|
+
# 创建新的 XF 样式
|
|
112
|
+
xf = RubyXL::XF.new
|
|
113
|
+
xf.fill_id = fill_id
|
|
114
|
+
xf.apply_fill = 1 # 使用1而不是true
|
|
115
|
+
# 添加到工作簿的单元格样式
|
|
116
|
+
@workbook.stylesheet.cell_xfs << xf
|
|
117
|
+
|
|
118
|
+
# 设置字体「MS Pゴシック」
|
|
119
|
+
font = RubyXL::Font.new
|
|
120
|
+
font.name = RubyXL::StringValue.new(val: 'MS Pゴシック')
|
|
121
|
+
font.sz = RubyXL::FloatValue.new(val: 12)
|
|
122
|
+
if cell_type == :table_name
|
|
123
|
+
font.color = RubyXL::Color.new(rgb: 'FFFFFF')
|
|
124
|
+
font.b = RubyXL::BooleanValue.new(val: true)
|
|
125
|
+
else
|
|
126
|
+
font.color = RubyXL::Color.new(rgb: '000000')
|
|
127
|
+
font.b = RubyXL::BooleanValue.new(val: false)
|
|
128
|
+
end
|
|
129
|
+
@workbook.stylesheet.fonts << font
|
|
130
|
+
font_id = @workbook.stylesheet.fonts.size - 1
|
|
131
|
+
xf.font_id = font_id
|
|
132
|
+
|
|
133
|
+
if @cell_border_index.nil?
|
|
134
|
+
border = RubyXL::Border.new
|
|
135
|
+
# 边框样式,可选值:hairline, thin, medium, thick
|
|
136
|
+
border.left = RubyXL::BorderEdge.new(style: 'medium')
|
|
137
|
+
border.right = RubyXL::BorderEdge.new(style: 'medium')
|
|
138
|
+
border.top = RubyXL::BorderEdge.new(style: 'medium')
|
|
139
|
+
border.bottom = RubyXL::BorderEdge.new(style: 'medium')
|
|
140
|
+
edge_color = '000000' # 黑色边框
|
|
141
|
+
border.set_edge_color(:left, edge_color)
|
|
142
|
+
border.set_edge_color(:right, edge_color)
|
|
143
|
+
border.set_edge_color(:top, edge_color)
|
|
144
|
+
border.set_edge_color(:bottom, edge_color)
|
|
145
|
+
@workbook.stylesheet.borders << border
|
|
146
|
+
@cell_border_index = @workbook.stylesheet.borders.size - 1
|
|
147
|
+
end
|
|
148
|
+
xf.border_id = @cell_border_index
|
|
149
|
+
|
|
150
|
+
# 获取新的 style_index
|
|
151
|
+
style_index = @workbook.stylesheet.cell_xfs.size - 1
|
|
152
|
+
return style_index
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
data/lib/dbtodoc/i18n.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'active_support/core_ext/string/inflections'
|
|
3
|
+
module Dbtodoc
|
|
4
|
+
class I18n
|
|
5
|
+
def initialize(path)
|
|
6
|
+
@models = Hash.new { |h, k| h[k] = {} }
|
|
7
|
+
@attributes = Hash.new { |h, k| h[k] = {} }
|
|
8
|
+
Dir.glob(path).each do |f|
|
|
9
|
+
yaml = YAML.load_file(f)
|
|
10
|
+
lang = yaml.keys[0]
|
|
11
|
+
next if yaml[lang]['activerecord'].blank?
|
|
12
|
+
if data = yaml[lang]['activerecord']['models']
|
|
13
|
+
@models[lang].merge!(data)
|
|
14
|
+
end
|
|
15
|
+
if data = yaml[lang]['activerecord']['attributes']
|
|
16
|
+
@attributes[lang].merge!(data)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def table_name(name)
|
|
22
|
+
# 单词复数转单数
|
|
23
|
+
name = name.singularize if name.end_with?('s')
|
|
24
|
+
# 尝试从i18n配置中获取表名
|
|
25
|
+
@models.map do |lang, models|
|
|
26
|
+
models.key?(name.downcase) ? models[name.downcase] : nil
|
|
27
|
+
end.compact.join("\n").presence || name.titleize
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def column_name(tablename, name)
|
|
31
|
+
tablename = tablename.singularize if tablename.end_with?('s')
|
|
32
|
+
# 尝试从i18n配置中获取列名
|
|
33
|
+
@attributes.map do |lang, attrs|
|
|
34
|
+
attrs.key?(tablename.downcase) && attrs[tablename.downcase].key?(name.downcase) ? attrs[tablename.downcase][name.downcase] : nil
|
|
35
|
+
end.compact.join("\n").presence || name.titleize
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
require 'active_record'
|
|
3
|
+
# 方法重写
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
class Schema
|
|
6
|
+
class << self
|
|
7
|
+
alias_method :original_get, :'[]'
|
|
8
|
+
def [](version)
|
|
9
|
+
@class_for_version ||= {}
|
|
10
|
+
@class_for_version[version] ||= Class.new do
|
|
11
|
+
# include Definition
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
#TODO
|
|
18
|
+
# 方法恢复
|
|
19
|
+
module ActiveRecord
|
|
20
|
+
class Schema
|
|
21
|
+
class << self
|
|
22
|
+
::ActiveRecord::Schema.instance_variable_get('@class_for_version').clear rescue nil
|
|
23
|
+
alias_method :'[]', :original_get
|
|
24
|
+
undef_method :original_get
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
=end
|
|
29
|
+
require 'active_record'
|
|
30
|
+
require_relative './definition.rb'
|
|
31
|
+
module ActiveRecord
|
|
32
|
+
class Schema
|
|
33
|
+
class << self
|
|
34
|
+
alias_method :original_get, :'[]'
|
|
35
|
+
def [](version)
|
|
36
|
+
@class_for_version ||= {}
|
|
37
|
+
@class_for_version[version] ||= Class.new do
|
|
38
|
+
include ::Dbtodoc::Definition
|
|
39
|
+
if @@doc_type_module
|
|
40
|
+
include @@doc_type_module
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def set_doc_type(type)
|
|
46
|
+
@@doc_type_module = case type
|
|
47
|
+
when 'csv'
|
|
48
|
+
require_relative('./doc/csv.rb')
|
|
49
|
+
::Dbtodoc::Doc::Csv
|
|
50
|
+
when 'excel'
|
|
51
|
+
require_relative './doc/excel.rb'
|
|
52
|
+
::Dbtodoc::Doc::Excel
|
|
53
|
+
else
|
|
54
|
+
raise ArgumentError, "Invalid doc type: #{type}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/dbtodoc.rb
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "dbtodoc/version"
|
|
4
|
+
require 'active_record'
|
|
5
|
+
require 'yaml'
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
require 'erb'
|
|
8
|
+
|
|
9
|
+
if Psych::VERSION >= '4.0'
|
|
10
|
+
module Psych
|
|
11
|
+
class << self
|
|
12
|
+
alias_method :original_safe_load, :safe_load
|
|
13
|
+
|
|
14
|
+
def safe_load(*args, **kwargs)
|
|
15
|
+
kwargs[:aliases] = true # 允许使用别名
|
|
16
|
+
original_safe_load(*args, **kwargs)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module Dbtodoc
|
|
23
|
+
class Error < StandardError; end
|
|
24
|
+
|
|
25
|
+
def self.start(options)
|
|
26
|
+
path = options[:path] || '.'
|
|
27
|
+
Dir.chdir(path) if path != '.' # 切换到目标目录
|
|
28
|
+
|
|
29
|
+
db_config_file = File.join(path, 'config/database.yml')
|
|
30
|
+
unless File.exist?(db_config_file)
|
|
31
|
+
puts "#{db_config_file}: 文件或目录不存在"
|
|
32
|
+
exit 1
|
|
33
|
+
end
|
|
34
|
+
# 读取数据库配置文件
|
|
35
|
+
all_db_configs = YAML.load_file(db_config_file).each_with_object({}) do |(_, config), h|
|
|
36
|
+
next if config['database'].blank?
|
|
37
|
+
config.each do |k, v|
|
|
38
|
+
config[k] = ERB.new(v).result if v.is_a?(String)
|
|
39
|
+
end
|
|
40
|
+
h[config['database']] = config
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
db_name = if all_db_configs.keys.size == 1
|
|
44
|
+
# 如果只有一个数据库,直接使用它
|
|
45
|
+
all_db_configs.keys.first
|
|
46
|
+
else
|
|
47
|
+
require 'cli/ui' #https://github.com/shopify/cli-ui
|
|
48
|
+
# 如果有多个数据库,选择一个
|
|
49
|
+
CLI::UI.ask('Select database:', options: all_db_configs.keys)
|
|
50
|
+
end
|
|
51
|
+
exit if db_name.blank?
|
|
52
|
+
|
|
53
|
+
# 读取数据库配置的adapter动态加载对应库
|
|
54
|
+
adapter = all_db_configs[db_name]['adapter']
|
|
55
|
+
case adapter
|
|
56
|
+
when 'mysql2'
|
|
57
|
+
require 'mysql2'
|
|
58
|
+
when 'pg'
|
|
59
|
+
require 'pg'
|
|
60
|
+
when 'sqlite3'
|
|
61
|
+
require 'sqlite3'
|
|
62
|
+
else
|
|
63
|
+
puts "Unknown adapter: #{adapter}"
|
|
64
|
+
exit 1
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# 连接数据库
|
|
68
|
+
ActiveRecord::Base.establish_connection(all_db_configs[db_name])
|
|
69
|
+
|
|
70
|
+
# 确保tmp目录存在
|
|
71
|
+
FileUtils.mkdir_p File.join(path, 'tmp')
|
|
72
|
+
|
|
73
|
+
# 生成数据库 schema 文件
|
|
74
|
+
schema_file = File.join(path, "tmp/#{db_name}_schema.rb")
|
|
75
|
+
File.open(schema_file, 'w:utf-8') do |file|
|
|
76
|
+
if ActiveRecord.version >= '7.1'
|
|
77
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, file)
|
|
78
|
+
else
|
|
79
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
case options[:type].downcase
|
|
84
|
+
when 'schema'
|
|
85
|
+
puts "Schema file: #{schema_file}"
|
|
86
|
+
when 'csv', 'excel'
|
|
87
|
+
require_relative File.join(__dir__, 'dbtodoc/schema.rb')
|
|
88
|
+
ActiveRecord::Schema.set_doc_type(options[:type])
|
|
89
|
+
#执行schema.rb文件,生成csv|excel文件
|
|
90
|
+
eval File.read(schema_file), binding
|
|
91
|
+
puts "CSV file: #{File.join(path, "tmp/#{db_name}.csv")}" if options[:type] == 'csv'
|
|
92
|
+
puts "Excel file: #{File.join(path, "tmp/#{db_name}.xlsx")}" if options[:type] == 'excel'
|
|
93
|
+
else
|
|
94
|
+
puts "Unknown type: #{options[:type]}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
data/sig/dbtodoc.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: dbtodoc
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- shuhjx
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-01-05 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: 将 Rails 项目的数据库 schema 导出为文档,支持 schema, csv, excel 格式
|
|
14
|
+
email:
|
|
15
|
+
- shuh_jx@163.com
|
|
16
|
+
executables:
|
|
17
|
+
- dbtodoc
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- ".DS_Store"
|
|
22
|
+
- ".rspec"
|
|
23
|
+
- README.md
|
|
24
|
+
- Rakefile
|
|
25
|
+
- bin/.DS_Store
|
|
26
|
+
- bin/dbtodoc
|
|
27
|
+
- dbtodoc.gemspec
|
|
28
|
+
- lib/dbtodoc.rb
|
|
29
|
+
- lib/dbtodoc/definition.rb
|
|
30
|
+
- lib/dbtodoc/doc/csv.rb
|
|
31
|
+
- lib/dbtodoc/doc/excel.rb
|
|
32
|
+
- lib/dbtodoc/i18n.rb
|
|
33
|
+
- lib/dbtodoc/schema.rb
|
|
34
|
+
- lib/dbtodoc/version.rb
|
|
35
|
+
- sig/dbtodoc.rbs
|
|
36
|
+
homepage: https://github.com/shuhjx/dbtodoc
|
|
37
|
+
licenses: []
|
|
38
|
+
metadata:
|
|
39
|
+
allowed_push_host: https://rubygems.org
|
|
40
|
+
homepage_uri: https://github.com/shuhjx/dbtodoc
|
|
41
|
+
source_code_uri: https://github.com/shuhjx/dbtodoc
|
|
42
|
+
changelog_uri: https://github.com/shuhjx/dbtodoc/blob/main/CHANGELOG.md
|
|
43
|
+
post_install_message:
|
|
44
|
+
rdoc_options: []
|
|
45
|
+
require_paths:
|
|
46
|
+
- lib
|
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: 2.6.0
|
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '0'
|
|
57
|
+
requirements: []
|
|
58
|
+
rubygems_version: 3.3.26
|
|
59
|
+
signing_key:
|
|
60
|
+
specification_version: 4
|
|
61
|
+
summary: 将 Rails 项目的数据库 schema 导出为文档
|
|
62
|
+
test_files: []
|