Skip to content

Commit dcf5bf5

Browse files
yhk1038claude
andcommitted
feat(docs): add individual example tests with line number mapping
- Add scripts/generate_example_tests.rb for automated test generation - Update all 45 spec files with individual describe blocks per example - Each code example now has its own test with correct line number - ExampleBadge links now point to exact test lines in spec files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2ebf8ab commit dcf5bf5

46 files changed

Lines changed: 8158 additions & 432 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

scripts/generate_example_tests.rb

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
# 문서의 코드 예제를 분석하여:
5+
# 1. spec 파일에 개별 예제 테스트 생성
6+
# 2. 문서의 ExampleBadge 라인 번호 업데이트
7+
#
8+
# 사용법:
9+
# ruby scripts/generate_example_tests.rb [--dry-run]
10+
11+
require_relative "../lib/t_ruby/docs_example_extractor"
12+
13+
class ExampleTestGenerator
14+
DOCS_ROOT = File.expand_path("../../t-ruby.github.io", __dir__)
15+
KO_DOCS = "#{DOCS_ROOT}/i18n/ko/docusaurus-plugin-content-docs/current"
16+
SPEC_ROOT = File.expand_path("../spec/docs_site/pages", __dir__)
17+
18+
def initialize(dry_run: false)
19+
@dry_run = dry_run
20+
@extractor = TRuby::DocsExampleExtractor.new
21+
end
22+
23+
def run
24+
doc_files = Dir.glob("#{KO_DOCS}/**/*.md")
25+
26+
doc_files.each do |doc_path|
27+
process_document(doc_path)
28+
end
29+
end
30+
31+
private
32+
33+
def process_document(doc_path)
34+
relative_path = doc_path.sub("#{KO_DOCS}/", "")
35+
spec_relative = relative_path.sub(".md", "_spec.rb").gsub("-", "_")
36+
spec_path = "#{SPEC_ROOT}/#{spec_relative}"
37+
38+
return unless File.exist?(spec_path)
39+
40+
examples = @extractor.extract_from_file(doc_path)
41+
return if examples.empty?
42+
43+
puts "Processing: #{relative_path} (#{examples.size} examples)"
44+
45+
# 1. spec 파일 생성
46+
spec_content = generate_spec_content(relative_path, examples)
47+
line_map = calculate_line_map(spec_content, examples)
48+
49+
# 2. 문서 업데이트
50+
doc_content = File.read(doc_path, encoding: "UTF-8")
51+
updated_doc = update_doc_badges(doc_content, examples, spec_relative, line_map)
52+
53+
if @dry_run
54+
puts " [DRY RUN] Would update spec and doc"
55+
puts " Line map: #{line_map.inspect}"
56+
else
57+
File.write(spec_path, spec_content, encoding: "UTF-8")
58+
File.write(doc_path, updated_doc, encoding: "UTF-8")
59+
puts " Updated: #{spec_path}"
60+
puts " Updated: #{doc_path}"
61+
end
62+
end
63+
64+
def generate_spec_content(relative_path, examples)
65+
doc_name = relative_path.sub(".md", "").split("/").last.gsub("-", " ").split.map(&:capitalize).join(" ")
66+
67+
lines = []
68+
lines << '# frozen_string_literal: true'
69+
lines << ''
70+
lines << 'require "spec_helper"'
71+
lines << 'require_relative "../support/shared_examples"'
72+
lines << ''
73+
lines << "RSpec.describe \"한글 문서: #{doc_name}\" do"
74+
lines << ' include_context "docs site paths"'
75+
lines << ''
76+
lines << " let(:relative_path) { \"#{relative_path}\" }"
77+
lines << ' let(:doc_path) { "#{ko_docs_root}/#{relative_path}" }'
78+
lines << ' let(:doc_content) { File.read(doc_path) }'
79+
lines << ' let(:extractor) { TRuby::DocsExampleExtractor.new }'
80+
lines << ' let(:verifier) { TRuby::DocsExampleVerifier.new }'
81+
lines << ' let(:examples) { extractor.extract_from_file(doc_path) }'
82+
lines << ''
83+
lines << ' before do'
84+
lines << ' skip "t-ruby.github.io not found" unless Dir.exist?(docs_site_root)'
85+
lines << ' skip "Document not found" unless File.exist?(doc_path)'
86+
lines << ' end'
87+
lines << ''
88+
lines << " it_behaves_like \"valid documentation page\", \"#{relative_path}\""
89+
lines << ''
90+
lines << ' describe "코드 예제" do'
91+
92+
examples.each_with_index do |example, index|
93+
lines << generate_example_test(example, index)
94+
end
95+
96+
lines << ' end'
97+
lines << 'end'
98+
lines << ''
99+
100+
lines.join("\n")
101+
end
102+
103+
def generate_example_test(example, index)
104+
# 예제 코드의 첫 줄을 기반으로 설명 생성
105+
first_line = example.code.lines.first&.strip || "코드"
106+
first_line = first_line[0..40] + "..." if first_line.length > 40
107+
108+
lang_name = case example.language
109+
when "trb" then "T-Ruby"
110+
when "ruby" then "Ruby"
111+
when "rbs" then "RBS"
112+
else example.language
113+
end
114+
115+
lines = []
116+
lines << ''
117+
lines << " # 예제 #{index + 1}: #{lang_name} (라인 #{example.line_number})"
118+
lines << " describe \"예제 #{index + 1}: #{lang_name} 코드\" do"
119+
lines << " let(:example) { examples[#{index}] }"
120+
lines << ''
121+
122+
case example.language
123+
when "trb"
124+
lines << ' it "T-Ruby 코드가 파싱에 성공한다" do'
125+
lines << ' skip "예제를 찾을 수 없음" unless example'
126+
lines << ' parser = TRuby::Parser.new(example.code)'
127+
lines << ' expect { parser.parse }.not_to raise_error'
128+
lines << ' end'
129+
when "ruby"
130+
lines << ' it "유효한 Ruby 문법이다" do'
131+
lines << ' skip "예제를 찾을 수 없음" unless example'
132+
lines << ' expect { RubyVM::InstructionSequence.compile(example.code) }.not_to raise_error'
133+
lines << ' end'
134+
when "rbs"
135+
lines << ' it "RBS 형식이다" do'
136+
lines << ' skip "예제를 찾을 수 없음" unless example'
137+
lines << ' expect(example.code).to match(/\b(def|class|module|interface|type)\b/)'
138+
lines << ' end'
139+
end
140+
141+
lines << ' end'
142+
lines.join("\n")
143+
end
144+
145+
def calculate_line_map(spec_content, examples)
146+
# spec 파일에서 각 예제 테스트의 시작 라인 번호 계산
147+
line_map = {}
148+
149+
spec_content.lines.each_with_index do |line, index|
150+
if match = line.match(/# 예제 (\d+):/)
151+
example_index = match[1].to_i - 1
152+
line_map[example_index] = index + 1 # 1-based line number
153+
end
154+
end
155+
156+
line_map
157+
end
158+
159+
def update_doc_badges(doc_content, examples, spec_file, line_map)
160+
lines = doc_content.lines
161+
result = []
162+
example_index = 0
163+
164+
i = 0
165+
while i < lines.size
166+
line = lines[i]
167+
168+
# ExampleBadge 찾기
169+
if line.include?("<ExampleBadge")
170+
# 다음 코드 블록의 예제 인덱스 찾기
171+
test_line = line_map[example_index] || 21
172+
173+
# 라인 번호 업데이트
174+
updated_badge = line.gsub(/line=\{\d+\}/, "line={#{test_line}}")
175+
result << updated_badge
176+
example_index += 1
177+
else
178+
result << line
179+
end
180+
181+
i += 1
182+
end
183+
184+
result.join
185+
end
186+
end
187+
188+
if __FILE__ == $0
189+
dry_run = ARGV.include?("--dry-run")
190+
ExampleTestGenerator.new(dry_run: dry_run).run
191+
end

spec/docs_site/pages/cli/compiler_options_spec.rb

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
require "spec_helper"
44
require_relative "../support/shared_examples"
55

6-
RSpec.describe "한글 문서: 컴파일러 옵션" do
6+
RSpec.describe "한글 문서: Compiler Options" do
77
include_context "docs site paths"
88

99
let(:relative_path) { "cli/compiler-options.md" }
@@ -19,4 +19,113 @@
1919
end
2020

2121
it_behaves_like "valid documentation page", "cli/compiler-options.md"
22+
23+
describe "코드 예제" do
24+
25+
# 예제 1: T-Ruby (라인 42)
26+
describe "예제 1: T-Ruby 코드" do
27+
let(:example) { examples[0] }
28+
29+
it "T-Ruby 코드가 파싱에 성공한다" do
30+
skip "예제를 찾을 수 없음" unless example
31+
parser = TRuby::Parser.new(example.code)
32+
expect { parser.parse }.not_to raise_error
33+
end
34+
end
35+
36+
# 예제 2: Ruby (라인 66)
37+
describe "예제 2: Ruby 코드" do
38+
let(:example) { examples[1] }
39+
40+
it "유효한 Ruby 문법이다" do
41+
skip "예제를 찾을 수 없음" unless example
42+
expect { RubyVM::InstructionSequence.compile(example.code) }.not_to raise_error
43+
end
44+
end
45+
46+
# 예제 3: T-Ruby (라인 85)
47+
describe "예제 3: T-Ruby 코드" do
48+
let(:example) { examples[2] }
49+
50+
it "T-Ruby 코드가 파싱에 성공한다" do
51+
skip "예제를 찾을 수 없음" unless example
52+
parser = TRuby::Parser.new(example.code)
53+
expect { parser.parse }.not_to raise_error
54+
end
55+
end
56+
57+
# 예제 4: T-Ruby (라인 107)
58+
describe "예제 4: T-Ruby 코드" do
59+
let(:example) { examples[3] }
60+
61+
it "T-Ruby 코드가 파싱에 성공한다" do
62+
skip "예제를 찾을 수 없음" unless example
63+
parser = TRuby::Parser.new(example.code)
64+
expect { parser.parse }.not_to raise_error
65+
end
66+
end
67+
68+
# 예제 5: T-Ruby (라인 129)
69+
describe "예제 5: T-Ruby 코드" do
70+
let(:example) { examples[4] }
71+
72+
it "T-Ruby 코드가 파싱에 성공한다" do
73+
skip "예제를 찾을 수 없음" unless example
74+
parser = TRuby::Parser.new(example.code)
75+
expect { parser.parse }.not_to raise_error
76+
end
77+
end
78+
79+
# 예제 6: T-Ruby (라인 151)
80+
describe "예제 6: T-Ruby 코드" do
81+
let(:example) { examples[5] }
82+
83+
it "T-Ruby 코드가 파싱에 성공한다" do
84+
skip "예제를 찾을 수 없음" unless example
85+
parser = TRuby::Parser.new(example.code)
86+
expect { parser.parse }.not_to raise_error
87+
end
88+
end
89+
90+
# 예제 7: T-Ruby (라인 172)
91+
describe "예제 7: T-Ruby 코드" do
92+
let(:example) { examples[6] }
93+
94+
it "T-Ruby 코드가 파싱에 성공한다" do
95+
skip "예제를 찾을 수 없음" unless example
96+
parser = TRuby::Parser.new(example.code)
97+
expect { parser.parse }.not_to raise_error
98+
end
99+
end
100+
101+
# 예제 8: Ruby (라인 354)
102+
describe "예제 8: Ruby 코드" do
103+
let(:example) { examples[7] }
104+
105+
it "유효한 Ruby 문법이다" do
106+
skip "예제를 찾을 수 없음" unless example
107+
expect { RubyVM::InstructionSequence.compile(example.code) }.not_to raise_error
108+
end
109+
end
110+
111+
# 예제 9: Ruby (라인 366)
112+
describe "예제 9: Ruby 코드" do
113+
let(:example) { examples[8] }
114+
115+
it "유효한 Ruby 문법이다" do
116+
skip "예제를 찾을 수 없음" unless example
117+
expect { RubyVM::InstructionSequence.compile(example.code) }.not_to raise_error
118+
end
119+
end
120+
121+
# 예제 10: Ruby (라인 376)
122+
describe "예제 10: Ruby 코드" do
123+
let(:example) { examples[9] }
124+
125+
it "유효한 Ruby 문법이다" do
126+
skip "예제를 찾을 수 없음" unless example
127+
expect { RubyVM::InstructionSequence.compile(example.code) }.not_to raise_error
128+
end
129+
end
130+
end
22131
end

0 commit comments

Comments
 (0)