docwright 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/.rspec +2 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +25 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +148 -0
- data/Rakefile +8 -0
- data/examples/.docwright.yml +11 -0
- data/examples/README.md +28 -0
- data/examples/api.md +20 -0
- data/examples/concerns.md +18 -0
- data/examples/database.md +27 -0
- data/examples/models.md +38 -0
- data/lib/docwright/checker.rb +151 -0
- data/lib/docwright/extractors/api_extractor.rb +24 -0
- data/lib/docwright/extractors/auth_extractor.rb +121 -0
- data/lib/docwright/extractors/background_jobs_extractor.rb +149 -0
- data/lib/docwright/extractors/concerns_extractor.rb +93 -0
- data/lib/docwright/extractors/database_extractor.rb +27 -0
- data/lib/docwright/extractors/model_extractor.rb +50 -0
- data/lib/docwright/extractors/services_extractor.rb +60 -0
- data/lib/docwright/generators/feature_generator.rb +74 -0
- data/lib/docwright/generators/manual_generator.rb +135 -0
- data/lib/docwright/merger.rb +59 -0
- data/lib/docwright/railtie.rb +10 -0
- data/lib/docwright/searcher.rb +55 -0
- data/lib/docwright/tasks/docwright.rake +28 -0
- data/lib/docwright/version.rb +5 -0
- data/lib/docwright/wizard.rb +243 -0
- data/lib/docwright.rb +23 -0
- data/sig/docwright.rbs +4 -0
- metadata +106 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docwright
|
|
4
|
+
module Generators
|
|
5
|
+
class ManualGenerator
|
|
6
|
+
TEMPLATES = {
|
|
7
|
+
"overview.md" => <<~MD,
|
|
8
|
+
# Overview
|
|
9
|
+
|
|
10
|
+
## What is this app?
|
|
11
|
+
<!-- Describe what this application does and why it exists -->
|
|
12
|
+
|
|
13
|
+
## Who is it for?
|
|
14
|
+
<!-- Describe the target users or customers -->
|
|
15
|
+
|
|
16
|
+
## Key features
|
|
17
|
+
<!-- List the main features of the application -->
|
|
18
|
+
MD
|
|
19
|
+
"business_rules.md" => <<~MD,
|
|
20
|
+
# Business Rules
|
|
21
|
+
|
|
22
|
+
## Core rules
|
|
23
|
+
<!-- Describe the fundamental business rules that drive this application -->
|
|
24
|
+
|
|
25
|
+
## Edge cases
|
|
26
|
+
<!-- Document important edge cases and how they are handled -->
|
|
27
|
+
MD
|
|
28
|
+
"setup.md" => <<~MD,
|
|
29
|
+
# Setup
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
<!-- List required tools, languages, and versions -->
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
<!-- Step by step setup instructions -->
|
|
36
|
+
|
|
37
|
+
## Environment variables
|
|
38
|
+
<!-- List all required environment variables and what they do -->
|
|
39
|
+
|
|
40
|
+
## Seed data
|
|
41
|
+
<!-- Describe seed data and how to run it -->
|
|
42
|
+
MD
|
|
43
|
+
"architecture.md" => <<~MD,
|
|
44
|
+
# Architecture
|
|
45
|
+
|
|
46
|
+
## High-level structure
|
|
47
|
+
<!-- Describe the overall structure of the application -->
|
|
48
|
+
|
|
49
|
+
## Key design decisions
|
|
50
|
+
<!-- Document important architectural decisions and the reasoning behind them -->
|
|
51
|
+
|
|
52
|
+
## External dependencies
|
|
53
|
+
<!-- List external services, APIs, or libraries this app depends on -->
|
|
54
|
+
MD
|
|
55
|
+
"deployment.md" => <<~MD,
|
|
56
|
+
# Deployment
|
|
57
|
+
|
|
58
|
+
## Environments
|
|
59
|
+
<!-- List all environments and their purposes -->
|
|
60
|
+
|
|
61
|
+
## Deploy process
|
|
62
|
+
<!-- Step by step deployment instructions -->
|
|
63
|
+
|
|
64
|
+
## Infrastructure
|
|
65
|
+
<!-- Describe the infrastructure this app runs on -->
|
|
66
|
+
MD
|
|
67
|
+
"security.md" => <<~MD,
|
|
68
|
+
# Security
|
|
69
|
+
|
|
70
|
+
## Authentication
|
|
71
|
+
<!-- Describe how authentication works -->
|
|
72
|
+
|
|
73
|
+
## Authorization
|
|
74
|
+
<!-- Describe how authorization and permissions work -->
|
|
75
|
+
|
|
76
|
+
## Sensitive data
|
|
77
|
+
<!-- List sensitive data this app handles and how it is protected -->
|
|
78
|
+
MD
|
|
79
|
+
"troubleshooting.md" => <<~MD,
|
|
80
|
+
# Troubleshooting
|
|
81
|
+
|
|
82
|
+
## Common problems
|
|
83
|
+
<!-- List common issues and their solutions -->
|
|
84
|
+
|
|
85
|
+
## Debugging tips
|
|
86
|
+
<!-- Useful tips for debugging this application -->
|
|
87
|
+
MD
|
|
88
|
+
"changelog.md" => <<~MD,
|
|
89
|
+
# Changelog
|
|
90
|
+
|
|
91
|
+
## Unreleased
|
|
92
|
+
<!-- Changes not yet in production -->
|
|
93
|
+
|
|
94
|
+
## [Version] - YYYY-MM-DD
|
|
95
|
+
<!-- Document significant changes per release -->
|
|
96
|
+
MD
|
|
97
|
+
"readme.md" => <<~MD
|
|
98
|
+
# [App Name]
|
|
99
|
+
|
|
100
|
+
## Overview
|
|
101
|
+
<!-- Brief description of the application -->
|
|
102
|
+
|
|
103
|
+
## Quick start
|
|
104
|
+
<!-- Fastest way to get the app running locally -->
|
|
105
|
+
|
|
106
|
+
## Documentation
|
|
107
|
+
<!-- Link to or summarize key documentation -->
|
|
108
|
+
MD
|
|
109
|
+
}.freeze
|
|
110
|
+
|
|
111
|
+
def generate
|
|
112
|
+
FileUtils.mkdir_p("docs")
|
|
113
|
+
TEMPLATES.each do |filename, content|
|
|
114
|
+
path = "docs/#{filename}"
|
|
115
|
+
if File.exist?(path)
|
|
116
|
+
puts "DocWright: skipped #{path} (already exists)"
|
|
117
|
+
else
|
|
118
|
+
File.write(path, content)
|
|
119
|
+
puts "DocWright: wrote #{path}"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def generate_single(path, filename)
|
|
125
|
+
return if File.exist?(path)
|
|
126
|
+
|
|
127
|
+
content = TEMPLATES[filename]
|
|
128
|
+
return unless content
|
|
129
|
+
|
|
130
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
131
|
+
File.write(path, content)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docwright
|
|
4
|
+
class Merger
|
|
5
|
+
AUTO_START = "<!-- DOCWRIGHT:AUTO -->"
|
|
6
|
+
AUTO_END = "<!-- DOCWRIGHT:END -->"
|
|
7
|
+
|
|
8
|
+
def self.write(path, auto_content)
|
|
9
|
+
if File.exist?(path)
|
|
10
|
+
merge(path, auto_content)
|
|
11
|
+
else
|
|
12
|
+
fresh(path, auto_content)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.write_named(path, name, auto_content, notes_placeholder)
|
|
17
|
+
if File.exist?(path)
|
|
18
|
+
merge_named(path, name, auto_content)
|
|
19
|
+
else
|
|
20
|
+
fresh_named(path, name, auto_content, notes_placeholder)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.fresh(path, auto_content)
|
|
25
|
+
content = "#{AUTO_START}\n#{auto_content}\n#{AUTO_END}"
|
|
26
|
+
File.write(path, content)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.fresh_named(path, name, auto_content, notes_placeholder)
|
|
30
|
+
content = ""
|
|
31
|
+
content += "<!-- DOCWRIGHT:AUTO:#{name} -->\n"
|
|
32
|
+
content += "#{auto_content}\n"
|
|
33
|
+
content += "<!-- DOCWRIGHT:END:#{name} -->\n"
|
|
34
|
+
content += "#{notes_placeholder}\n"
|
|
35
|
+
File.write(path, content)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.merge(path, auto_content)
|
|
39
|
+
existing = File.read(path)
|
|
40
|
+
replacement = "#{AUTO_START}\n#{auto_content}\n#{AUTO_END}"
|
|
41
|
+
updated = existing.gsub(/#{Regexp.escape(AUTO_START)}.*?#{Regexp.escape(AUTO_END)}/m, replacement)
|
|
42
|
+
File.write(path, updated)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.merge_named(path, name, auto_content)
|
|
46
|
+
existing = File.read(path)
|
|
47
|
+
start_marker = "<!-- DOCWRIGHT:AUTO:#{name} -->"
|
|
48
|
+
end_marker = "<!-- DOCWRIGHT:END:#{name} -->"
|
|
49
|
+
replacement = "#{start_marker}\n#{auto_content}\n#{end_marker}"
|
|
50
|
+
|
|
51
|
+
updated = if existing.include?(start_marker)
|
|
52
|
+
existing.gsub(/#{Regexp.escape(start_marker)}.*?#{Regexp.escape(end_marker)}/m, replacement)
|
|
53
|
+
else
|
|
54
|
+
existing + "\n#{replacement}\n### Notes for #{name}\n<!-- Add your notes about #{name} here -->\n"
|
|
55
|
+
end
|
|
56
|
+
File.write(path, updated)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docwright
|
|
4
|
+
class Searcher
|
|
5
|
+
def initialize(term)
|
|
6
|
+
@term = term
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def run
|
|
10
|
+
puts "\nDocWright Search: \"#{@term}\""
|
|
11
|
+
puts "===================================\n\n"
|
|
12
|
+
|
|
13
|
+
results = search_docs
|
|
14
|
+
print_results(results)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def search_docs
|
|
20
|
+
md_files = Dir.glob("docs/**/*.md")
|
|
21
|
+
results = {}
|
|
22
|
+
|
|
23
|
+
md_files.each do |file|
|
|
24
|
+
matches = []
|
|
25
|
+
File.readlines(file).each_with_index do |line, i|
|
|
26
|
+
matches << { line_number: i + 1, content: line.chomp } if line.downcase.include?(@term.downcase)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
results[file] = matches if matches.any?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
results
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def print_results(results)
|
|
36
|
+
if results.empty?
|
|
37
|
+
puts "No matches found for \"#{@term}\""
|
|
38
|
+
return
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
total_matches = 0
|
|
42
|
+
|
|
43
|
+
results.each do |file, matches|
|
|
44
|
+
puts file
|
|
45
|
+
matches.each do |match|
|
|
46
|
+
puts "Line #{match[:line_number]}: #{match[:content].strip}"
|
|
47
|
+
total_matches += 1
|
|
48
|
+
end
|
|
49
|
+
puts ""
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
puts "#{results.size} #{"file".pluralize(results.size)}, #{total_matches} #{"match".pluralize(total_matches)} found."
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :docwright do
|
|
4
|
+
desc "Generate documentation for this Rails application"
|
|
5
|
+
task generate: :environment do
|
|
6
|
+
# Docwright::Extractors::DatabaseExtractor.new.generate
|
|
7
|
+
# Docwright::Extractors::ApiExtractor.new.generate
|
|
8
|
+
# Docwright::Extractors::ModelExtractor.new.generate
|
|
9
|
+
# Docwright::Generators::ManualGenerator.new.generate
|
|
10
|
+
# Docwright::Generators::FeatureGenerator.new.generate
|
|
11
|
+
Docwright::Wizard.new.run
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
desc "Check Documentation completeness"
|
|
15
|
+
task check: :environment do
|
|
16
|
+
Docwright::Checker.new.run
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
desc "Search documentation"
|
|
20
|
+
task :search, [:term] => :environment do |_, args|
|
|
21
|
+
term = args[:term]
|
|
22
|
+
if term.nil? || term.empty?
|
|
23
|
+
puts "Usage: rake docwright:search[your_search_term]"
|
|
24
|
+
else
|
|
25
|
+
Docwright::Searcher.new(term).run
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docwright
|
|
4
|
+
class Wizard # rubocop:disable Metrics/ClassLength
|
|
5
|
+
FIRST_RUN_MESSAGE = <<~MSG
|
|
6
|
+
============================================
|
|
7
|
+
DocWright — Rails Documentation Generator
|
|
8
|
+
============================================
|
|
9
|
+
|
|
10
|
+
This tool will generate a docs/ folder with:
|
|
11
|
+
|
|
12
|
+
Auto-generated (safe to regenerate anytime):
|
|
13
|
+
- database.md — tables, columns, types
|
|
14
|
+
- api.md — routes and endpoints
|
|
15
|
+
- models.md — model associations and validations
|
|
16
|
+
|
|
17
|
+
Manual templates (written once, never overwritten):
|
|
18
|
+
- overview.md, setup.md, architecture.md,
|
|
19
|
+
deployment.md, security.md, troubleshooting.md,
|
|
20
|
+
business_rules.md, changelog.md, readme.md
|
|
21
|
+
- docs/features/ — feature-specific docs
|
|
22
|
+
|
|
23
|
+
Important:
|
|
24
|
+
Never manually edit content inside
|
|
25
|
+
<!-- DOCWRIGHT:AUTO --> blocks — it will be overwritten.
|
|
26
|
+
Write your notes outside those markers.
|
|
27
|
+
|
|
28
|
+
============================================
|
|
29
|
+
MSG
|
|
30
|
+
|
|
31
|
+
DOCWRIGHT_YML_TEMPLATE = <<~YAML
|
|
32
|
+
# DocWright feature configuration
|
|
33
|
+
# Declare your app's features here and DocWright will generate a doc template for each one.
|
|
34
|
+
##{" "}
|
|
35
|
+
# Optional narrative docs - set to true to generate
|
|
36
|
+
# DocWright will only generate these if the relevant folders exist in your app
|
|
37
|
+
##{" "}
|
|
38
|
+
# optional_docs:
|
|
39
|
+
# controllers: true
|
|
40
|
+
# background_jobs: true
|
|
41
|
+
# services: true
|
|
42
|
+
# auth_and_permissions: true
|
|
43
|
+
#
|
|
44
|
+
# Feature-specific docs
|
|
45
|
+
# Uncomment and edit the example below to add your features:
|
|
46
|
+
#
|
|
47
|
+
# features:
|
|
48
|
+
# - name: qr_flow
|
|
49
|
+
# description: QR code generation and scanning flow
|
|
50
|
+
# - name: subscriptions
|
|
51
|
+
# description: Billing, plan management, and renewals
|
|
52
|
+
YAML
|
|
53
|
+
|
|
54
|
+
def run # rubocop:disable Metrics/MethodLength
|
|
55
|
+
## File.exist? also works for both files and directories but Dir.exist? is specific for folder and return nil if that is named as file name
|
|
56
|
+
first_run = !Dir.exist?("docs")
|
|
57
|
+
puts FIRST_RUN_MESSAGE if first_run
|
|
58
|
+
|
|
59
|
+
create_config_if_missing
|
|
60
|
+
|
|
61
|
+
puts "\nDocWright: scanning your app...\n\n"
|
|
62
|
+
|
|
63
|
+
report = detection_pass
|
|
64
|
+
display_report(report)
|
|
65
|
+
|
|
66
|
+
return unless confirm
|
|
67
|
+
|
|
68
|
+
generate_auto_files
|
|
69
|
+
process_manual_files(report[:new_manual])
|
|
70
|
+
process_manual_files(report[:new_features])
|
|
71
|
+
|
|
72
|
+
puts "\nDocWright: done!\n"
|
|
73
|
+
rescue Interrupt
|
|
74
|
+
puts "\n\nDocWright: wizard cancelled."
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def create_config_if_missing
|
|
80
|
+
return if File.exist?(".docwright.yml")
|
|
81
|
+
|
|
82
|
+
File.write(".docwright.yml", DOCWRIGHT_YML_TEMPLATE)
|
|
83
|
+
puts "DocWright: created .docwright.yml — add your features there.\n"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def detection_pass # rubocop:disable Metrics/MethodLength
|
|
87
|
+
auto_files = %w[database.md api.md models.md]
|
|
88
|
+
manual_files = %w[overview.md business_rules.md setup.md
|
|
89
|
+
architecture.md deployment.md security.md
|
|
90
|
+
troubleshooting.md changelog.md readme.md]
|
|
91
|
+
|
|
92
|
+
feature_files = load_feature_files
|
|
93
|
+
{
|
|
94
|
+
auto: auto_files,
|
|
95
|
+
new_manual: manual_files.reject { |f| File.exist?("docs/#{f}") },
|
|
96
|
+
existing_manual: manual_files.select { |f| File.exist?("docs/#{f}") },
|
|
97
|
+
new_features: feature_files.reject { |f| File.exist?("docs/features/#{f}") },
|
|
98
|
+
existing_features: feature_files.select { |f| File.exist?("docs/features/#{f}") }
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def load_feature_files
|
|
103
|
+
return [] unless File.exist?(".docwright.yml")
|
|
104
|
+
|
|
105
|
+
require "yaml"
|
|
106
|
+
config = YAML.load_file(".docwright.yml")
|
|
107
|
+
return [] unless config.is_a?(Hash)
|
|
108
|
+
|
|
109
|
+
(config["features"] || []).map { |f| "#{f["name"]}.md" }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def display_report(report) # rubocop:disable Metrics/CyclomaticComplexity
|
|
113
|
+
puts " Will regenerate (auto):"
|
|
114
|
+
report[:auto].each { |f| puts " - #{f}" }
|
|
115
|
+
|
|
116
|
+
unless report[:new_manual].empty?
|
|
117
|
+
puts "\n Will generate (new manual templates):"
|
|
118
|
+
report[:new_manual].each { |f| puts " - #{f}" }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
unless report[:new_features].empty?
|
|
122
|
+
puts "\n Will generate (new feature docs):"
|
|
123
|
+
report[:new_features].each { |f| puts " - #{f}" }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
unless report[:existing_manual].empty?
|
|
127
|
+
puts "\n Will skip (already exist):"
|
|
128
|
+
report[:existing_manual].each { |f| puts " - #{f}" }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
return if report[:existing_features].empty?
|
|
132
|
+
|
|
133
|
+
puts "\n Will skip (already exist):"
|
|
134
|
+
report[:existing_features].each { |f| puts " - #{f}" }
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def confirm
|
|
138
|
+
print "\nContinue? (y/n): "
|
|
139
|
+
$stdin.gets.chomp.downcase == "y"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def generate_auto_files
|
|
143
|
+
puts "\nDocWright: generating auto files..."
|
|
144
|
+
Docwright::Extractors::DatabaseExtractor.new.generate
|
|
145
|
+
Docwright::Extractors::ApiExtractor.new.generate
|
|
146
|
+
Docwright::Extractors::ModelExtractor.new.generate
|
|
147
|
+
generate_optional_docs
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def generate_optional_docs
|
|
151
|
+
return unless File.exist?(".docwright.yml")
|
|
152
|
+
|
|
153
|
+
require "yaml"
|
|
154
|
+
config = YAML.load_file(".docwright.yml")
|
|
155
|
+
return unless config.is_a?(Hash)
|
|
156
|
+
|
|
157
|
+
optional = config["optional_docs"] || {}
|
|
158
|
+
Docwright::Extractors::AuthExtractor.new.generate if optional["auth_and_permissions"]
|
|
159
|
+
Docwright::Extractors::BackgroundJobsExtractor.new.generate if optional["background_jobs"]
|
|
160
|
+
Docwright::Extractors::ServicesExtractor.new.generate if optional["services"]
|
|
161
|
+
Docwright::Extractors::ConcernsExtractor.new.generate if optional["concerns"]
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def process_manual_files(files)
|
|
165
|
+
return if files.empty?
|
|
166
|
+
|
|
167
|
+
files.each do |filename|
|
|
168
|
+
feature = !%w[overview.md business_rules.md setup.md
|
|
169
|
+
architecture.md deployment.md security.md
|
|
170
|
+
troubleshooting.md changelog.md readme.md].include?(filename)
|
|
171
|
+
|
|
172
|
+
path = feature ? "docs/features/#{filename}" : "docs/#{filename}"
|
|
173
|
+
|
|
174
|
+
loop do
|
|
175
|
+
puts "\nGenerate #{filename}?"
|
|
176
|
+
puts " [w] write in terminal"
|
|
177
|
+
puts " [e] open in $EDITOR"
|
|
178
|
+
puts " [s] skip for now"
|
|
179
|
+
puts " [q] quit wizard"
|
|
180
|
+
print "Choice: "
|
|
181
|
+
|
|
182
|
+
case $stdin.gets.chomp.downcase
|
|
183
|
+
when "w"
|
|
184
|
+
write_in_terminal(path)
|
|
185
|
+
break
|
|
186
|
+
when "e"
|
|
187
|
+
open_in_editor(path)
|
|
188
|
+
break
|
|
189
|
+
when "s"
|
|
190
|
+
write_template(path, filename)
|
|
191
|
+
puts "DocWright: skipped #{filename} — template saved, fill it in later"
|
|
192
|
+
break
|
|
193
|
+
when "q"
|
|
194
|
+
puts "DocWright: wizard exited."
|
|
195
|
+
return
|
|
196
|
+
else
|
|
197
|
+
puts "Invalid choice. Please enter w, e, s, or q."
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def write_in_terminal(path)
|
|
204
|
+
puts "Type your content. Type 'END' on a new line when done (or Ctrl+C to cancel):"
|
|
205
|
+
|
|
206
|
+
lines = []
|
|
207
|
+
begin
|
|
208
|
+
loop do
|
|
209
|
+
line = $stdin.gets.chomp
|
|
210
|
+
break if line == "END"
|
|
211
|
+
break if %w[exit q].include?(line.downcase)
|
|
212
|
+
|
|
213
|
+
lines << line
|
|
214
|
+
end
|
|
215
|
+
rescue Interrupt
|
|
216
|
+
puts "\nCancelled."
|
|
217
|
+
return
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
if lines.empty?
|
|
221
|
+
puts "DocWright: nothing written, skipping #{path}"
|
|
222
|
+
return
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
226
|
+
File.write(path, lines.join("\n"))
|
|
227
|
+
puts "DocWright: wrote #{path}"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def open_in_editor(path)
|
|
231
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
232
|
+
write_template(path, File.basename(path))
|
|
233
|
+
editor = ENV["EDITOR"] || "nano"
|
|
234
|
+
system("#{editor} #{path}")
|
|
235
|
+
puts "DocWright: wrote #{path}"
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def write_template(path, filename)
|
|
239
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
240
|
+
Docwright::Generators::ManualGenerator.new.generate_single(path, filename)
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
data/lib/docwright.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "docwright/version"
|
|
4
|
+
require "rails/railtie"
|
|
5
|
+
require_relative "docwright/railtie"
|
|
6
|
+
require_relative "docwright/merger"
|
|
7
|
+
require_relative "docwright/extractors/database_extractor"
|
|
8
|
+
require_relative "docwright/extractors/api_extractor"
|
|
9
|
+
require_relative "docwright/extractors/model_extractor"
|
|
10
|
+
require_relative "docwright/extractors/auth_extractor"
|
|
11
|
+
require_relative "docwright/extractors/background_jobs_extractor"
|
|
12
|
+
require_relative "docwright/extractors/services_extractor"
|
|
13
|
+
require_relative "docwright/extractors/concerns_extractor"
|
|
14
|
+
require_relative "docwright/generators/manual_generator"
|
|
15
|
+
require_relative "docwright/generators/feature_generator"
|
|
16
|
+
require_relative "docwright/wizard"
|
|
17
|
+
require_relative "docwright/checker"
|
|
18
|
+
require_relative "docwright/searcher"
|
|
19
|
+
|
|
20
|
+
module Docwright
|
|
21
|
+
class Error < StandardError; end
|
|
22
|
+
# Your code goes here...
|
|
23
|
+
end
|
data/sig/docwright.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: docwright
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Grace
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '8.0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '8.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rspec
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.0'
|
|
40
|
+
description: DocWright introspects your live Rails app to generate structured markdown
|
|
41
|
+
documentation for your database schema, API routes, models, services, background
|
|
42
|
+
jobs, and more. Human-written content is preserved across regenerations using smart
|
|
43
|
+
merge markers.
|
|
44
|
+
email:
|
|
45
|
+
- thirihtethtetaung.dev@gmail.com
|
|
46
|
+
executables: []
|
|
47
|
+
extensions: []
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- ".rspec"
|
|
51
|
+
- ".rubocop.yml"
|
|
52
|
+
- CHANGELOG.md
|
|
53
|
+
- CODE_OF_CONDUCT.md
|
|
54
|
+
- LICENSE.txt
|
|
55
|
+
- README.md
|
|
56
|
+
- Rakefile
|
|
57
|
+
- examples/.docwright.yml
|
|
58
|
+
- examples/README.md
|
|
59
|
+
- examples/api.md
|
|
60
|
+
- examples/concerns.md
|
|
61
|
+
- examples/database.md
|
|
62
|
+
- examples/models.md
|
|
63
|
+
- lib/docwright.rb
|
|
64
|
+
- lib/docwright/checker.rb
|
|
65
|
+
- lib/docwright/extractors/api_extractor.rb
|
|
66
|
+
- lib/docwright/extractors/auth_extractor.rb
|
|
67
|
+
- lib/docwright/extractors/background_jobs_extractor.rb
|
|
68
|
+
- lib/docwright/extractors/concerns_extractor.rb
|
|
69
|
+
- lib/docwright/extractors/database_extractor.rb
|
|
70
|
+
- lib/docwright/extractors/model_extractor.rb
|
|
71
|
+
- lib/docwright/extractors/services_extractor.rb
|
|
72
|
+
- lib/docwright/generators/feature_generator.rb
|
|
73
|
+
- lib/docwright/generators/manual_generator.rb
|
|
74
|
+
- lib/docwright/merger.rb
|
|
75
|
+
- lib/docwright/railtie.rb
|
|
76
|
+
- lib/docwright/searcher.rb
|
|
77
|
+
- lib/docwright/tasks/docwright.rake
|
|
78
|
+
- lib/docwright/version.rb
|
|
79
|
+
- lib/docwright/wizard.rb
|
|
80
|
+
- sig/docwright.rbs
|
|
81
|
+
homepage: https://github.com/GraceHtet/docwright
|
|
82
|
+
licenses:
|
|
83
|
+
- MIT
|
|
84
|
+
metadata:
|
|
85
|
+
allowed_push_host: https://rubygems.org
|
|
86
|
+
homepage_uri: https://github.com/GraceHtet/docwright
|
|
87
|
+
source_code_uri: https://github.com/GraceHtet/docwright
|
|
88
|
+
changelog_uri: https://github.com/GraceHtet/docwright/blob/main/CHANGELOG.md
|
|
89
|
+
rdoc_options: []
|
|
90
|
+
require_paths:
|
|
91
|
+
- lib
|
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 3.1.0
|
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
requirements: []
|
|
103
|
+
rubygems_version: 3.6.9
|
|
104
|
+
specification_version: 4
|
|
105
|
+
summary: Auto-generate and maintain documentation for your Rails application
|
|
106
|
+
test_files: []
|