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.
@@ -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,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docwright
4
+ # Railtie for integrating Docwright with Rails.
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks do
7
+ load File.expand_path("tasks/docwright.rake", __dir__)
8
+ end
9
+ end
10
+ 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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docwright
4
+ VERSION = "0.1.0"
5
+ 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
@@ -0,0 +1,4 @@
1
+ module Docwright
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
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: []