Skip to content

Commit 75b34c1

Browse files
committed
wip: AI generated code for now
1 parent bc5a13c commit 75b34c1

File tree

3 files changed

+239
-0
lines changed

3 files changed

+239
-0
lines changed

granc/src/cli.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,19 @@ pub enum Commands {
5757
/// Fully qualified name (e.g. my.package.Service)
5858
symbol: String,
5959
},
60+
61+
/// Generate Markdown documentation for a service.
62+
Docs {
63+
#[command(flatten)]
64+
source: SourceSelection,
65+
66+
/// Fully qualified service name (e.g. my.package.MyService)
67+
symbol: String,
68+
69+
/// Output directory for the generated markdown files
70+
#[arg(long, short = 'o')]
71+
output: PathBuf,
72+
},
6073
}
6174

6275
#[derive(Args, Debug)]

granc/src/docs.rs

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
// granc/src/docs.rs
2+
use crate::formatter::FormattedString;
3+
use colored::control::set_override;
4+
use granc_core::prost_reflect::{EnumDescriptor, Kind, MessageDescriptor, ServiceDescriptor};
5+
use std::collections::HashSet;
6+
use std::fs;
7+
use std::path::PathBuf;
8+
9+
pub struct DocsGenerator {
10+
output_dir: PathBuf,
11+
visited: HashSet<String>,
12+
}
13+
14+
impl DocsGenerator {
15+
pub fn new(output_dir: PathBuf) -> Self {
16+
Self {
17+
output_dir,
18+
visited: HashSet::new(),
19+
}
20+
}
21+
22+
/// Entry point for documentation generation.
23+
pub fn generate(&mut self, service: ServiceDescriptor) -> std::io::Result<()> {
24+
// Force colored output OFF so we get plain text for the markdown files
25+
set_override(false);
26+
27+
if !self.output_dir.exists() {
28+
fs::create_dir_all(&self.output_dir)?;
29+
}
30+
31+
// 1. Generate the Service page and recursively all dependencies
32+
self.generate_service(&service)?;
33+
34+
// 2. Generate the Index (Table of Contents)
35+
self.generate_index(&service)?;
36+
37+
// Restore colored output for the CLI
38+
set_override(true);
39+
Ok(())
40+
}
41+
42+
fn generate_index(&self, service: &ServiceDescriptor) -> std::io::Result<()> {
43+
let path = self.output_dir.join("index.md");
44+
let mut out = String::new();
45+
46+
out.push_str(&format!("# Documentation: `{}`\n\n", service.name()));
47+
48+
out.push_str("## Entry Point\n\n");
49+
out.push_str(&format!(
50+
"- [**Service Definition: {}**]({}.md)\n",
51+
service.name(),
52+
service.full_name()
53+
));
54+
55+
out.push_str("\n## Messages & Enums\n\n");
56+
let mut types: Vec<_> = self.visited.iter().collect();
57+
types.sort();
58+
59+
if types.is_empty() {
60+
out.push_str("*None*\n");
61+
} else {
62+
for name in types {
63+
out.push_str(&format!("- [{}]({}.md)\n", name, name));
64+
}
65+
}
66+
67+
fs::write(path, out)?;
68+
println!("Generated: index.md");
69+
Ok(())
70+
}
71+
72+
fn generate_service(&mut self, service: &ServiceDescriptor) -> std::io::Result<()> {
73+
let filename = format!("{}.md", service.full_name());
74+
let path = self.output_dir.join(&filename);
75+
76+
let mut out = String::new();
77+
out.push_str(&format!("# Service: `{}`\n\n", service.name()));
78+
79+
// 1. Protobuf Definition
80+
out.push_str("## Definition\n\n```protobuf\n");
81+
out.push_str(&FormattedString::from(service.clone()).0);
82+
out.push_str("\n```\n\n");
83+
84+
// 2. Methods List
85+
out.push_str("## Methods\n\n");
86+
for method in service.methods() {
87+
out.push_str(&format!("### `{}`\n\n", method.name()));
88+
89+
let input = method.input();
90+
let output = method.output();
91+
92+
out.push_str(&format!(
93+
"- Request: [{}]({}.md)\n",
94+
input.full_name(),
95+
input.full_name()
96+
));
97+
out.push_str(&format!(
98+
"- Response: [{}]({}.md)\n",
99+
output.full_name(),
100+
output.full_name()
101+
));
102+
out.push('\n');
103+
104+
// Queue recursion for dependencies
105+
self.queue_message(input);
106+
self.queue_message(output);
107+
}
108+
109+
fs::write(path, out)?;
110+
println!("Generated: {}", filename);
111+
Ok(())
112+
}
113+
114+
fn queue_message(&mut self, message: MessageDescriptor) {
115+
let name = message.full_name().to_string();
116+
if self.visited.contains(&name) {
117+
return;
118+
}
119+
self.visited.insert(name);
120+
121+
if let Err(e) = self.generate_message(message) {
122+
eprintln!("Failed to generate docs for message: {}", e);
123+
}
124+
}
125+
126+
fn generate_message(&mut self, message: MessageDescriptor) -> std::io::Result<()> {
127+
let filename = format!("{}.md", message.full_name());
128+
let path = self.output_dir.join(&filename);
129+
130+
let mut out = String::new();
131+
out.push_str(&format!("# Message: `{}`\n\n", message.name()));
132+
133+
// Definition
134+
out.push_str("## Definition\n\n```protobuf\n");
135+
out.push_str(&FormattedString::from(message.clone()).0);
136+
out.push_str("\n```\n\n");
137+
138+
// Dependencies
139+
out.push_str("## Dependencies\n\n");
140+
let mut has_deps = false;
141+
142+
for field in message.fields() {
143+
match field.kind() {
144+
Kind::Message(m) => {
145+
has_deps = true;
146+
out.push_str(&format!(
147+
"- Field `{}`: [{}]({}.md)\n",
148+
field.name(),
149+
m.full_name(),
150+
m.full_name()
151+
));
152+
self.queue_message(m);
153+
}
154+
Kind::Enum(e) => {
155+
has_deps = true;
156+
out.push_str(&format!(
157+
"- Field `{}`: [{}]({}.md)\n",
158+
field.name(),
159+
e.full_name(),
160+
e.full_name()
161+
));
162+
self.queue_enum(e);
163+
}
164+
_ => {}
165+
}
166+
}
167+
168+
if !has_deps {
169+
out.push_str("*None*\n");
170+
}
171+
172+
fs::write(path, out)?;
173+
println!("Generated: {}", filename);
174+
Ok(())
175+
}
176+
177+
fn queue_enum(&mut self, enum_desc: EnumDescriptor) {
178+
let name = enum_desc.full_name().to_string();
179+
if self.visited.contains(&name) {
180+
return;
181+
}
182+
self.visited.insert(name);
183+
184+
if let Err(e) = self.generate_enum(enum_desc) {
185+
eprintln!("Failed to generate docs for enum: {}", e);
186+
}
187+
}
188+
189+
fn generate_enum(&mut self, enum_desc: EnumDescriptor) -> std::io::Result<()> {
190+
let filename = format!("{}.md", enum_desc.full_name());
191+
let path = self.output_dir.join(&filename);
192+
193+
let mut out = String::new();
194+
out.push_str(&format!("# Enum: `{}`\n\n", enum_desc.name()));
195+
196+
out.push_str("## Definition\n\n```protobuf\n");
197+
out.push_str(&FormattedString::from(enum_desc).0);
198+
out.push_str("\n```\n");
199+
200+
fs::write(path, out)?;
201+
println!("Generated: {}", filename);
202+
Ok(())
203+
}
204+
}

granc/src/main.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
//! 3. **Execution**: Delegates request processing to `GrancClient`.
99
//! 4. **Presentation**: Formats and prints data.
1010
mod cli;
11+
mod docs;
1112
mod formatter;
1213

1314
use clap::Parser;
@@ -44,6 +45,27 @@ async fn main() {
4445
let descriptor = describe(symbol, source.value()).await;
4546
println!("{}", FormattedString::from(descriptor))
4647
}
48+
49+
// Add the Docs handler
50+
Commands::Docs {
51+
symbol,
52+
source,
53+
output,
54+
} => {
55+
let descriptor = describe(symbol, source.value()).await;
56+
57+
if let Descriptor::ServiceDescriptor(service) = descriptor {
58+
let mut generator = docs::DocsGenerator::new(output);
59+
if let Err(e) = generator.generate(service) {
60+
eprintln!("Error generating docs: {}", e);
61+
process::exit(1);
62+
}
63+
println!("Documentation generated successfully.");
64+
} else {
65+
eprintln!("Error: The symbol passed is not a Service.");
66+
process::exit(1);
67+
}
68+
}
4769
}
4870
}
4971

0 commit comments

Comments
 (0)