2
1
Fork 0

feat: signal support

also ran formatter
This commit is contained in:
outfoxxed 2024-02-18 17:38:55 -08:00
parent 0981ac7345
commit 3fc1aa19fd
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
6 changed files with 392 additions and 149 deletions

View file

@ -98,6 +98,21 @@
</ul> </ul>
{{- end -}} {{- end -}}
{{- if $type.signals -}}
<h4>Signals</h4>
<ul>
{{- range $signame, $sig := $type.signals -}}
<li>
<span class="typegray">
<a href="#signal.{{ $signame }}">{{ $signame }}</a>(
{{- partial "qmlparams.html" $sig.params -}}
)
</span>
</li>
{{- end -}}
</ul>
{{- end -}}
{{- if $type.variants -}} {{- if $type.variants -}}
<h4>Variants</h4> <h4>Variants</h4>
<ul> <ul>
@ -186,6 +201,34 @@
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- if $type.signals -}}
<h3>Signal Details</h3>
{{ range $signame, $sig := $type.signals }}
<div id="signal.{{ $signame }}" class = "qmlpropdef">
{{- if $sig.flags -}}
<span class="qmlprops typegray">
{{ partial "qmltypeflags.html" $sig.flags }}
</span>
{{- end -}}
<p>
{{ $signame -}}
<span class="typegray">(
{{- partial "qmlparams.html" $sig.params -}}
)</span>
</p>
</div>
<div class="qmlpropdetails">
{{- if $sig.details -}}
{{- $sig.details | $.Page.RenderString (dict "display" "block") -}}
{{- else -}}
<p style="color: #999999"><i>No details provided.</i></p>
{{- end -}}
</div>
{{- end -}}
{{- end -}}
{{- if $type.variants -}} {{- if $type.variants -}}
<h3>Variant Details</h3> <h3>Variant Details</h3>
{{ range $name, $variant := $type.variants }} {{ range $name, $variant := $type.variants }}

View file

@ -2,10 +2,10 @@ use std::{collections::HashMap, path::Path};
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
mod parse;
mod typespec;
mod outform; mod outform;
mod parse;
mod resolver; mod resolver;
mod typespec;
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let args = std::env::args().collect::<Vec<_>>(); let args = std::env::args().collect::<Vec<_>>();
@ -19,10 +19,17 @@ fn main() -> anyhow::Result<()> {
let text = std::fs::read_to_string(path).expect("failed to read module file"); let text = std::fs::read_to_string(path).expect("failed to read module file");
let module = parse::parse_module(&text)?; let module = parse::parse_module(&text)?;
let texts = module.header.headers.iter() let texts = module
.header
.headers
.iter()
.map(|header| { .map(|header| {
let text = std::fs::read_to_string(dir.join(header)) let text = std::fs::read_to_string(dir.join(header)).with_context(|| {
.with_context(|| format!("failed to read module header `{header}` at {:?}", dir.join(header)))?; format!(
"failed to read module header `{header}` at {:?}",
dir.join(header)
)
})?;
Ok::<_, anyhow::Error>((header, text)) Ok::<_, anyhow::Error>((header, text))
}) })
@ -30,9 +37,11 @@ fn main() -> anyhow::Result<()> {
let parser = parse::Parser::new(); let parser = parse::Parser::new();
let mut ctx = parse::ParseContext::default(); let mut ctx = parse::ParseContext::default();
texts.iter() texts
.iter()
.map(|(header, text)| { .map(|(header, text)| {
parser.parse(&text, &mut ctx) parser
.parse(&text, &mut ctx)
.with_context(|| format!("while parsing module header `{header}`")) .with_context(|| format!("while parsing module header `{header}`"))
}) })
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
@ -41,8 +50,7 @@ fn main() -> anyhow::Result<()> {
let text = serde_json::to_string_pretty(&typespec).unwrap(); let text = serde_json::to_string_pretty(&typespec).unwrap();
std::fs::write(outpath, text) std::fs::write(outpath, text).context("saving typespec")?;
.context("saving typespec")?;
}, },
Some("gendocs") => { Some("gendocs") => {
let modinfo = args.get(2).expect("expected module file"); let modinfo = args.get(2).expect("expected module file");
@ -77,18 +85,24 @@ fn main() -> anyhow::Result<()> {
for (name, info) in types { for (name, info) in types {
let json = serde_json::to_string_pretty(&info).unwrap(); let json = serde_json::to_string_pretty(&info).unwrap();
let datapath = datapath.join(format!("{name}.json")); let datapath = datapath.join(format!("{name}.json"));
std::fs::write(&datapath, json).with_context(|| format!("while writing {datapath:?}"))?; std::fs::write(&datapath, json)
.with_context(|| format!("while writing {datapath:?}"))?;
let template = format!("+++ let template = format!(
"+++
title = \"{name}\" title = \"{name}\"
hidetitle = true hidetitle = true
+++ +++
{{{{< qmltype module=\"{module}\" type=\"{name}\" >}}}} {{{{< qmltype module=\"{module}\" type=\"{name}\" >}}}}
", name = name, module = module.header.name); ",
name = name,
module = module.header.name
);
let templatepath = templatepath.join(format!("{name}.md")); let templatepath = templatepath.join(format!("{name}.md"));
std::fs::write(&templatepath, template).with_context(|| format!("while writing {templatepath:?}"))?; std::fs::write(&templatepath, template)
.with_context(|| format!("while writing {templatepath:?}"))?;
} }
let index = outform::ModuleIndex { let index = outform::ModuleIndex {
@ -98,17 +112,22 @@ hidetitle = true
let datapath = datapath.join("index.json"); let datapath = datapath.join("index.json");
let json = serde_json::to_string_pretty(&index).unwrap(); let json = serde_json::to_string_pretty(&index).unwrap();
std::fs::write(&datapath, json).with_context(|| format!("while writing {datapath:?}"))?; std::fs::write(&datapath, json)
.with_context(|| format!("while writing {datapath:?}"))?;
let template = format!("+++ let template = format!(
"+++
title = \"{name}\" title = \"{name}\"
+++ +++
{{{{< qmlmodule module=\"{name}\" >}}}} {{{{< qmlmodule module=\"{name}\" >}}}}
", name = module.header.name); ",
name = module.header.name
);
let templatepath = templatepath.join(format!("_index.md")); let templatepath = templatepath.join(format!("_index.md"));
std::fs::write(&templatepath, template).with_context(|| format!("while writing {templatepath:?}"))?; std::fs::write(&templatepath, template)
.with_context(|| format!("while writing {templatepath:?}"))?;
}, },
_ => { _ => {
panic!("typegen invoked without mode"); panic!("typegen invoked without mode");

View file

@ -26,6 +26,7 @@ pub struct ClassInfo {
pub flags: Vec<Flag>, pub flags: Vec<Flag>,
pub properties: HashMap<String, Property>, pub properties: HashMap<String, Property>,
pub functions: HashMap<String, Function>, pub functions: HashMap<String, Function>,
pub signals: HashMap<String, Signal>,
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
@ -53,6 +54,13 @@ pub struct Function {
pub params: HashMap<String, Type>, pub params: HashMap<String, Type>,
} }
#[derive(Debug, Serialize)]
pub struct Signal {
pub name: String,
pub details: Option<String>,
pub params: HashMap<String, Type>,
}
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct EnumInfo { pub struct EnumInfo {
pub description: Option<String>, pub description: Option<String>,

View file

@ -27,13 +27,10 @@ pub fn parse_module(text: &str) -> anyhow::Result<ModuleInfo> {
header = header.trim(); header = header.trim();
details = details.trim(); details = details.trim();
let header = toml::from_str::<ModuleInfoHeader>(header) let header =
.context("parsing module info header")?; toml::from_str::<ModuleInfoHeader>(header).context("parsing module info header")?;
Ok(ModuleInfo { Ok(ModuleInfo { header, details })
header,
details,
})
} }
#[derive(Debug)] #[derive(Debug)]
@ -47,6 +44,7 @@ pub struct ClassInfo<'a> {
pub comment: Option<&'a str>, pub comment: Option<&'a str>,
pub properties: Vec<Property<'a>>, pub properties: Vec<Property<'a>>,
pub invokables: Vec<Invokable<'a>>, pub invokables: Vec<Invokable<'a>>,
pub signals: Vec<Signal<'a>>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -73,6 +71,13 @@ pub struct Invokable<'a> {
pub params: Vec<InvokableParam<'a>>, pub params: Vec<InvokableParam<'a>>,
} }
#[derive(Debug, Clone)]
pub struct Signal<'a> {
pub name: &'a str,
pub comment: Option<&'a str>,
pub params: Vec<InvokableParam<'a>>,
}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct InvokableParam<'a> { pub struct InvokableParam<'a> {
pub name: &'a str, pub name: &'a str,
@ -98,6 +103,7 @@ pub struct Parser {
pub class_regex: Regex, pub class_regex: Regex,
pub macro_regex: Regex, pub macro_regex: Regex,
pub property_regex: Regex, pub property_regex: Regex,
pub signals_regex: Regex,
pub fn_regex: Regex, pub fn_regex: Regex,
pub fn_param_regex: Regex, pub fn_param_regex: Regex,
pub defaultprop_classinfo_regex: Regex, pub defaultprop_classinfo_regex: Regex,
@ -126,15 +132,20 @@ impl Parser {
class_regex: Regex::new(r#"(?<comment>(\s*\/\/\/.*\n)+)?\s*class\s+(?<name>\w+)(?:\s*:\s*public\s+(?<super>\w+))?.+?\{(?<body>[\s\S]*?)};"#).unwrap(), class_regex: Regex::new(r#"(?<comment>(\s*\/\/\/.*\n)+)?\s*class\s+(?<name>\w+)(?:\s*:\s*public\s+(?<super>\w+))?.+?\{(?<body>[\s\S]*?)};"#).unwrap(),
macro_regex: Regex::new(r#"(?<comment>(\s*\/\/\/.*\n)+)?\s*(?<type>(Q|QML)_\w+)\s*(\(\s*(?<args>.*)\s*\))?;"#).unwrap(), macro_regex: Regex::new(r#"(?<comment>(\s*\/\/\/.*\n)+)?\s*(?<type>(Q|QML)_\w+)\s*(\(\s*(?<args>.*)\s*\))?;"#).unwrap(),
property_regex: Regex::new(r#"^\s*(?<type>(\w|::|<|>)+\*?)\s+(?<name>\w+)(\s+(MEMBER\s+(?<member>\w+)|READ\s+(?<read>\w+)|WRITE\s+(?<write>\w+)|NOTIFY\s+(?<notify>\w+)|(?<const>CONSTANT)))+\s*$"#).unwrap(), property_regex: Regex::new(r#"^\s*(?<type>(\w|::|<|>)+\*?)\s+(?<name>\w+)(\s+(MEMBER\s+(?<member>\w+)|READ\s+(?<read>\w+)|WRITE\s+(?<write>\w+)|NOTIFY\s+(?<notify>\w+)|(?<const>CONSTANT)))+\s*$"#).unwrap(),
fn_regex: Regex::new(r#"(?<comment>(\s*\/\/\/.*\n)+)?\s*Q_INVOKABLE\s+(?<type>(\w|::|<|>)+\*?)\s+(?<name>\w+)\((?<params>[\s\S]*?)\);"#).unwrap(), fn_regex: Regex::new(r#"(?<comment>(\s*\/\/\/.*\n)+)?\s*(?<invokable>Q_INVOKABLE)?\s+(?<type>(\w|::|<|>)+\*?)\s+(?<name>\w+)\((?<params>[\s\S]*?)\);"#).unwrap(),
fn_param_regex: Regex::new(r#"(?<type>(\w|::|<|>)+\*?)\s+(?<name>\w+)(,|$)"#).unwrap(), fn_param_regex: Regex::new(r#"(?<type>(\w|::|<|>)+\*?)\s+(?<name>\w+)(,|$)"#).unwrap(),
signals_regex: Regex::new(r#"signals:(?<signals>(\s*(\s*///.*\s*)*void .*;)*)"#).unwrap(),
defaultprop_classinfo_regex: Regex::new(r#"^\s*"DefaultProperty", "(?<prop>.+)"\s*$"#).unwrap(), defaultprop_classinfo_regex: Regex::new(r#"^\s*"DefaultProperty", "(?<prop>.+)"\s*$"#).unwrap(),
enum_regex: Regex::new(r#"(?<comment>(\s*\/\/\/.*\n)+)?\s*namespace (?<namespace>\w+)\s*\{[\s\S]*?(QML_ELEMENT|QML_NAMED_ELEMENT\((?<qml_name>\w+)\));[\s\S]*?enum\s*(?<enum_name>\w+)\s*\{(?<body>[\s\S]*?)\};[\s\S]*?\}"#).unwrap(), enum_regex: Regex::new(r#"(?<comment>(\s*\/\/\/.*\n)+)?\s*namespace (?<namespace>\w+)\s*\{[\s\S]*?(QML_ELEMENT|QML_NAMED_ELEMENT\((?<qml_name>\w+)\));[\s\S]*?enum\s*(?<enum_name>\w+)\s*\{(?<body>[\s\S]*?)\};[\s\S]*?\}"#).unwrap(),
enum_variant_regex: Regex::new(r#"(?<comment>(\s*\/\/\/.*\n)+)?\s*(?<name>\w+)\s*=\s*\d+,"#).unwrap(), enum_variant_regex: Regex::new(r#"(?<comment>(\s*\/\/\/.*\n)+)?\s*(?<name>\w+)\s*=\s*\d+,"#).unwrap(),
} }
} }
pub fn parse_classes<'a>(&self, text: &'a str, ctx: &mut ParseContext<'a>) -> anyhow::Result<()> { pub fn parse_classes<'a>(
&self,
text: &'a str,
ctx: &mut ParseContext<'a>,
) -> anyhow::Result<()> {
for class in self.class_regex.captures_iter(text) { for class in self.class_regex.captures_iter(text) {
let comment = class.name("comment").map(|m| m.as_str()); let comment = class.name("comment").map(|m| m.as_str());
let name = class.name("name").unwrap().as_str(); let name = class.name("name").unwrap().as_str();
@ -148,6 +159,8 @@ impl Parser {
let mut properties = Vec::new(); let mut properties = Vec::new();
let mut default_property = None; let mut default_property = None;
let mut invokables = Vec::new(); let mut invokables = Vec::new();
let mut notify_signals = Vec::new();
let mut signals = Vec::new();
(|| { (|| {
for macro_ in self.macro_regex.captures_iter(body) { for macro_ in self.macro_regex.captures_iter(body) {
@ -160,11 +173,19 @@ impl Parser {
"Q_OBJECT" => classtype = Some(ClassType::Object), "Q_OBJECT" => classtype = Some(ClassType::Object),
"Q_GADGET" => classtype = Some(ClassType::Gadget), "Q_GADGET" => classtype = Some(ClassType::Gadget),
"QML_ELEMENT" => qml_name = Some(name), "QML_ELEMENT" => qml_name = Some(name),
"QML_NAMED_ELEMENT" => qml_name = Some(args.ok_or_else(|| anyhow!("expected name for QML_NAMED_ELEMENT"))?), "QML_NAMED_ELEMENT" => {
qml_name = Some(args.ok_or_else(|| {
anyhow!("expected name for QML_NAMED_ELEMENT")
})?)
},
"QML_SINGLETON" => singleton = true, "QML_SINGLETON" => singleton = true,
"QML_UNCREATABLE" => uncreatable = true, "QML_UNCREATABLE" => uncreatable = true,
"Q_PROPERTY" => { "Q_PROPERTY" => {
let prop = self.property_regex.captures(args.ok_or_else(|| anyhow!("expected args for Q_PROPERTY"))?) let prop =
self.property_regex
.captures(args.ok_or_else(|| {
anyhow!("expected args for Q_PROPERTY")
})?)
.ok_or_else(|| anyhow!("unable to parse Q_PROPERTY"))?; .ok_or_else(|| anyhow!("unable to parse Q_PROPERTY"))?;
let member = prop.name("member").is_some(); let member = prop.name("member").is_some();
@ -172,6 +193,10 @@ impl Parser {
let write = prop.name("write").is_some(); let write = prop.name("write").is_some();
let constant = prop.name("const").is_some(); let constant = prop.name("const").is_some();
if let Some(notify) = prop.name("notify").map(|v| v.as_str()) {
notify_signals.push(notify);
}
properties.push(Property { properties.push(Property {
type_: prop.name("type").unwrap().as_str(), type_: prop.name("type").unwrap().as_str(),
name: prop.name("name").unwrap().as_str(), name: prop.name("name").unwrap().as_str(),
@ -182,7 +207,9 @@ impl Parser {
}); });
}, },
"Q_CLASSINFO" => { "Q_CLASSINFO" => {
let classinfo = self.defaultprop_classinfo_regex.captures(args.ok_or_else(|| anyhow!("expected args for Q_CLASSINFO"))?); let classinfo = self.defaultprop_classinfo_regex.captures(
args.ok_or_else(|| anyhow!("expected args for Q_CLASSINFO"))?,
);
if let Some(classinfo) = classinfo { if let Some(classinfo) = classinfo {
let prop = classinfo.name("prop").unwrap().as_str(); let prop = classinfo.name("prop").unwrap().as_str();
@ -192,10 +219,26 @@ impl Parser {
_ => {}, _ => {},
} }
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
})().with_context(|| format!("while parsing macro `{}`", macro_.get(0).unwrap().as_str()))?; })()
.with_context(|| {
format!("while parsing macro `{}`", macro_.get(0).unwrap().as_str())
})?;
}
if let Some(prop) = default_property {
let prop = properties
.iter_mut()
.find(|p| p.name == prop)
.ok_or_else(|| anyhow!("could not find default property `{prop}`"))?;
prop.default = true;
} }
for invokable in self.fn_regex.captures_iter(body) { for invokable in self.fn_regex.captures_iter(body) {
if invokable.name("invokable").is_none() {
continue;
}
let comment = invokable.name("comment").map(|m| m.as_str()); let comment = invokable.name("comment").map(|m| m.as_str());
let type_ = invokable.name("type").unwrap().as_str(); let type_ = invokable.name("type").unwrap().as_str();
let name = invokable.name("name").unwrap().as_str(); let name = invokable.name("name").unwrap().as_str();
@ -207,10 +250,7 @@ impl Parser {
let type_ = param.name("type").unwrap().as_str(); let type_ = param.name("type").unwrap().as_str();
let name = param.name("name").unwrap().as_str(); let name = param.name("name").unwrap().as_str();
params.push(InvokableParam { params.push(InvokableParam { type_, name });
type_,
name,
});
} }
invokables.push(Invokable { invokables.push(Invokable {
@ -221,15 +261,47 @@ impl Parser {
}); });
} }
if let Some(prop) = default_property { for signal_set in self.signals_regex.captures_iter(body) {
let prop = properties.iter_mut().find(|p| p.name == prop) let signals_body = signal_set.name("signals").unwrap().as_str();
.ok_or_else(|| anyhow!("could not find default property `{prop}`"))?;
prop.default = true; for signal in self.fn_regex.captures_iter(signals_body) {
if signal.name("invokable").is_some() {
continue;
}
let comment = signal.name("comment").map(|m| m.as_str());
let type_ = signal.name("type").unwrap().as_str();
let name = signal.name("name").unwrap().as_str();
let params_raw = signal.name("params").unwrap().as_str();
if notify_signals.contains(&name) {
continue;
}
if type_ != "void" {
bail!("non void return for signal {name}");
}
let mut params = Vec::new();
for param in self.fn_param_regex.captures_iter(params_raw) {
let type_ = param.name("type").unwrap().as_str();
let name = param.name("name").unwrap().as_str();
params.push(InvokableParam { type_, name });
}
signals.push(Signal {
name,
comment,
params,
});
}
} }
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
})().with_context(|| format!("while parsing class `{name}`"))?; })()
.with_context(|| format!("while parsing class `{name}`"))?;
let Some(type_) = classtype else { continue }; let Some(type_) = classtype else { continue };
@ -243,6 +315,7 @@ impl Parser {
comment, comment,
properties, properties,
invokables, invokables,
signals,
}); });
} }
@ -254,7 +327,10 @@ impl Parser {
let comment = enum_.name("comment").map(|m| m.as_str()); let comment = enum_.name("comment").map(|m| m.as_str());
let namespace = enum_.name("namespace").unwrap().as_str(); let namespace = enum_.name("namespace").unwrap().as_str();
let enum_name = enum_.name("enum_name").unwrap().as_str(); let enum_name = enum_.name("enum_name").unwrap().as_str();
let qml_name = enum_.name("qml_name").map(|m| m.as_str()).unwrap_or(namespace); let qml_name = enum_
.name("qml_name")
.map(|m| m.as_str())
.unwrap_or(namespace);
let body = enum_.name("body").unwrap().as_str(); let body = enum_.name("body").unwrap().as_str();
let mut variants = Vec::new(); let mut variants = Vec::new();
@ -263,10 +339,7 @@ impl Parser {
let comment = variant.name("comment").map(|m| m.as_str()); let comment = variant.name("comment").map(|m| m.as_str());
let name = variant.name("name").unwrap().as_str(); let name = variant.name("name").unwrap().as_str();
variants.push(Variant { variants.push(Variant { name, comment });
name,
comment,
});
} }
ctx.enums.push(EnumInfo { ctx.enums.push(EnumInfo {
@ -292,16 +365,25 @@ impl Parser {
impl ParseContext<'_> { impl ParseContext<'_> {
pub fn gen_typespec(&self, module: &str) -> typespec::TypeSpec { pub fn gen_typespec(&self, module: &str) -> typespec::TypeSpec {
typespec::TypeSpec { typespec::TypeSpec {
typemap: self.classes.iter().filter_map(|class| { typemap: self
.classes
.iter()
.filter_map(|class| {
Some(typespec::QmlTypeMapping { Some(typespec::QmlTypeMapping {
// filters gadgets // filters gadgets
name: class.qml_name?.to_string(), name: class.qml_name?.to_string(),
cname: class.name.to_string(), cname: class.name.to_string(),
module: Some(module.to_string()), module: Some(module.to_string()),
}) })
}).collect(), })
classes: self.classes.iter().filter_map(|class| { .collect(),
let (description, details) = class.comment.map(parse_details_desc) classes: self
.classes
.iter()
.filter_map(|class| {
let (description, details) = class
.comment
.map(parse_details_desc)
.unwrap_or((None, None)); .unwrap_or((None, None));
Some(typespec::Class { Some(typespec::Class {
@ -315,17 +397,28 @@ impl ParseContext<'_> {
uncreatable: class.uncreatable, uncreatable: class.uncreatable,
properties: class.properties.iter().map(|p| (*p).into()).collect(), properties: class.properties.iter().map(|p| (*p).into()).collect(),
functions: class.invokables.iter().map(|f| f.as_typespec()).collect(), functions: class.invokables.iter().map(|f| f.as_typespec()).collect(),
signals: class.signals.iter().map(|s| s.as_typespec()).collect(),
}) })
}).collect(), })
gadgets: self.classes.iter().filter_map(|class| match class.type_ { .collect(),
gadgets: self
.classes
.iter()
.filter_map(|class| match class.type_ {
ClassType::Gadget => Some(typespec::Gadget { ClassType::Gadget => Some(typespec::Gadget {
cname: class.name.to_string(), cname: class.name.to_string(),
properties: class.properties.iter().map(|p| (*p).into()).collect(), properties: class.properties.iter().map(|p| (*p).into()).collect(),
}), }),
_ => None, _ => None,
}).collect(), })
enums: self.enums.iter().map(|enum_| { .collect(),
let (description, details) = enum_.comment.map(parse_details_desc) enums: self
.enums
.iter()
.map(|enum_| {
let (description, details) = enum_
.comment
.map(parse_details_desc)
.unwrap_or((None, None)); .unwrap_or((None, None));
typespec::Enum { typespec::Enum {
@ -336,7 +429,8 @@ impl ParseContext<'_> {
details, details,
varaints: enum_.variants.iter().map(|v| (*v).into()).collect(), varaints: enum_.variants.iter().map(|v| (*v).into()).collect(),
} }
}).collect(), })
.collect(),
} }
} }
} }
@ -374,6 +468,16 @@ impl Invokable<'_> {
} }
} }
impl Signal<'_> {
fn as_typespec(&self) -> typespec::Signal {
typespec::Signal {
name: self.name.to_string(),
details: self.comment.map(parse_details),
params: self.params.iter().map(|p| (*p).into()).collect(),
}
}
}
impl From<InvokableParam<'_>> for typespec::FnParam { impl From<InvokableParam<'_>> for typespec::FnParam {
fn from(value: InvokableParam<'_>) -> Self { fn from(value: InvokableParam<'_>) -> Self {
Self { Self {
@ -387,7 +491,8 @@ fn parse_details(text: &str) -> String {
let mut seen_content = false; let mut seen_content = false;
let mut callout = false; let mut callout = false;
let mut str = text.lines() let mut str = text
.lines()
.map(|line| { .map(|line| {
line.trim() line.trim()
.strip_prefix("///") .strip_prefix("///")
@ -400,8 +505,7 @@ fn parse_details(text: &str) -> String {
seen_content |= any; seen_content |= any;
filter filter
}) })
.map(|line| { .map(|line| match callout {
match callout {
true => { true => {
if line.starts_with('>') { if line.starts_with('>') {
Cow::Borrowed(line[1..].strip_prefix(' ').unwrap_or(&line[1..])) Cow::Borrowed(line[1..].strip_prefix(' ').unwrap_or(&line[1..]))
@ -409,7 +513,7 @@ fn parse_details(text: &str) -> String {
callout = false; callout = false;
Cow::Owned(format!("{{{{< /callout >}}}}\n{line}")) Cow::Owned(format!("{{{{< /callout >}}}}\n{line}"))
} }
} },
false => { false => {
if line.starts_with("> [!") { if line.starts_with("> [!") {
let code = line[4..].split_once(']'); let code = line[4..].split_once(']');
@ -422,8 +526,7 @@ fn parse_details(text: &str) -> String {
} }
return Cow::Borrowed(line); return Cow::Borrowed(line);
} },
}
}) })
.fold(String::new(), |accum, line| accum + line.as_ref() + "\n"); .fold(String::new(), |accum, line| accum + line.as_ref() + "\n");
@ -438,8 +541,19 @@ fn parse_details_desc(text: &str) -> (Option<String>, Option<String>) {
let details = parse_details(text); let details = parse_details(text);
if details.starts_with('!') { if details.starts_with('!') {
match details[1..].split_once('\n') { match details[1..].split_once('\n') {
Some((desc, details)) => (Some(desc.strip_prefix(' ').unwrap_or(desc).to_string()), Some(details.to_string())), Some((desc, details)) => (
None => (Some(details[1..].strip_prefix(' ').unwrap_or(&details[1..]).to_string()), None), Some(desc.strip_prefix(' ').unwrap_or(desc).to_string()),
Some(details.to_string()),
),
None => (
Some(
details[1..]
.strip_prefix(' ')
.unwrap_or(&details[1..])
.to_string(),
),
None,
),
} }
} else { } else {
(None, Some(details)) (None, Some(details))

View file

@ -1,24 +1,35 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::{outform::{self, Flag, PropertyType}, typespec::{FnParam, Function, Property, TypeSpec}}; use crate::{
outform::{self, Flag, PropertyType},
typespec::{FnParam, Function, Property, Signal, TypeSpec},
};
pub fn resolve_types(module: &str, typespec: TypeSpec) -> anyhow::Result<HashMap<String, outform::TypeInfo>> { pub fn resolve_types(
module: &str,
typespec: TypeSpec,
) -> anyhow::Result<HashMap<String, outform::TypeInfo>> {
let mut outtypes = HashMap::new(); let mut outtypes = HashMap::new();
let types = typespec.typemap.iter() let types = typespec
.typemap
.iter()
.filter(|type_| type_.module.as_ref().map(|v| v as &str) == Some(module)); .filter(|type_| type_.module.as_ref().map(|v| v as &str) == Some(module));
let findqmltype = |cname: &str| typespec.typemap let findqmltype = |cname: &str| typespec.typemap.iter().find(|type_| type_.cname == cname);
.iter()
.find(|type_| type_.cname == cname);
for mapping in types { for mapping in types {
let Some(class) = typespec.classes.iter().find(|class| class.name == mapping.cname) else { let Some(class) = typespec
.classes
.iter()
.find(|class| class.name == mapping.cname)
else {
continue continue
}; };
let mut properties = Vec::<&Property>::new(); let mut properties = Vec::<&Property>::new();
let mut functions = Vec::<&Function>::new(); let mut functions = Vec::<&Function>::new();
let mut signals = Vec::<&Signal>::new();
// the first superclass availible from QML // the first superclass availible from QML
let mut superclass = &class.superclass; let mut superclass = &class.superclass;
@ -29,12 +40,16 @@ pub fn resolve_types(module: &str, typespec: TypeSpec) -> anyhow::Result<HashMap
break outform::Type::resolve(type_.module.as_ref().map(|v| v as &str), &type_.name) break outform::Type::resolve(type_.module.as_ref().map(|v| v as &str), &type_.name)
} }
let superctype = typespec.classes.iter().find(|class| &class.name == superclass); let superctype = typespec
.classes
.iter()
.find(|class| &class.name == superclass);
match superctype { match superctype {
Some(superctype) => { Some(superctype) => {
properties.extend(superctype.properties.iter()); properties.extend(superctype.properties.iter());
functions.extend(superctype.functions.iter()); functions.extend(superctype.functions.iter());
signals.extend(superctype.signals.iter());
superclass = &superctype.superclass; superclass = &superctype.superclass;
}, },
None => break outform::Type::unknown(), None => break outform::Type::unknown(),
@ -42,19 +57,23 @@ pub fn resolve_types(module: &str, typespec: TypeSpec) -> anyhow::Result<HashMap
}; };
fn qmlparamtype(ctype: &str, typespec: &TypeSpec) -> outform::Type { fn qmlparamtype(ctype: &str, typespec: &TypeSpec) -> outform::Type {
let qtype = typespec.typemap let qtype = typespec
.typemap
.iter() .iter()
.find(|type_| &type_.cname == ctype) .find(|type_| &type_.cname == ctype)
.map(|type_| (&type_.module, &type_.name)) .map(|type_| (&type_.module, &type_.name))
.or_else(|| { .or_else(|| {
typespec.enums typespec
.enums
.iter() .iter()
.find(|type_| type_.cname.as_ref().map(|v| v as &str) == Some(ctype)) .find(|type_| type_.cname.as_ref().map(|v| v as &str) == Some(ctype))
.map(|type_| (&type_.module, &type_.name)) .map(|type_| (&type_.module, &type_.name))
}); });
match qtype { match qtype {
Some((module, name)) => outform::Type::resolve(module.as_ref().map(|v| v as &str), &name), Some((module, name)) => {
outform::Type::resolve(module.as_ref().map(|v| v as &str), &name)
},
None => outform::Type::unknown(), None => outform::Type::unknown(),
} }
} }
@ -78,15 +97,16 @@ pub fn resolve_types(module: &str, typespec: TypeSpec) -> anyhow::Result<HashMap
flags flags
}; };
let gadget = typespec.gadgets.iter() let gadget = typespec.gadgets.iter().find(|gadget| gadget.cname == ctype);
.find(|gadget| gadget.cname == ctype);
match gadget { match gadget {
Some(gadget) => outform::Property { Some(gadget) => outform::Property {
type_: PropertyType::Gadget( type_: PropertyType::Gadget(
gadget.properties.iter() gadget
.properties
.iter()
.map(|prop| (prop.name.clone(), solveprop(prop, typespec).type_)) .map(|prop| (prop.name.clone(), solveprop(prop, typespec).type_))
.collect() .collect(),
), ),
details: prop.details.clone(), details: prop.details.clone(),
flags, flags,
@ -131,7 +151,23 @@ pub fn resolve_types(module: &str, typespec: TypeSpec) -> anyhow::Result<HashMap
ret: qmlparamtype(&func.ret, typespec), ret: qmlparamtype(&func.ret, typespec),
name: func.name.clone(), name: func.name.clone(),
details: func.details.clone(), details: func.details.clone(),
params: func.params.iter().map(|FnParam { type_, name }| (name.clone(), qmlparamtype(type_, typespec))).collect(), params: func
.params
.iter()
.map(|FnParam { type_, name }| (name.clone(), qmlparamtype(type_, typespec)))
.collect(),
}
}
fn solvesignal(func: &Signal, typespec: &TypeSpec) -> outform::Signal {
outform::Signal {
name: func.name.clone(),
details: func.details.clone(),
params: func
.params
.iter()
.map(|FnParam { type_, name }| (name.clone(), qmlparamtype(type_, typespec)))
.collect(),
} }
} }
@ -141,15 +177,23 @@ pub fn resolve_types(module: &str, typespec: TypeSpec) -> anyhow::Result<HashMap
functions.extend(class.functions.iter()); functions.extend(class.functions.iter());
functions.sort_by(|a, b| Ord::cmp(&a.name, &b.name)); functions.sort_by(|a, b| Ord::cmp(&a.name, &b.name));
let properties = properties.iter().map(|prop| ( signals.extend(class.signals.iter());
prop.name.clone(), signals.sort_by(|a, b| Ord::cmp(&a.name, &b.name));
solveprop(prop, &typespec)
)).collect::<HashMap<_, _>>();
let functions = functions.iter().map(|func| ( let properties = properties
func.name.clone(), .iter()
solvefunc(func, &typespec) .map(|prop| (prop.name.clone(), solveprop(prop, &typespec)))
)).collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>();
let functions = functions
.iter()
.map(|func| (func.name.clone(), solvefunc(func, &typespec)))
.collect::<HashMap<_, _>>();
let signals = signals
.iter()
.map(|signal| (signal.name.clone(), solvesignal(signal, &typespec)))
.collect::<HashMap<_, _>>();
let type_ = outform::ClassInfo { let type_ = outform::ClassInfo {
superclass, superclass,
@ -168,6 +212,7 @@ pub fn resolve_types(module: &str, typespec: TypeSpec) -> anyhow::Result<HashMap
}, },
properties, properties,
functions, functions,
signals,
}; };
outtypes.insert(mapping.name.clone(), outform::TypeInfo::Class(type_)); outtypes.insert(mapping.name.clone(), outform::TypeInfo::Class(type_));
@ -175,16 +220,22 @@ pub fn resolve_types(module: &str, typespec: TypeSpec) -> anyhow::Result<HashMap
for enum_ in typespec.enums { for enum_ in typespec.enums {
if enum_.module.as_ref().map(|v| v as &str) == Some(module) { if enum_.module.as_ref().map(|v| v as &str) == Some(module) {
outtypes.insert(enum_.name, outform::TypeInfo::Enum(outform::EnumInfo { outtypes.insert(
enum_.name,
outform::TypeInfo::Enum(outform::EnumInfo {
description: enum_.description, description: enum_.description,
details: enum_.details, details: enum_.details,
variants: enum_.varaints.into_iter().map(|variant| ( variants: enum_
variant.name, .varaints
outform::Variant { .into_iter()
.map(|variant| {
(variant.name, outform::Variant {
details: variant.details, details: variant.details,
}, })
)).collect(), })
})); .collect(),
}),
);
} }
} }

View file

@ -37,6 +37,7 @@ pub struct Class {
pub uncreatable: bool, pub uncreatable: bool,
pub properties: Vec<Property>, pub properties: Vec<Property>,
pub functions: Vec<Function>, pub functions: Vec<Function>,
pub signals: Vec<Signal>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -61,7 +62,14 @@ pub struct Function {
pub ret: String, pub ret: String,
pub name: String, pub name: String,
pub details: Option<String>, pub details: Option<String>,
pub params: Vec<FnParam> pub params: Vec<FnParam>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Signal {
pub name: String,
pub details: Option<String>,
pub params: Vec<FnParam>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]