initial commit
This commit is contained in:
commit
27b3274027
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
# direnv
|
||||
/.envrc
|
||||
/.direnv
|
||||
|
||||
# typegen
|
||||
/typegen/target
|
||||
/build
|
||||
/content/docs/types/QuickShell
|
||||
/data/modules
|
||||
|
||||
# hugo
|
||||
/.hugo_build.lock
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "themes/hextra"]
|
||||
path = themes/hextra
|
||||
url = https://github.com/imfing/hextra.git
|
13
Justfile
Normal file
13
Justfile
Normal file
|
@ -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
|
47
assets/css/custom.css
Normal file
47
assets/css/custom.css
Normal file
|
@ -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;
|
||||
}
|
9
content/_index.md
Normal file
9
content/_index.md
Normal file
|
@ -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 >}}
|
3
content/docs/_index.md
Normal file
3
content/docs/_index.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
+++
|
||||
title = 'Docs'
|
||||
+++
|
7
content/docs/types/_index.md
Normal file
7
content/docs/types/_index.md
Normal file
|
@ -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 >}}
|
36
hugo.toml
Normal file
36
hugo.toml
Normal file
|
@ -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"
|
18
layouts/docs/single.html
Normal file
18
layouts/docs/single.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
{{ define "main" }}
|
||||
<div class='mx-auto flex {{ partial "utils/page-width" . }}'>
|
||||
{{ partial "sidebar.html" (dict "context" .) }}
|
||||
{{ partial "toc.html" . }}
|
||||
<article class="w-full break-words flex min-h-[calc(100vh-var(--navbar-height))] min-w-0 justify-center pb-8 pr-[calc(env(safe-area-inset-right)-1.5rem)]">
|
||||
<main class="w-full min-w-0 max-w-6xl px-6 pt-4 md:px-12">
|
||||
{{ partial "breadcrumb.html" . }}
|
||||
<div class="content">
|
||||
{{- if not (isset .Params "hidetitle") -}}<h1>{{ .Title }}</h1>{{- end -}}
|
||||
{{ .Content }}
|
||||
</div>
|
||||
{{ partial "components/last-updated.html" . }}
|
||||
{{ partial "components/pager.html" . }}
|
||||
{{ partial "components/comments.html" . }}
|
||||
</main>
|
||||
</article>
|
||||
</div>
|
||||
{{ end }}
|
6
layouts/partials/qmlparams.html
Normal file
6
layouts/partials/qmlparams.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
{{- $first := true -}}
|
||||
{{- range $param, $type := . -}}
|
||||
{{- if ne $first true -}}, {{ end -}}
|
||||
{{- $first = false -}}
|
||||
{{ $param }}: {{ partial "qmltype.html" $type }}
|
||||
{{- end -}}
|
17
layouts/partials/qmltype.html
Normal file
17
layouts/partials/qmltype.html
Normal file
|
@ -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 -}}
|
||||
|
||||
<a href="{{ $link }}">{{ .name }}</a>{{ $of | safeHTML -}}
|
||||
{{- end -}}
|
14
layouts/shortcodes/qmlmodule.html
Normal file
14
layouts/shortcodes/qmlmodule.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
{{- $modulename := .Get "module" -}}
|
||||
{{- $module := index .Site.Data.modules $modulename -}}
|
||||
{{- $module.index.description | $.Page.RenderString (dict "display" "block") -}}
|
||||
<h3>Types</h3>
|
||||
<table>
|
||||
{{- range $name, $type := $module -}}
|
||||
{{- if ne $name "index" -}}
|
||||
<tr>
|
||||
<td><a href="/docs/types/{{ lower $modulename }}/{{ lower $name }}">{{ $name }}</a></td>
|
||||
<td>{{ $type.description }}</td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
</table>
|
8
layouts/shortcodes/qmlmodulelisting.html
Normal file
8
layouts/shortcodes/qmlmodulelisting.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<table>
|
||||
{{- range $name, $module := .Site.Data.modules -}}
|
||||
<tr>
|
||||
<td><a href="/docs/types/{{ lower $name }}">{{ $name }}</a></td>
|
||||
<td>{{ $module.index.shortDescription }}</td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
</table>
|
204
layouts/shortcodes/qmltype.html
Normal file
204
layouts/shortcodes/qmltype.html
Normal file
|
@ -0,0 +1,204 @@
|
|||
{{ $modulename := .Get "module" }}
|
||||
{{ $typename := .Get "type" }}
|
||||
{{ $type := index .Site.Data.modules $modulename $typename }}
|
||||
|
||||
{{- define "partials/qmltypeflag.html" -}}
|
||||
{{- if eq . "default" -}}
|
||||
<span title="Components written directly into an object are collected in its default property.">{{ . }}</span>
|
||||
{{- else if eq . "singleton" -}}
|
||||
<span title="Only one instance of this type exists, accessible by its name.">{{ . }}</span>
|
||||
{{- else if eq . "uncreatable" -}}
|
||||
<span title="This type cannot be manually created.">{{ . }}</span>
|
||||
{{- else if eq . "readonly" -}}
|
||||
<span title="This property cannot be assigned to, only read from.">{{ . }}</span>
|
||||
{{- else if eq . "writeonly" -}}
|
||||
<span title="This property cannot be read, only assigned.">{{ . }}</span>
|
||||
{{- else -}}
|
||||
<span>{{ . }}</span>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "partials/qmltypeflags.html" -}}
|
||||
{{- $first := true -}}
|
||||
[
|
||||
{{- range $flag := . }}
|
||||
{{- if not $first -}}, {{ end -}}
|
||||
{{- $first = false -}}
|
||||
{{ partial "qmltypeflag.html" $flag }}
|
||||
{{- end -}}
|
||||
]
|
||||
{{- end -}}
|
||||
|
||||
<h1>
|
||||
{{ $typename -}}
|
||||
<span class="typegray" style="font-weight: normal">
|
||||
{{- if eq $type.type "class" -}}
|
||||
: {{ partial "qmltype.html" $type.super }}
|
||||
{{ if $type.flags -}}
|
||||
<span class="qmlprops">{{ partial "qmltypeflags.html" $type.flags }}</span>
|
||||
{{- end -}}
|
||||
{{- else if eq $type.type "enum" -}}
|
||||
<span class="qmlprops">[enum]</span>
|
||||
{{- end -}}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<code>
|
||||
<i>import {{ $modulename }}</i>
|
||||
</code>
|
||||
|
||||
{{- if $type.description -}}
|
||||
<br><br>
|
||||
{{- $type.description | $.Page.RenderString (dict "display" "inline") }}
|
||||
{{ if $type.details -}} <a href="#details">More</a> {{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if $type.properties -}}
|
||||
<h4>Properties</h4>
|
||||
<ul>
|
||||
{{- range $propname, $prop := $type.properties -}}
|
||||
<li>
|
||||
<span class="typegray">
|
||||
<a href="#prop.{{ $propname }}">{{ $propname }}</a>
|
||||
{{- if not $prop.type.gadget -}}
|
||||
: {{ partial "qmltype.html" $prop.type }}
|
||||
{{- end -}}
|
||||
{{ if in $prop.flags "default" }}
|
||||
[{{ partial "qmltypeflag.html" "default" }}]
|
||||
{{ end }}
|
||||
</span>
|
||||
{{- if $prop.type.gadget -}}
|
||||
<ul>
|
||||
{{- range $gadgetname, $gadgettype := $prop.type.gadget -}}
|
||||
<li>
|
||||
<a href="#prop.{{ $propname }}">{{ $propname }}.{{ $gadgetname }}</a>:
|
||||
{{ partial "qmltype.html" $gadgettype }}
|
||||
</li>
|
||||
{{- end -}}
|
||||
</ul>
|
||||
{{- end -}}
|
||||
</li>
|
||||
{{- end -}}
|
||||
</ul>
|
||||
{{- end -}}
|
||||
|
||||
{{- if $type.functions -}}
|
||||
<h4>Functions</h4>
|
||||
<ul>
|
||||
{{- range $funcname, $func := $type.functions -}}
|
||||
<li>
|
||||
<span class="typegray">
|
||||
{{ partial "qmltype.html" $func.ret }}
|
||||
<a href="#func.{{ $funcname }}">{{ $funcname }}</a>(
|
||||
{{- partial "qmlparams.html" $func.params -}}
|
||||
)
|
||||
</span>
|
||||
</li>
|
||||
{{- end -}}
|
||||
</ul>
|
||||
{{- end -}}
|
||||
|
||||
{{- if $type.variants -}}
|
||||
<h4>Variants</h4>
|
||||
<ul>
|
||||
{{- range $name, $variant := $type.variants -}}
|
||||
<li>
|
||||
<span class="typegray">
|
||||
<a href="#variant.{{ $name }}">{{ $name }}</a>
|
||||
</span>
|
||||
</li>
|
||||
{{- end -}}
|
||||
</ul>
|
||||
{{- end -}}
|
||||
|
||||
{{- if $type.details -}}
|
||||
<h3 id="details">Detailed Description</h3>
|
||||
{{- $type.details | $.Page.RenderString (dict "display" "block") -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if $type.properties -}}
|
||||
<h3>Property Details</h3>
|
||||
{{ range $propname, $prop := $type.properties }}
|
||||
<div id="prop.{{ $propname }}" class = "qmlpropdef">
|
||||
{{- if $prop.flags -}}
|
||||
<span class="qmlprops typegray">
|
||||
{{ partial "qmltypeflags.html" $prop.flags }}
|
||||
</span>
|
||||
{{- end -}}
|
||||
|
||||
{{- if $prop.type.gadget -}}
|
||||
{{- range $gadgetname, $gadgettype := $prop.type.gadget -}}
|
||||
<p id="prop.{{ $propname }}.{{ $gadgetname }}">
|
||||
{{ $propname }}.{{ $gadgetname -}}
|
||||
<span class="typegray">:
|
||||
{{ partial "qmltype.html" $gadgettype -}}
|
||||
<span>
|
||||
</p>
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
<p>
|
||||
{{ $propname -}}
|
||||
<span class="typegray">:
|
||||
{{ partial "qmltype.html" $prop.type -}}
|
||||
<span>
|
||||
</p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
<div class="qmlpropdetails">
|
||||
{{- if $prop.details -}}
|
||||
{{- $prop.details | $.Page.RenderString (dict "display" "block") -}}
|
||||
{{- else -}}
|
||||
<p style="color: #999999"><i>No details provided.</i></p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if $type.functions -}}
|
||||
<h3>Function Details</h3>
|
||||
{{ range $funcname, $func := $type.functions }}
|
||||
<div id="func.{{ $funcname }}" class = "qmlpropdef">
|
||||
{{- if $func.flags -}}
|
||||
<span class="qmlprops typegray">
|
||||
{{ partial "qmltypeflags.html" $func.flags }}
|
||||
</span>
|
||||
{{- end -}}
|
||||
|
||||
<p>
|
||||
<span class="typegray">
|
||||
{{ partial "qmltype.html" $func.ret -}}
|
||||
</span>
|
||||
{{ $funcname -}}
|
||||
<span class="typegray">(
|
||||
{{- partial "qmlparams.html" $func.params -}}
|
||||
)</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="qmlpropdetails">
|
||||
{{- if $func.details -}}
|
||||
{{- $func.details | $.Page.RenderString (dict "display" "block") -}}
|
||||
{{- else -}}
|
||||
<p style="color: #999999"><i>No details provided.</i></p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if $type.variants -}}
|
||||
<h3>Variant Details</h3>
|
||||
{{ range $name, $variant := $type.variants }}
|
||||
<div id="variant.{{ $name }}" class = "qmlpropdef">
|
||||
<p>{{ $name -}}</p>
|
||||
</div>
|
||||
|
||||
<div class="qmlpropdetails">
|
||||
{{- if $variant.details -}}
|
||||
{{- $variant.details | $.Page.RenderString (dict "display" "block") -}}
|
||||
{{- else -}}
|
||||
<p style="color: #999999"><i>No details provided.</i></p>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- end -}}
|
||||
{{- end -}}
|
22
shell.nix
Normal file
22
shell.nix
Normal file
|
@ -0,0 +1,22 @@
|
|||
{ pkgs ? import <nixpkgs> {} }: 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
|
||||
];
|
||||
}
|
1
themes/hextra
Submodule
1
themes/hextra
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 7191e259584e339b499a63e2e60cb6d818222e18
|
216
typegen/Cargo.lock
generated
Normal file
216
typegen/Cargo.lock
generated
Normal file
|
@ -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",
|
||||
]
|
11
typegen/Cargo.toml
Normal file
11
typegen/Cargo.toml
Normal file
|
@ -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"
|
21
typegen/rustfmt.toml
Normal file
21
typegen/rustfmt.toml
Normal file
|
@ -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
|
119
typegen/src/main.rs
Normal file
119
typegen/src/main.rs
Normal file
|
@ -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::<Vec<_>>();
|
||||
|
||||
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::<Result<HashMap<_, _>, _>>()?;
|
||||
|
||||
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::<Result<_, _>>()?;
|
||||
|
||||
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::<typespec::TypeSpec>(&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(())
|
||||
}
|
120
typegen/src/outform.rs
Normal file
120
typegen/src/outform.rs
Normal file
|
@ -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<String>,
|
||||
pub details: Option<String>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub flags: Vec<Flag>,
|
||||
pub properties: HashMap<String, Property>,
|
||||
pub functions: HashMap<String, Function>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Property {
|
||||
#[serde(rename = "type")]
|
||||
pub type_: PropertyType,
|
||||
pub details: Option<String>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub flags: Vec<Flag>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub enum PropertyType {
|
||||
#[serde(rename = "gadget")]
|
||||
Gadget(HashMap<String, PropertyType>),
|
||||
#[serde(untagged)]
|
||||
Type(Type),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Function {
|
||||
pub ret: Type,
|
||||
pub name: String,
|
||||
pub details: Option<String>,
|
||||
pub params: HashMap<String, Type>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct EnumInfo {
|
||||
pub description: Option<String>,
|
||||
pub details: Option<String>,
|
||||
pub variants: HashMap<String, Variant>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Variant {
|
||||
pub details: Option<String>,
|
||||
}
|
||||
|
||||
#[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<Box<Type>>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
452
typegen/src/parse.rs
Normal file
452
typegen/src/parse.rs
Normal file
|
@ -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<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ModuleInfo<'a> {
|
||||
pub header: ModuleInfoHeader,
|
||||
pub details: &'a str,
|
||||
}
|
||||
|
||||
pub fn parse_module(text: &str) -> anyhow::Result<ModuleInfo> {
|
||||
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::<ModuleInfoHeader>(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<Property<'a>>,
|
||||
pub invokables: Vec<Invokable<'a>>,
|
||||
}
|
||||
|
||||
#[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<InvokableParam<'a>>,
|
||||
}
|
||||
|
||||
#[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<Variant<'a>>,
|
||||
}
|
||||
|
||||
#[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<ClassInfo<'a>>,
|
||||
pub enums: Vec<EnumInfo<'a>>,
|
||||
}
|
||||
|
||||
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#"(?<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*(\((?<args>.*)\))?;"#).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_param_regex: Regex::new(r#"(?<type>(\w|::|<|>)+\*?)\s+(?<name>\w+)(,|$)"#).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_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<()> {
|
||||
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<Property<'_>> 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<Variant<'_>> 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<InvokableParam<'_>> 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<String>, Option<String>) {
|
||||
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))
|
||||
}
|
||||
}
|
192
typegen/src/resolver.rs
Normal file
192
typegen/src/resolver.rs
Normal file
|
@ -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<HashMap<String, outform::TypeInfo>> {
|
||||
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::<HashMap<_, _>>();
|
||||
|
||||
let functions = functions.iter().map(|func| (
|
||||
func.name.clone(),
|
||||
solvefunc(func, &typespec)
|
||||
)).collect::<HashMap<_, _>>();
|
||||
|
||||
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)
|
||||
}
|
88
typegen/src/typespec.rs
Normal file
88
typegen/src/typespec.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct TypeSpec {
|
||||
pub typemap: Vec<QmlTypeMapping>,
|
||||
pub classes: Vec<Class>,
|
||||
pub gadgets: Vec<Gadget>,
|
||||
pub enums: Vec<Enum>,
|
||||
}
|
||||
|
||||
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<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Class {
|
||||
pub name: String,
|
||||
pub module: String,
|
||||
pub description: Option<String>,
|
||||
pub details: Option<String>,
|
||||
pub superclass: String,
|
||||
pub singleton: bool,
|
||||
pub uncreatable: bool,
|
||||
pub properties: Vec<Property>,
|
||||
pub functions: Vec<Function>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Gadget {
|
||||
pub cname: String,
|
||||
pub properties: Vec<Property>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Property {
|
||||
#[serde(rename = "type")]
|
||||
pub type_: String,
|
||||
pub name: String,
|
||||
pub details: Option<String>,
|
||||
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<String>,
|
||||
pub params: Vec<FnParam>
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
pub module: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub details: Option<String>,
|
||||
pub varaints: Vec<Variant>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Variant {
|
||||
pub name: String,
|
||||
pub details: Option<String>,
|
||||
}
|
57
types/QtQuick.json
Normal file
57
types/QtQuick.json
Normal file
|
@ -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": []
|
||||
}
|
Loading…
Reference in a new issue