b37063e20a
- Dropped the protobuf extensions in favor of explicitly listing out methods in both services if we want to implement both, as it's clearer. - Move Service/Method wrappers into a separate crate that the various clients can import, to easily get at the list of backend services and their correct indices and comments.
193 lines
5.4 KiB
Rust
193 lines
5.4 KiB
Rust
// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
use std::collections::HashSet;
|
|
use std::fmt::Write as WriteFmt;
|
|
use std::io::BufWriter;
|
|
use std::io::Write;
|
|
use std::path::Path;
|
|
|
|
use anki_io::create_dir_all;
|
|
use anki_io::create_file;
|
|
use anki_proto_gen::BackendService;
|
|
use anki_proto_gen::Method;
|
|
use anyhow::Result;
|
|
use inflections::Inflect;
|
|
|
|
pub(crate) fn write_ts_interface(services: &[BackendService]) -> Result<()> {
|
|
let root = Path::new("../../out/ts/lib/anki");
|
|
create_dir_all(root)?;
|
|
|
|
for service in services {
|
|
if service.name == "BackendAnkidroidService" {
|
|
continue;
|
|
}
|
|
|
|
let service_name = service.name.replace("Service", "").to_snake_case();
|
|
|
|
write_dts_file(root, &service_name, service)?;
|
|
write_js_file(root, &service_name, service)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn write_dts_file(root: &Path, service_name: &str, service: &BackendService) -> Result<()> {
|
|
let output_path = root.join(format!("{service_name}_service.d.ts"));
|
|
let mut out = BufWriter::new(create_file(output_path)?);
|
|
write_dts_header(&mut out)?;
|
|
|
|
let mut referenced_packages = HashSet::new();
|
|
let mut method_text = String::new();
|
|
|
|
for method in service.all_methods() {
|
|
let method = MethodDetails::from_method(method);
|
|
record_referenced_type(&mut referenced_packages, &method.input_type)?;
|
|
record_referenced_type(&mut referenced_packages, &method.output_type)?;
|
|
write_dts_method(&method, &mut method_text)?;
|
|
}
|
|
|
|
write_imports(referenced_packages, &mut out)?;
|
|
write!(out, "{}", method_text)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn write_dts_header(out: &mut impl std::io::Write) -> Result<()> {
|
|
out.write_all(
|
|
br#"// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; https://www.gnu.org/licenses/agpl.html
|
|
|
|
import type { PlainMessage } from "@bufbuild/protobuf";
|
|
import type { PostProtoOptions } from "../post";
|
|
"#,
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn write_imports(referenced_packages: HashSet<String>, out: &mut impl Write) -> Result<()> {
|
|
for package in referenced_packages {
|
|
writeln!(
|
|
out,
|
|
"import * as {} from \"./{}_pb\";",
|
|
package,
|
|
package.to_snake_case()
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn write_dts_method(
|
|
MethodDetails {
|
|
method_name,
|
|
input_type,
|
|
output_type,
|
|
comments,
|
|
}: &MethodDetails,
|
|
out: &mut String,
|
|
) -> Result<()> {
|
|
let comments = format_comments(comments);
|
|
writeln!(
|
|
out,
|
|
r#"{comments}export declare function {method_name}(input: PlainMessage<{input_type}>, options?: PostProtoOptions): Promise<{output_type}>;"#
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn write_js_file(root: &Path, service_name: &str, service: &BackendService) -> Result<()> {
|
|
let output_path = root.join(format!("{service_name}_service.js"));
|
|
let mut out = BufWriter::new(create_file(output_path)?);
|
|
write_js_header(&mut out)?;
|
|
|
|
let mut referenced_packages = HashSet::new();
|
|
let mut method_text = String::new();
|
|
for method in service.all_methods() {
|
|
let method = MethodDetails::from_method(method);
|
|
record_referenced_type(&mut referenced_packages, &method.input_type)?;
|
|
record_referenced_type(&mut referenced_packages, &method.output_type)?;
|
|
write_js_method(&method, &mut method_text)?;
|
|
}
|
|
|
|
write_imports(referenced_packages, &mut out)?;
|
|
write!(out, "{}", method_text)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn write_js_header(out: &mut impl std::io::Write) -> Result<()> {
|
|
out.write_all(
|
|
br#"// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; https://www.gnu.org/licenses/agpl.html
|
|
|
|
import { postProto } from "../post";
|
|
"#,
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn write_js_method(
|
|
MethodDetails {
|
|
method_name,
|
|
input_type,
|
|
output_type,
|
|
..
|
|
}: &MethodDetails,
|
|
out: &mut String,
|
|
) -> Result<()> {
|
|
write!(
|
|
out,
|
|
r#"export async function {method_name}(input, options = {{}}) {{
|
|
return await postProto("{method_name}", new {input_type}(input), {output_type}, options);
|
|
}}
|
|
"#
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn format_comments(comments: &Option<String>) -> String {
|
|
comments
|
|
.as_ref()
|
|
.map(|s| format!("/** {s} */\n"))
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
struct MethodDetails {
|
|
method_name: String,
|
|
input_type: String,
|
|
output_type: String,
|
|
comments: Option<String>,
|
|
}
|
|
|
|
impl MethodDetails {
|
|
fn from_method(method: &Method) -> MethodDetails {
|
|
let name = method.name.to_camel_case();
|
|
let input_type = full_name_to_imported_reference(method.proto.input().full_name());
|
|
let output_type = full_name_to_imported_reference(method.proto.output().full_name());
|
|
let comments = method.comments.clone();
|
|
Self {
|
|
method_name: name,
|
|
input_type,
|
|
output_type,
|
|
comments,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn record_referenced_type(
|
|
referenced_packages: &mut HashSet<String>,
|
|
type_name: &str,
|
|
) -> Result<()> {
|
|
referenced_packages.insert(type_name.split('.').next().unwrap().to_string());
|
|
Ok(())
|
|
}
|
|
|
|
// e.g. anki.import_export.ImportResponse ->
|
|
// importExport.ImportResponse
|
|
fn full_name_to_imported_reference(name: &str) -> String {
|
|
let mut name = name.splitn(3, '.');
|
|
name.next().unwrap();
|
|
format!(
|
|
"{}.{}",
|
|
name.next().unwrap().to_camel_case(),
|
|
name.next().unwrap()
|
|
)
|
|
}
|