commit 27b3274027251ebf382e31546ef2b350ae2f7b0e Author: outfoxxed Date: Mon Feb 12 04:07:01 2024 -0800 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3afa476 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# direnv +/.envrc +/.direnv + +# typegen +/typegen/target +/build +/content/docs/types/QuickShell +/data/modules + +# hugo +/.hugo_build.lock diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..dd62212 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "themes/hextra"] + path = themes/hextra + url = https://github.com/imfing/hextra.git diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..77362de --- /dev/null +++ b/Justfile @@ -0,0 +1,13 @@ +clean: + rm -rf build + rm -rf data/modules/QuickShell + rm -rf content/docs/types/QuickShell + +typedocs: clean + cd typegen && cargo build + mkdir -p build/types/types + ./typegen/target/debug/typegen gentypes ../src/cpp/module.md build/types/types/QuickShell.json + sh -c './typegen/target/debug/typegen gendocs ../src/cpp/module.md data/modules/QuickShell content/docs/types/QuickShell types/* build/types/types/*' + +serve: typedocs + hugo server --buildDrafts --disableFastRender diff --git a/assets/css/custom.css b/assets/css/custom.css new file mode 100644 index 0000000..c8536cd --- /dev/null +++ b/assets/css/custom.css @@ -0,0 +1,47 @@ +[id] { + scroll-margin-top: var(--navbar-height); +} + +.nav-container-blur {} + +.content a:not(:hover) { + text-decoration-line: none; +} + +.hextra-scrollbar .font-semibold { + font-weight: normal; +} + +.content ul { + margin-top: 0; +} + +.typegray { + color: #999999; +} + +.qmlpropdef { + background-color: rgb(0 0 0 / .05); + font-weight: normal; + font-size: 1.2rem; + border-radius: 5px; + margin: 5px 0; + padding: 5px; +} + +:is(html[class~="dark"] .qmlpropdef) { + background-color: #55555530; +} + +.qmlpropdef > p { + padding: 0; + margin: 0; +} + +.qmlpropdetails { + margin-left: 8px; +} + +.qmlprops { + float: right; +} diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 0000000..6ffa354 --- /dev/null +++ b/content/_index.md @@ -0,0 +1,9 @@ ++++ ++++ + +# QuickShell +QuickShell is a fully user customizable desktop shell based on QtQuick. + +{{< cards >}} + {{< card link="/docs/types" title="Type Reference" >}} +{{< /cards >}} diff --git a/content/docs/_index.md b/content/docs/_index.md new file mode 100644 index 0000000..9a9ccd5 --- /dev/null +++ b/content/docs/_index.md @@ -0,0 +1,3 @@ ++++ +title = 'Docs' ++++ diff --git a/content/docs/types/_index.md b/content/docs/types/_index.md new file mode 100644 index 0000000..63230b5 --- /dev/null +++ b/content/docs/types/_index.md @@ -0,0 +1,7 @@ ++++ +title = "Type Reference" ++++ + +Index of all QuickShell types. See [the QtQuick type reference](https://doc.qt.io/qt-6/qtquick-qmlmodule.html) for builtin Qt types. + +{{< qmlmodulelisting >}} diff --git a/hugo.toml b/hugo.toml new file mode 100644 index 0000000..6ea2f68 --- /dev/null +++ b/hugo.toml @@ -0,0 +1,36 @@ +baseURL = "https://example.org/" +languageCode = "en-us" +title = "QuickShell" +theme = "hextra" + +[params.theme] +default = "dark" +displayToggle = true + +[params.search] +enable = true +type = "flexsearch" +flexsearch.index = "content" + +[markup] # required by theme to render properly +goldmark.renderer.unsafe = true +highlight.noClasses = false + +[[menu.sidebar]] +name = "QtQuick Type Reference ↗" +url = "https://doc.qt.io/qt-6/qtquick-qmlmodule.html" + +[[menu.main]] +name = "Docs" +pageRef = "/docs" +weight = 1 + +[[menu.main]] +name = "Types" +pageRef = "/docs/types" +weight = 2 + +[[menu.main]] +name = "Search" +weight = 3 +params.type = "search" diff --git a/layouts/docs/single.html b/layouts/docs/single.html new file mode 100644 index 0000000..fc5c73a --- /dev/null +++ b/layouts/docs/single.html @@ -0,0 +1,18 @@ +{{ define "main" }} +
+ {{ partial "sidebar.html" (dict "context" .) }} + {{ partial "toc.html" . }} +
+
+ {{ partial "breadcrumb.html" . }} +
+ {{- if not (isset .Params "hidetitle") -}}

{{ .Title }}

{{- end -}} + {{ .Content }} +
+ {{ partial "components/last-updated.html" . }} + {{ partial "components/pager.html" . }} + {{ partial "components/comments.html" . }} +
+
+
+{{ end }} diff --git a/layouts/partials/qmlparams.html b/layouts/partials/qmlparams.html new file mode 100644 index 0000000..1135651 --- /dev/null +++ b/layouts/partials/qmlparams.html @@ -0,0 +1,6 @@ +{{- $first := true -}} +{{- range $param, $type := . -}} + {{- if ne $first true -}}, {{ end -}} + {{- $first = false -}} + {{ $param }}: {{ partial "qmltype.html" $type }} +{{- end -}} \ No newline at end of file diff --git a/layouts/partials/qmltype.html b/layouts/partials/qmltype.html new file mode 100644 index 0000000..a674bf3 --- /dev/null +++ b/layouts/partials/qmltype.html @@ -0,0 +1,17 @@ +{{- if eq .type "unknown" -}} + unknown +{{- else -}} + {{- $link := "#ERROR" -}} + {{- if eq .type "qt" -}} + {{- $link = printf "https://doc.qt.io/qt-6/%s-%s.html" (lower (replace .module "." "-")) (lower .name) -}} + {{- else if eq .type "local" -}} + {{- $link = printf "/docs/types/%s/%s" (lower .module) (lower .name) -}} + {{- end -}} + + {{- $of := "" -}} + {{- if .of -}} + {{- $of = printf "<%s>" (partial "qmltype.html" .of) }} + {{- end -}} + + {{ .name }}{{ $of | safeHTML -}} +{{- end -}} diff --git a/layouts/shortcodes/qmlmodule.html b/layouts/shortcodes/qmlmodule.html new file mode 100644 index 0000000..d1a01b2 --- /dev/null +++ b/layouts/shortcodes/qmlmodule.html @@ -0,0 +1,14 @@ +{{- $modulename := .Get "module" -}} +{{- $module := index .Site.Data.modules $modulename -}} +{{- $module.index.description | $.Page.RenderString (dict "display" "block") -}} +

Types

+ + {{- range $name, $type := $module -}} + {{- if ne $name "index" -}} + + + + + {{- end -}} + {{- end -}} +
{{ $name }}{{ $type.description }}
diff --git a/layouts/shortcodes/qmlmodulelisting.html b/layouts/shortcodes/qmlmodulelisting.html new file mode 100644 index 0000000..ee46d96 --- /dev/null +++ b/layouts/shortcodes/qmlmodulelisting.html @@ -0,0 +1,8 @@ + + {{- range $name, $module := .Site.Data.modules -}} + + + + + {{- end -}} +
{{ $name }}{{ $module.index.shortDescription }}
diff --git a/layouts/shortcodes/qmltype.html b/layouts/shortcodes/qmltype.html new file mode 100644 index 0000000..b6ed8c8 --- /dev/null +++ b/layouts/shortcodes/qmltype.html @@ -0,0 +1,204 @@ +{{ $modulename := .Get "module" }} +{{ $typename := .Get "type" }} +{{ $type := index .Site.Data.modules $modulename $typename }} + +{{- define "partials/qmltypeflag.html" -}} + {{- if eq . "default" -}} + {{ . }} + {{- else if eq . "singleton" -}} + {{ . }} + {{- else if eq . "uncreatable" -}} + {{ . }} + {{- else if eq . "readonly" -}} + {{ . }} + {{- else if eq . "writeonly" -}} + {{ . }} + {{- else -}} + {{ . }} + {{- end -}} +{{- end -}} + +{{- define "partials/qmltypeflags.html" -}} + {{- $first := true -}} + [ + {{- range $flag := . }} + {{- if not $first -}}, {{ end -}} + {{- $first = false -}} + {{ partial "qmltypeflag.html" $flag }} + {{- end -}} + ] +{{- end -}} + +

+ {{ $typename -}} + + {{- if eq $type.type "class" -}} + : {{ partial "qmltype.html" $type.super }} + {{ if $type.flags -}} + {{ partial "qmltypeflags.html" $type.flags }} + {{- end -}} + {{- else if eq $type.type "enum" -}} + [enum] + {{- end -}} + +

+ + + import {{ $modulename }} + + +{{- if $type.description -}} +

+ {{- $type.description | $.Page.RenderString (dict "display" "inline") }} + {{ if $type.details -}} More {{- end -}} +{{- end -}} + +{{- if $type.properties -}} +

Properties

+ +{{- end -}} + +{{- if $type.functions -}} +

Functions

+ +{{- end -}} + +{{- if $type.variants -}} +

Variants

+ +{{- end -}} + +{{- if $type.details -}} +

Detailed Description

+ {{- $type.details | $.Page.RenderString (dict "display" "block") -}} +{{- end -}} + +{{- if $type.properties -}} +

Property Details

+ {{ range $propname, $prop := $type.properties }} +
+ {{- if $prop.flags -}} + + {{ partial "qmltypeflags.html" $prop.flags }} + + {{- end -}} + + {{- if $prop.type.gadget -}} + {{- range $gadgetname, $gadgettype := $prop.type.gadget -}} +

+ {{ $propname }}.{{ $gadgetname -}} + : + {{ partial "qmltype.html" $gadgettype -}} + +

+ {{- end -}} + {{- else -}} +

+ {{ $propname -}} + : + {{ partial "qmltype.html" $prop.type -}} + +

+ {{- end -}} +
+ +
+ {{- if $prop.details -}} + {{- $prop.details | $.Page.RenderString (dict "display" "block") -}} + {{- else -}} +

No details provided.

+ {{- end -}} +
+ {{- end -}} +{{- end -}} + +{{- if $type.functions -}} +

Function Details

+ {{ range $funcname, $func := $type.functions }} +
+ {{- if $func.flags -}} + + {{ partial "qmltypeflags.html" $func.flags }} + + {{- end -}} + +

+ + {{ partial "qmltype.html" $func.ret -}} + + {{ $funcname -}} + ( + {{- partial "qmlparams.html" $func.params -}} + ) +

+
+ +
+ {{- if $func.details -}} + {{- $func.details | $.Page.RenderString (dict "display" "block") -}} + {{- else -}} +

No details provided.

+ {{- end -}} +
+ {{- end -}} +{{- end -}} + +{{- if $type.variants -}} +

Variant Details

+ {{ range $name, $variant := $type.variants }} +
+

{{ $name -}}

+
+ +
+ {{- if $variant.details -}} + {{- $variant.details | $.Page.RenderString (dict "display" "block") -}} + {{- else -}} +

No details provided.

+ {{- end -}} +
+ {{- end -}} +{{- end -}} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..9b401af --- /dev/null +++ b/shell.nix @@ -0,0 +1,22 @@ +{ pkgs ? import {} }: let + # rustfmt unstable + fenix = import (pkgs.fetchFromGitHub { + owner = "nix-community"; + repo = "fenix"; + rev = "3776d0e2a30184cc6a0ba20fb86dc6df5b41fccd"; + sha256 = "K8QDx8UgbvGdENuvPvcsCXcd8brd55OkRDFLBT7xUVY="; + }) {}; + + rust-toolchain = fenix.complete.withComponents [ + "cargo" + "rustc" + "clippy" + "rustfmt" + ]; +in pkgs.mkShell { + buildInputs = with pkgs; [ + just + hugo + rust-toolchain + ]; +} diff --git a/themes/hextra b/themes/hextra new file mode 160000 index 0000000..7191e25 --- /dev/null +++ b/themes/hextra @@ -0,0 +1 @@ +Subproject commit 7191e259584e339b499a63e2e60cb6d818222e18 diff --git a/typegen/Cargo.lock b/typegen/Cargo.lock new file mode 100644 index 0000000..1e65307 --- /dev/null +++ b/typegen/Cargo.lock @@ -0,0 +1,216 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "indexmap" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "toml" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typegen" +version = "0.1.0" +dependencies = [ + "anyhow", + "regex", + "serde", + "serde_json", + "toml", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "winnow" +version = "0.5.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cad8365489051ae9f054164e459304af2e7e9bb407c958076c8bf4aef52da5" +dependencies = [ + "memchr", +] diff --git a/typegen/Cargo.toml b/typegen/Cargo.toml new file mode 100644 index 0000000..c3a3e31 --- /dev/null +++ b/typegen/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "typegen" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "^1" +regex = "^1.10" +serde = { version = "^1", features = ["derive"] } +serde_json = "^1" +toml = "^0.8" diff --git a/typegen/rustfmt.toml b/typegen/rustfmt.toml new file mode 100644 index 0000000..9511b44 --- /dev/null +++ b/typegen/rustfmt.toml @@ -0,0 +1,21 @@ +unstable_features = true +error_on_line_overflow = true + +hard_tabs = true +newline_style = "Unix" + +max_width = 100 + +imports_layout = "HorizontalVertical" +imports_granularity = "Crate" +group_imports = "StdExternalCrate" +hex_literal_case = "Lower" +match_block_trailing_comma = true +overflow_delimited_expr = true +reorder_impl_items = true +trailing_semicolon = false +use_field_init_shorthand = true +use_try_shorthand = true +condense_wildcard_suffixes = true +single_line_if_else_max_width = 80 +single_line_let_else_max_width = 80 diff --git a/typegen/src/main.rs b/typegen/src/main.rs new file mode 100644 index 0000000..b5c6dbb --- /dev/null +++ b/typegen/src/main.rs @@ -0,0 +1,119 @@ +use std::{collections::HashMap, path::Path}; + +use anyhow::{anyhow, Context}; + +mod parse; +mod typespec; +mod outform; +mod resolver; + +fn main() -> anyhow::Result<()> { + let args = std::env::args().collect::>(); + + match args.get(1).map(|v| v as &str) { + Some("gentypes") => { + let modinfo = args.get(2).expect("expected module file"); + let outpath = args.get(3).expect("expected output path"); + let path = Path::new(modinfo); + let dir = path.parent().unwrap(); + let text = std::fs::read_to_string(path).expect("failed to read module file"); + let module = parse::parse_module(&text)?; + + let texts = module.header.headers.iter() + .map(|header| { + let text = std::fs::read_to_string(dir.join(header)) + .with_context(|| format!("failed to read module header `{header}` at {:?}", dir.join(header)))?; + + Ok::<_, anyhow::Error>((header, text)) + }) + .collect::, _>>()?; + + let parser = parse::Parser::new(); + let mut ctx = parse::ParseContext::default(); + texts.iter() + .map(|(header, text)| { + parser.parse(&text, &mut ctx) + .with_context(|| format!("while parsing module header `{header}`")) + }) + .collect::>()?; + + let typespec = ctx.gen_typespec(&module.header.name); + + let text = serde_json::to_string_pretty(&typespec).unwrap(); + + std::fs::write(outpath, text) + .context("saving typespec")?; + }, + Some("gendocs") => { + let modinfo = args.get(2).expect("expected module file"); + let datapath = args.get(3).expect("expected datapath"); + let templatepath = args.get(4).expect("expected templatepath"); + + let text = std::fs::read_to_string(modinfo).expect("failed to read module file"); + let module = parse::parse_module(&text)?; + + let mut typespec = typespec::TypeSpec::default(); + + for path in &args[5..] { + let text = std::fs::read_to_string(&path) + .with_context(|| anyhow!("attempting to read {path}"))?; + + let ts = serde_json::from_str::(&text) + .with_context(|| anyhow!("attempting to parse {path}"))?; + + typespec.typemap.extend(ts.typemap); + typespec.classes.extend(ts.classes); + typespec.gadgets.extend(ts.gadgets); + typespec.enums.extend(ts.enums); + } + + let types = resolver::resolve_types(&module.header.name, typespec)?; + + let datapath = Path::new(datapath); + let templatepath = Path::new(templatepath); + std::fs::create_dir_all(datapath)?; + std::fs::create_dir_all(templatepath)?; + + for (name, info) in types { + let json = serde_json::to_string_pretty(&info).unwrap(); + let datapath = datapath.join(format!("{name}.json")); + std::fs::write(&datapath, json).with_context(|| format!("while writing {datapath:?}"))?; + + let template = format!("+++ +title = \"{name}\" +hidetitle = true ++++ + +{{{{< qmltype module=\"{module}\" type=\"{name}\" >}}}} +", name = name, module = module.header.name); + + let templatepath = templatepath.join(format!("{name}.md")); + std::fs::write(&templatepath, template).with_context(|| format!("while writing {templatepath:?}"))?; + } + + let index = outform::ModuleIndex { + description: module.header.description, + details: module.details.to_string(), + }; + + let datapath = datapath.join("index.json"); + let json = serde_json::to_string_pretty(&index).unwrap(); + std::fs::write(&datapath, json).with_context(|| format!("while writing {datapath:?}"))?; + + let template = format!("+++ +title = \"{name}\" ++++ + +{{{{< qmlmodule module=\"{name}\" >}}}} +", name = module.header.name); + + let templatepath = templatepath.join(format!("_index.md")); + std::fs::write(&templatepath, template).with_context(|| format!("while writing {templatepath:?}"))?; + }, + _ => { + panic!("typegen invoked without mode"); + }, + } + + Ok(()) +} diff --git a/typegen/src/outform.rs b/typegen/src/outform.rs new file mode 100644 index 0000000..be7f643 --- /dev/null +++ b/typegen/src/outform.rs @@ -0,0 +1,120 @@ +use std::collections::HashMap; + +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct ModuleIndex { + pub description: String, + pub details: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "lowercase")] +#[serde(tag = "type")] +pub enum TypeInfo { + Class(ClassInfo), + Enum(EnumInfo), +} + +#[derive(Debug, Serialize)] +pub struct ClassInfo { + #[serde(rename = "super")] + pub superclass: Type, + pub description: Option, + pub details: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub flags: Vec, + pub properties: HashMap, + pub functions: HashMap, +} + +#[derive(Debug, Serialize)] +pub struct Property { + #[serde(rename = "type")] + pub type_: PropertyType, + pub details: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub flags: Vec, +} + +#[derive(Debug, Serialize)] +pub enum PropertyType { + #[serde(rename = "gadget")] + Gadget(HashMap), + #[serde(untagged)] + Type(Type), +} + +#[derive(Debug, Serialize)] +pub struct Function { + pub ret: Type, + pub name: String, + pub details: Option, + pub params: HashMap, +} + +#[derive(Debug, Serialize)] +pub struct EnumInfo { + pub description: Option, + pub details: Option, + pub variants: HashMap, +} + +#[derive(Debug, Serialize)] +pub struct Variant { + pub details: Option, +} + +#[derive(Debug, Serialize)] +pub struct Type { + #[serde(rename = "type")] + pub type_: TypeSource, + pub module: String, + pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub of: Option>, +} + +impl Type { + pub fn resolve(module: Option<&str>, name: &str) -> Self { + let (src, module) = match module { + None => (TypeSource::Qt, "qml".to_string()), + Some(name) if name.starts_with("qml.") => (TypeSource::Qt, name.to_string()), + Some(name) => (TypeSource::Local, name.to_string()), + }; + + Type { + type_: src, + module, + name: name.to_string(), + of: None, + } + } + + pub fn unknown() -> Type { + Type { + type_: TypeSource::Unknown, + module: "".to_string(), + name: "".to_string(), + of: None, + } + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum TypeSource { + Qt, + Local, + Unknown, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Flag { + Default, + Readonly, + Writeonly, + Singleton, + Uncreatable, +} diff --git a/typegen/src/parse.rs b/typegen/src/parse.rs new file mode 100644 index 0000000..f556d74 --- /dev/null +++ b/typegen/src/parse.rs @@ -0,0 +1,452 @@ +use std::borrow::Cow; + +use anyhow::{anyhow, bail, Context}; +use regex::Regex; +use serde::Deserialize; + +use crate::typespec; + +#[derive(Deserialize, Debug)] +pub struct ModuleInfoHeader { + pub name: String, + pub description: String, + pub headers: Vec, +} + +#[derive(Debug)] +pub struct ModuleInfo<'a> { + pub header: ModuleInfoHeader, + pub details: &'a str, +} + +pub fn parse_module(text: &str) -> anyhow::Result { + let Some((mut header, mut details)) = text.split_once("-----") else { + bail!("could not split module header"); + }; + + header = header.trim(); + details = details.trim(); + + let header = toml::from_str::(header) + .context("parsing module info header")?; + + Ok(ModuleInfo { + header, + details, + }) +} + +#[derive(Debug)] +pub struct ClassInfo<'a> { + pub type_: ClassType, + pub name: &'a str, + pub qml_name: Option<&'a str>, + pub superclass: Option<&'a str>, + pub singleton: bool, + pub uncreatable: bool, + pub comment: Option<&'a str>, + pub properties: Vec>, + pub invokables: Vec>, +} + +#[derive(Debug)] +pub enum ClassType { + Object, + Gadget, +} + +#[derive(Debug, Clone, Copy)] +pub struct Property<'a> { + pub type_: &'a str, + pub name: &'a str, + pub comment: Option<&'a str>, + pub readable: bool, + pub writable: bool, + pub default: bool, +} + +#[derive(Debug, Clone)] +pub struct Invokable<'a> { + pub name: &'a str, + pub ret: &'a str, + pub comment: Option<&'a str>, + pub params: Vec>, +} + +#[derive(Debug, Clone, Copy)] +pub struct InvokableParam<'a> { + pub name: &'a str, + pub type_: &'a str, +} + +#[derive(Debug)] +pub struct EnumInfo<'a> { + pub namespace: &'a str, + pub enum_name: &'a str, + pub qml_name: &'a str, + pub comment: Option<&'a str>, + pub variants: Vec>, +} + +#[derive(Debug, Clone, Copy)] +pub struct Variant<'a> { + pub name: &'a str, + pub comment: Option<&'a str>, +} + +pub struct Parser { + pub class_regex: Regex, + pub macro_regex: Regex, + pub property_regex: Regex, + pub fn_regex: Regex, + pub fn_param_regex: Regex, + pub defaultprop_classinfo_regex: Regex, + pub enum_regex: Regex, + pub enum_variant_regex: Regex, +} + +#[derive(Debug)] +pub struct ParseContext<'a> { + pub classes: Vec>, + pub enums: Vec>, +} + +impl Default for ParseContext<'_> { + fn default() -> Self { + Self { + classes: Vec::new(), + enums: Vec::new(), + } + } +} + +impl Parser { + pub fn new() -> Self { + Self { + class_regex: Regex::new(r#"(?(\s*\/\/\/.*\n)+)?\s*class\s+(?\w+)(?:\s*:\s*public\s+(?\w+))?.+?\{(?[\s\S]*?)};"#).unwrap(), + macro_regex: Regex::new(r#"(?(\s*\/\/\/.*\n)+)?\s*(?(Q|QML)_\w+)\s*(\((?.*)\))?;"#).unwrap(), + property_regex: Regex::new(r#"^\s*(?(\w|::|<|>)+\*?)\s+(?\w+)(\s+(MEMBER\s+(?\w+)|READ\s+(?\w+)|WRITE\s+(?\w+)|NOTIFY\s+(?\w+)|(?CONSTANT)))+\s*$"#).unwrap(), + fn_regex: Regex::new(r#"(?(\s*\/\/\/.*\n)+)?\s*Q_INVOKABLE\s+(?(\w|::|<|>)+\*?)\s+(?\w+)\((?[\s\S]*?)\);"#).unwrap(), + fn_param_regex: Regex::new(r#"(?(\w|::|<|>)+\*?)\s+(?\w+)(,|$)"#).unwrap(), + defaultprop_classinfo_regex: Regex::new(r#"^\s*"DefaultProperty", "(?.+)"\s*$"#).unwrap(), + enum_regex: Regex::new(r#"(?(\s*\/\/\/.*\n)+)?\s*namespace (?\w+)\s*\{[\s\S]*?(QML_ELEMENT|QML_NAMED_ELEMENT\((?\w+)\));[\s\S]*?enum\s*(?\w+)\s*\{(?[\s\S]*?)\};[\s\S]*?\}"#).unwrap(), + enum_variant_regex: Regex::new(r#"(?(\s*\/\/\/.*\n)+)?\s*(?\w+)\s*=\s*\d+,"#).unwrap(), + } + } + + pub fn parse_classes<'a>(&self, text: &'a str, ctx: &mut ParseContext<'a>) -> anyhow::Result<()> { + for class in self.class_regex.captures_iter(text) { + let comment = class.name("comment").map(|m| m.as_str()); + let name = class.name("name").unwrap().as_str(); + let superclass = class.name("super").map(|m| m.as_str()); + let body = class.name("body").unwrap().as_str(); + + let mut classtype = None; + let mut qml_name = None; + let mut singleton = false; + let mut uncreatable = false; + let mut properties = Vec::new(); + let mut default_property = None; + let mut invokables = Vec::new(); + + (|| { + for macro_ in self.macro_regex.captures_iter(body) { + let comment = macro_.name("comment").map(|m| m.as_str()); + let type_ = macro_.name("type").unwrap().as_str(); + let args = macro_.name("args").map(|m| m.as_str()); + + (|| { + match type_ { + "Q_OBJECT" => classtype = Some(ClassType::Object), + "Q_GADGET" => classtype = Some(ClassType::Gadget), + "QML_ELEMENT" => qml_name = Some(name), + "QML_NAMED_ELEMENT" => qml_name = Some(args.ok_or_else(|| anyhow!("expected name for QML_NAMED_ELEMENT"))?), + "QML_SINGLETON" => singleton = true, + "QML_UNCREATABLE" => uncreatable = true, + "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"))?; + + let member = prop.name("member").is_some(); + let read = prop.name("read").is_some(); + let write = prop.name("write").is_some(); + let constant = prop.name("const").is_some(); + + properties.push(Property { + type_: prop.name("type").unwrap().as_str(), + name: prop.name("name").unwrap().as_str(), + comment, + readable: read || member, + writable: !constant && (write || member), + default: false, + }); + }, + "Q_CLASSINFO" => { + let classinfo = self.defaultprop_classinfo_regex.captures(args.ok_or_else(|| anyhow!("expected args for Q_CLASSINFO"))?); + + if let Some(classinfo) = classinfo { + let prop = classinfo.name("prop").unwrap().as_str(); + default_property = Some(prop); + } + }, + _ => {}, + } + Ok::<_, anyhow::Error>(()) + })().with_context(|| format!("while parsing macro `{}`", macro_.get(0).unwrap().as_str()))?; + } + + for invokable in self.fn_regex.captures_iter(body) { + let comment = invokable.name("comment").map(|m| m.as_str()); + let type_ = invokable.name("type").unwrap().as_str(); + let name = invokable.name("name").unwrap().as_str(); + let params_raw = invokable.name("params").unwrap().as_str(); + + 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, + }); + } + + invokables.push(Invokable { + name, + ret: type_, + comment, + params, + }); + } + + 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; + } + + Ok::<_, anyhow::Error>(()) + })().with_context(|| format!("while parsing class `{name}`"))?; + + let Some(type_) = classtype else { continue }; + + ctx.classes.push(ClassInfo { + type_, + name, + qml_name, + superclass, + singleton, + uncreatable, + comment, + properties, + invokables, + }); + } + + Ok(()) + } + + pub fn parse_enums<'a>(&self, text: &'a str, ctx: &mut ParseContext<'a>) -> anyhow::Result<()> { + for enum_ in self.enum_regex.captures_iter(text) { + let comment = enum_.name("comment").map(|m| m.as_str()); + let namespace = enum_.name("namespace").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 body = enum_.name("body").unwrap().as_str(); + + let mut variants = Vec::new(); + + for variant in self.enum_variant_regex.captures_iter(body) { + let comment = variant.name("comment").map(|m| m.as_str()); + let name = variant.name("name").unwrap().as_str(); + + variants.push(Variant { + name, + comment, + }); + } + + ctx.enums.push(EnumInfo { + namespace, + enum_name, + qml_name, + comment, + variants, + }); + } + + Ok(()) + } + + pub fn parse<'a>(&self, text: &'a str, ctx: &mut ParseContext<'a>) -> anyhow::Result<()> { + self.parse_classes(text, ctx)?; + self.parse_enums(text, ctx)?; + + Ok(()) + } +} + +impl ParseContext<'_> { + pub fn gen_typespec(&self, module: &str) -> typespec::TypeSpec { + typespec::TypeSpec { + typemap: self.classes.iter().filter_map(|class| { + Some(typespec::QmlTypeMapping { + // filters gadgets + name: class.qml_name?.to_string(), + cname: class.name.to_string(), + module: Some(module.to_string()), + }) + }).collect(), + classes: self.classes.iter().filter_map(|class| { + let (description, details) = class.comment.map(parse_details_desc) + .unwrap_or((None, None)); + + Some(typespec::Class { + name: class.name.to_string(), + module: module.to_string(), + description, + details, + // filters gadgets + superclass: class.superclass?.to_string(), + singleton: class.singleton, + uncreatable: class.uncreatable, + properties: class.properties.iter().map(|p| (*p).into()).collect(), + functions: class.invokables.iter().map(|f| f.as_typespec()).collect(), + }) + }).collect(), + gadgets: self.classes.iter().filter_map(|class| match class.type_ { + ClassType::Gadget => Some(typespec::Gadget { + cname: class.name.to_string(), + properties: class.properties.iter().map(|p| (*p).into()).collect(), + }), + _ => None, + }).collect(), + enums: self.enums.iter().map(|enum_| { + let (description, details) = enum_.comment.map(parse_details_desc) + .unwrap_or((None, None)); + + typespec::Enum { + name: enum_.qml_name.to_string(), + module: Some(module.to_string()), + cname: Some(format!("{}::{}", enum_.namespace, enum_.enum_name)), + description, + details, + varaints: enum_.variants.iter().map(|v| (*v).into()).collect(), + } + }).collect(), + } + } +} + +impl From> for typespec::Property { + fn from(value: Property) -> Self { + Self { + type_: value.type_.to_string(), + name: value.name.to_string(), + details: value.comment.map(parse_details), + readable: value.readable, + writable: value.writable, + default: value.default, + } + } +} + +impl From> for typespec::Variant { + fn from(value: Variant<'_>) -> Self { + Self { + name: value.name.to_string(), + details: value.comment.map(parse_details), + } + } +} + +impl Invokable<'_> { + fn as_typespec(&self) -> typespec::Function { + typespec::Function { + ret: self.ret.to_string(), + name: self.name.to_string(), + details: self.comment.map(parse_details), + params: self.params.iter().map(|p| (*p).into()).collect(), + } + } +} + +impl From> for typespec::FnParam { + fn from(value: InvokableParam<'_>) -> Self { + Self { + type_: value.type_.to_string(), + name: value.name.to_string(), + } + } +} + +fn parse_details(text: &str) -> String { + let mut seen_content = false; + let mut callout = false; + + let mut str = text.lines() + .map(|line| { + line.trim() + .strip_prefix("///") + .map(|line| line.strip_prefix(' ').unwrap_or(line)) + .unwrap_or(line) + }) + .filter(|line| { + let any = !line.is_empty(); + let filter = any || seen_content; + seen_content |= any; + filter + }) + .map(|line| { + match callout { + true => { + if line.starts_with('>') { + Cow::Borrowed(line[1..].strip_prefix(' ').unwrap_or(&line[1..])) + } else { + callout = false; + Cow::Owned(format!("{{{{< /callout >}}}}\n{line}")) + } + } + false => { + if line.starts_with("> [!") { + let code = line[4..].split_once(']'); + + if let Some((code, line)) = code { + let code = code.to_lowercase(); + callout = true; + return Cow::Owned(format!("{{{{< callout type=\"{code}\" >}}}}\n{line}")) + } + } + + return Cow::Borrowed(line); + } + } + }) + .fold(String::new(), |accum, line| { + let sep = if accum.is_empty() { "" } else { "\n" }; + accum + sep + line.as_ref() + }); + + if callout { + str += "\n{{< /callout >}}"; + } + + str +} + +fn parse_details_desc(text: &str) -> (Option, Option) { + let details = parse_details(text); + dbg!(&details); + if details.starts_with('!') { + dbg!(&details, &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())), + None => (Some(details[1..].strip_prefix(' ').unwrap_or(&details[1..]).to_string()), None), + } + } else { + (None, Some(details)) + } +} diff --git a/typegen/src/resolver.rs b/typegen/src/resolver.rs new file mode 100644 index 0000000..352c9ec --- /dev/null +++ b/typegen/src/resolver.rs @@ -0,0 +1,192 @@ +use std::collections::HashMap; + +use crate::{outform::{self, Flag, PropertyType}, typespec::{FnParam, Function, Property, TypeSpec}}; + +pub fn resolve_types(module: &str, typespec: TypeSpec) -> anyhow::Result> { + let mut outtypes = HashMap::new(); + + let types = typespec.typemap.iter() + .filter(|type_| type_.module.as_ref().map(|v| v as &str) == Some(module)); + + let findqmltype = |cname: &str| typespec.typemap + .iter() + .find(|type_| type_.cname == cname); + + for mapping in types { + let Some(class) = typespec.classes.iter().find(|class| class.name == mapping.cname) else { + continue + }; + + let mut properties = Vec::<&Property>::new(); + let mut functions = Vec::<&Function>::new(); + + // the first superclass availible from QML + let mut superclass = &class.superclass; + let superclass = loop { + let type_ = findqmltype(superclass); + + if let Some(type_) = type_ { + 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); + + match superctype { + Some(superctype) => { + properties.extend(superctype.properties.iter()); + functions.extend(superctype.functions.iter()); + superclass = &superctype.superclass; + }, + None => break outform::Type::unknown(), + } + }; + + fn qmlparamtype(ctype: &str, typespec: &TypeSpec) -> outform::Type { + let qtype = typespec.typemap + .iter() + .find(|type_| &type_.cname == ctype) + .map(|type_| (&type_.module, &type_.name)) + .or_else(|| { + typespec.enums + .iter() + .find(|type_| type_.cname.as_ref().map(|v| v as &str) == Some(ctype)) + .map(|type_| (&type_.module, &type_.name)) + }); + + match qtype { + Some((module, name)) => outform::Type::resolve(module.as_ref().map(|v| v as &str), &name), + None => outform::Type::unknown(), + } + } + + fn solveprop(prop: &Property, typespec: &TypeSpec) -> outform::Property { + let mut ctype = &prop.type_[..]; + + let flags = { + let mut flags = Vec::new(); + + if prop.default { + flags.push(Flag::Default); + } + + if !prop.readable { + flags.push(Flag::Writeonly); + } else if !prop.writable { + flags.push(Flag::Readonly); + } + + flags + }; + + let gadget = typespec.gadgets.iter() + .find(|gadget| gadget.cname == ctype); + + match gadget { + Some(gadget) => outform::Property { + type_: PropertyType::Gadget( + gadget.properties.iter() + .map(|prop| (prop.name.clone(), solveprop(prop, typespec).type_)) + .collect() + ), + details: prop.details.clone(), + flags, + }, + None => { + let mut list = false; + + if ctype.starts_with("QQmlListProperty<") { + ctype = &ctype[17..ctype.len() - 1]; + list = true; + } else if ctype.starts_with("QList<") { + ctype = &ctype[6..ctype.len() - 1]; + list = true; + } + + if ctype.ends_with('*') { + ctype = &ctype[..ctype.len() - 1]; + } + + let mut type_ = qmlparamtype(ctype, typespec); + + if list { + type_ = outform::Type { + type_: outform::TypeSource::Qt, + module: "qml".to_string(), + name: "list".to_string(), + of: Some(Box::new(type_)), + }; + } + + outform::Property { + type_: PropertyType::Type(type_), + details: prop.details.clone(), + flags, + } + }, + } + } + + fn solvefunc(func: &Function, typespec: &TypeSpec) -> outform::Function { + outform::Function { + ret: qmlparamtype(&func.ret, typespec), + name: func.name.clone(), + details: func.details.clone(), + params: func.params.iter().map(|FnParam { type_, name }| (name.clone(), qmlparamtype(type_, typespec))).collect(), + } + } + + properties.extend(class.properties.iter()); + properties.sort_by(|a, b| Ord::cmp(&a.name, &b.name)); + + functions.extend(class.functions.iter()); + functions.sort_by(|a, b| Ord::cmp(&a.name, &b.name)); + + let properties = properties.iter().map(|prop| ( + prop.name.clone(), + solveprop(prop, &typespec) + )).collect::>(); + + let functions = functions.iter().map(|func| ( + func.name.clone(), + solvefunc(func, &typespec) + )).collect::>(); + + let type_ = outform::ClassInfo { + superclass, + description: class.description.clone(), + details: class.details.clone(), + flags: { + let mut flags = Vec::new(); + + if class.singleton { + flags.push(Flag::Singleton); + } else if class.uncreatable { + flags.push(Flag::Uncreatable); + } + + flags + }, + properties, + functions, + }; + + outtypes.insert(mapping.name.clone(), outform::TypeInfo::Class(type_)); + } + + for enum_ in typespec.enums { + if enum_.module.as_ref().map(|v| v as &str) == Some(module) { + outtypes.insert(enum_.name, outform::TypeInfo::Enum(outform::EnumInfo { + description: enum_.description, + details: enum_.details, + variants: enum_.varaints.into_iter().map(|variant| ( + variant.name, + outform::Variant { + details: variant.details, + }, + )).collect(), + })); + } + } + + Ok(outtypes) +} diff --git a/typegen/src/typespec.rs b/typegen/src/typespec.rs new file mode 100644 index 0000000..920dd32 --- /dev/null +++ b/typegen/src/typespec.rs @@ -0,0 +1,88 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct TypeSpec { + pub typemap: Vec, + pub classes: Vec, + pub gadgets: Vec, + pub enums: Vec, +} + +impl Default for TypeSpec { + fn default() -> Self { + Self { + typemap: Vec::new(), + classes: Vec::new(), + gadgets: Vec::new(), + enums: Vec::new(), + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct QmlTypeMapping { + pub name: String, + pub cname: String, + pub module: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Class { + pub name: String, + pub module: String, + pub description: Option, + pub details: Option, + pub superclass: String, + pub singleton: bool, + pub uncreatable: bool, + pub properties: Vec, + pub functions: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Gadget { + pub cname: String, + pub properties: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Property { + #[serde(rename = "type")] + pub type_: String, + pub name: String, + pub details: Option, + pub readable: bool, + pub writable: bool, + pub default: bool, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Function { + pub ret: String, + pub name: String, + pub details: Option, + pub params: Vec +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct FnParam { + #[serde(rename = "type")] + pub type_: String, + pub name: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Enum { + pub name: String, + pub cname: Option, + pub module: Option, + pub description: Option, + pub details: Option, + pub varaints: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Variant { + pub name: String, + pub details: Option, +} diff --git a/types/QtQuick.json b/types/QtQuick.json new file mode 100644 index 0000000..62ba66f --- /dev/null +++ b/types/QtQuick.json @@ -0,0 +1,57 @@ +{ + "typemap": [ + { + "name": "void", + "cname": "void", + "module": null + }, + { + "name": "variant", + "cname": "QVariant", + "module": null + }, + { + "name": "bool", + "cname": "bool", + "module": null + }, + { + "name": "int", + "cname": "qint32", + "module": null + }, + { + "name": "real", + "cname": "qreal", + "module": null + }, + { + "name": "string", + "cname": "QString", + "module": null + }, + { + "name": "color", + "cname": "QColor", + "module": null + }, + { + "name": "QtObject", + "cname": "QObject", + "module": "qml.QtQml" + }, + { + "name": "Component", + "cname": "QQmlComponent", + "module": "qml.QtQml" + }, + { + "name": "Item", + "cname": "QQuickItem", + "module": "qml.QtQuick" + } + ], + "classes": [], + "gadgets": [], + "enums": [] +}