first commit
This commit is contained in:
commit
117a0fc5ed
34 changed files with 3100 additions and 0 deletions
17
.cargo/config.toml
Normal file
17
.cargo/config.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Copyright 2019-2022 Tauri Programme within The Commons Conservancy
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
[target.aarch64-unknown-linux-gnu]
|
||||||
|
linker = "aarch64-linux-gnu-gcc"
|
||||||
|
[target.aarch64-unknown-linux-musl]
|
||||||
|
linker = "aarch64-linux-musl-gcc"
|
||||||
|
rustflags = ["-C", "target-feature=-crt-static"]
|
||||||
|
[target.armv7-unknown-linux-gnueabihf]
|
||||||
|
linker = "arm-linux-gnueabihf-gcc"
|
||||||
|
[target.x86_64-pc-windows-msvc]
|
||||||
|
rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
|
[target.i686-pc-windows-msvc]
|
||||||
|
rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
|
[target.aarch64-pc-windows-msvc]
|
||||||
|
rustflags = ["-C", "target-feature=+crt-static"]
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/target/
|
||||||
|
Cargo.lock
|
24
Cargo.toml
Normal file
24
Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
members = [
|
||||||
|
"packages/cli",
|
||||||
|
# "packages/cli/node".
|
||||||
|
]
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[profile.small]
|
||||||
|
inherits = "release"
|
||||||
|
#debug = true
|
||||||
|
opt-level = 'z' # Optimize for size
|
||||||
|
lto = true # Enable link-time optimization
|
||||||
|
codegen-units = 1 # Reduce number of codegen units to increase optimizations
|
||||||
|
panic = 'abort' # Abort on panic
|
||||||
|
strip = true
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = "s"
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
panic = "abort"
|
||||||
|
strip = true
|
37
README.md
Normal file
37
README.md
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
cargo run my-app
|
||||||
|
|
||||||
|
<img src="https://makepad.nl/img/logo_makepad.svg" alt="Rapidly scaffold out a new Makepad app project." />
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
## Cargo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install create-makepad-app
|
||||||
|
cargo create-makepad-app
|
||||||
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# cargo
|
||||||
|
cargo create-makepad-app my-app --template counter --manager cargo
|
||||||
|
```
|
||||||
|
|
||||||
|
Currently supported template presets include:
|
||||||
|
|
||||||
|
- `counter`
|
||||||
|
- `simple stack navigation`
|
||||||
|
- `simple login`
|
||||||
|
|
||||||
|
You can use `.` for the project name to scaffold in the current directory.
|
||||||
|
|
||||||
|
## Semver
|
||||||
|
|
||||||
|
**create-makepad-app** is following [Semantic Versioning 2.0](https://semver.org/).
|
||||||
|
|
||||||
|
## Licenses
|
||||||
|
|
||||||
|
Code: (c) 2022.
|
||||||
|
|
||||||
|
MIT or MIT/Apache 2.0 where applicable.
|
24
packages/cli/Cargo.toml
Normal file
24
packages/cli/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
[package]
|
||||||
|
name = "create-makepad-app"
|
||||||
|
description = "Rapidly scaffold out a new makepad app project."
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "Apache-2.0 OR MIT"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://gitdab.com/andodeki/create-makepad-app"
|
||||||
|
keywords = [ "makepad" ]
|
||||||
|
categories = [ "gui" ]
|
||||||
|
exclude = [ "/node" ]
|
||||||
|
rust-version = "1.59"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "cargo-create-makepad-app"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
dialoguer = "0.10"
|
||||||
|
pico-args = "0.5"
|
||||||
|
ctrlc = "3.3"
|
||||||
|
rust-embed = { version = "8.0", features = [ "compression", "interpolate-folder-path" ] }
|
||||||
|
makepad-widgets = { version = "0.6.0" }
|
48
packages/cli/README.md
Normal file
48
packages/cli/README.md
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
## Cargo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install create-makepad-app
|
||||||
|
cargo create-makepad-app
|
||||||
|
```
|
||||||
|
|
||||||
|
# Clone this repository Need help cloning? Visit Help.
|
||||||
|
https://gitdab.com/andodeki/makepad-template.git
|
||||||
|
# Creating a new repository on the command line
|
||||||
|
touch README.md
|
||||||
|
git init
|
||||||
|
git checkout -b main
|
||||||
|
git add README.md
|
||||||
|
git commit -m "first commit"
|
||||||
|
git remote add origin https://gitdab.com/andodeki/makepad-template.git
|
||||||
|
git push -u origin main
|
||||||
|
# Pushing an existing repository from the command line
|
||||||
|
git remote add origin https://gitdab.com/andodeki/makepad-template.git
|
||||||
|
git push -u origin main
|
||||||
|
|
||||||
|
# Desktop in Debug Mode
|
||||||
|
cargo run
|
||||||
|
|
||||||
|
# Desktop in Release Mode
|
||||||
|
cargo run --release
|
||||||
|
|
||||||
|
# Desktop in small size
|
||||||
|
cargo run --profile=small
|
||||||
|
|
||||||
|
# Android
|
||||||
|
cargo makepad android run --release
|
||||||
|
|
||||||
|
# IOS Simulator
|
||||||
|
cargo makepad apple ios --org=my.test --app=makepad-template run-sim --release
|
||||||
|
|
||||||
|
# IOS Device
|
||||||
|
cargo makepad apple ios --org-id=123456 --org=my.test --app=makepad-template run-device makepad-template --release
|
||||||
|
|
||||||
|
# Cargo Check Builds
|
||||||
|
cargo makepad check install-toolchain
|
||||||
|
cargo makepad check all
|
||||||
|
|
||||||
|
cargo makepad wasm install-toolchain
|
||||||
|
cargo makepad apple ios install-toolchain
|
||||||
|
cargo makepad android --abi=all install-toolchain
|
43
packages/cli/fragments/_assets_/makepad.svg
Normal file
43
packages/cli/fragments/_assets_/makepad.svg
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 7067 1035" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(1,0,0,1,-54.1122,-304.466)">
|
||||||
|
<rect id="ArtBoard5" x="54.112" y="304.466" width="7066.93" height="1034.07" style="fill:none;"/>
|
||||||
|
<g>
|
||||||
|
<clipPath id="_clip1">
|
||||||
|
<rect x="54.112" y="304.466" width="7066.93" height="1034.07"/>
|
||||||
|
</clipPath>
|
||||||
|
<g clip-path="url(#_clip1)">
|
||||||
|
<g transform="matrix(2.46433,0,0,0.355348,0.112216,246.617)">
|
||||||
|
<g transform="matrix(0.412987,0,0,2.51071,-14964.8,-23383.5)">
|
||||||
|
<path d="M37739.3,9977.06L38019.7,10537.9L37458.9,10537.9L37739.3,9977.06Z" style="fill:rgb(255,92,57);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(-0.412987,0,0,2.51071,15723.6,-23383.5)">
|
||||||
|
<path d="M37739.3,9977.06L38019.7,10537.9L37458.9,10537.9L37739.3,9977.06Z" style="fill:rgb(255,92,57);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.412987,0,0,2.51071,-15087.3,-24885.4)">
|
||||||
|
<path d="M37739.3,9977.06L38019.7,10537.9L37458.9,10537.9L37739.3,9977.06Z" style="fill:rgb(255,92,57);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(-0.412987,0,0,2.51071,15846,-24885.4)">
|
||||||
|
<path d="M37739.3,9977.06L38019.7,10537.9L37458.9,10537.9L37739.3,9977.06Z" style="fill:rgb(255,92,57);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(-0.412987,-3.50746e-16,4.43366e-17,-2.51071,16084.4,28088.3)">
|
||||||
|
<path d="M37739.3,9977.06L38019.7,10537.9L37458.9,10537.9L37739.3,9977.06Z" style="fill:rgb(255,92,57);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.412987,-3.50746e-16,-4.43366e-17,-2.51071,-15325.6,28088.3)">
|
||||||
|
<path d="M37739.3,9977.06L38019.7,10537.9L37458.9,10537.9L37739.3,9977.06Z" style="fill:rgb(255,92,57);"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(83.3091,0,0,83.3091,-43233.8,-47386.6)">
|
||||||
|
<path d="M544.422,582.292L546.018,582.292L546.63,579.016C546.75,578.38 546.87,577.936 547.026,577.672C547.302,577.204 547.746,576.952 548.31,576.952C548.718,576.952 549.054,577.096 549.222,577.348C549.318,577.492 549.366,577.684 549.366,577.948C549.366,578.14 549.33,578.368 549.282,578.644L548.61,582.292L550.206,582.292L550.818,579.016C550.95,578.296 551.07,577.924 551.274,577.624C551.574,577.192 552.018,576.952 552.534,576.952C552.918,576.952 553.23,577.084 553.398,577.336C553.506,577.48 553.554,577.672 553.554,577.948C553.554,578.14 553.518,578.368 553.47,578.644L552.798,582.292L554.394,582.292L555.102,578.452C555.162,578.116 555.198,577.804 555.198,577.528C555.198,577.168 555.15,576.856 555.054,576.604C554.766,575.896 553.998,575.488 552.954,575.488C552.018,575.488 551.31,575.8 550.638,576.52C550.314,575.788 549.798,575.488 548.874,575.488C548.094,575.488 547.578,575.704 547.002,576.268L547.122,575.644L545.658,575.644L544.422,582.292Z" style="fill:rgb(250,225,188);fill-rule:nonzero;"/>
|
||||||
|
<path d="M563.61,575.644L562.146,575.644L561.978,576.532C561.558,575.8 560.958,575.488 559.998,575.488C558.03,575.488 556.338,576.964 555.966,578.992C555.918,579.244 555.894,579.484 555.894,579.724C555.894,581.344 556.986,582.448 558.678,582.448C559.614,582.448 560.298,582.16 561.066,581.428L560.91,582.292L562.374,582.292L563.61,575.644ZM559.866,576.952C560.874,576.952 561.522,577.588 561.522,578.548C561.522,578.692 561.498,578.836 561.474,578.992C561.258,580.18 560.322,580.984 559.17,580.984C558.15,580.984 557.526,580.372 557.526,579.448C557.526,579.316 557.538,579.172 557.562,579.028C557.79,577.828 558.75,576.952 559.866,576.952Z" style="fill:rgb(250,225,188);fill-rule:nonzero;"/>
|
||||||
|
<path d="M563.67,582.292L565.266,582.292L565.842,579.184L567.534,582.292L569.682,582.292L567.546,578.896L570.534,575.644L568.626,575.644L565.914,578.776L566.91,573.424L565.314,573.424L563.67,582.292Z" style="fill:rgb(250,225,188);fill-rule:nonzero;"/>
|
||||||
|
<path d="M576.918,579.676C577.014,579.448 577.05,579.304 577.098,579.064C577.146,578.8 577.17,578.548 577.17,578.308C577.17,576.64 576.054,575.488 574.326,575.488C572.37,575.488 570.534,577.036 570.174,578.992C570.138,579.208 570.114,579.424 570.114,579.628C570.114,581.26 571.338,582.448 573.138,582.448C574.23,582.448 575.142,582.064 575.994,581.248C576.306,580.936 576.522,580.66 576.69,580.324L574.95,580.324C574.458,580.804 574.026,580.984 573.366,580.984C572.418,580.984 571.818,580.48 571.77,579.676L576.918,579.676ZM571.986,578.272C572.394,577.408 573.126,576.952 574.074,576.952C575.058,576.952 575.622,577.42 575.67,578.272L571.986,578.272Z" style="fill:rgb(250,225,188);fill-rule:nonzero;"/>
|
||||||
|
<path d="M577.074,584.512L578.67,584.512L579.198,581.668C579.714,582.208 580.278,582.448 581.094,582.448C583.014,582.448 584.694,580.96 585.066,578.944C585.114,578.692 585.138,578.44 585.138,578.2C585.138,576.592 584.07,575.488 582.39,575.488C581.49,575.488 580.662,575.812 580.038,576.424L580.182,575.644L578.718,575.644L577.074,584.512ZM581.874,576.952C582.858,576.952 583.494,577.612 583.494,578.548C583.494,578.68 583.47,578.836 583.446,578.98C583.242,580.108 582.246,580.984 581.178,580.984C580.206,580.984 579.558,580.312 579.558,579.388C579.558,579.256 579.57,579.124 579.594,578.992C579.81,577.828 580.794,576.952 581.874,576.952Z" style="fill:rgb(250,225,188);fill-rule:nonzero;"/>
|
||||||
|
<path d="M593.37,575.644L591.906,575.644L591.738,576.532C591.318,575.8 590.718,575.488 589.758,575.488C587.79,575.488 586.098,576.964 585.726,578.992C585.678,579.244 585.654,579.484 585.654,579.724C585.654,581.344 586.746,582.448 588.438,582.448C589.374,582.448 590.058,582.16 590.826,581.428L590.67,582.292L592.134,582.292L593.37,575.644ZM589.626,576.952C590.634,576.952 591.282,577.588 591.282,578.548C591.282,578.692 591.258,578.836 591.234,578.992C591.018,580.18 590.082,580.984 588.93,580.984C587.91,580.984 587.286,580.372 587.286,579.448C587.286,579.316 587.298,579.172 587.322,579.028C587.55,577.828 588.51,576.952 589.626,576.952Z" style="fill:rgb(250,225,188);fill-rule:nonzero;"/>
|
||||||
|
<path d="M601.758,573.424L600.162,573.424L599.646,576.232C599.274,575.716 598.554,575.404 597.714,575.404C595.854,575.404 594.114,576.964 593.742,578.956C593.694,579.196 593.67,579.436 593.67,579.664C593.67,581.284 594.774,582.448 596.43,582.448C597.33,582.448 598.026,582.148 598.806,581.428L598.65,582.292L600.114,582.292L601.758,573.424ZM597.654,576.868C598.662,576.868 599.298,577.528 599.298,578.5C599.298,578.644 599.286,578.788 599.262,578.944C599.046,580.084 598.026,580.984 596.946,580.984C595.974,580.984 595.314,580.276 595.314,579.316C595.314,579.184 595.326,579.052 595.35,578.908C595.566,577.756 596.574,576.868 597.654,576.868Z" style="fill:rgb(250,225,188);fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 7.2 KiB |
109
packages/cli/fragments/_assets_/styles.css
Normal file
109
packages/cli/fragments/_assets_/styles.css
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
:root {
|
||||||
|
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
color: #0f0f0f;
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 10vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 6em;
|
||||||
|
padding: 1.5em;
|
||||||
|
will-change: filter;
|
||||||
|
transition: 0.75s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo.tauri:hover {
|
||||||
|
filter: drop-shadow(0 0 2em #24c8db);
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #646cff;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #535bf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: inherit;
|
||||||
|
color: #0f0f0f;
|
||||||
|
background-color: #ffffff;
|
||||||
|
transition: border-color 0.25s;
|
||||||
|
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
border-color: #396cd8;
|
||||||
|
}
|
||||||
|
button:active {
|
||||||
|
border-color: #396cd8;
|
||||||
|
background-color: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#greet-input {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
color: #f6f6f6;
|
||||||
|
background-color: #2f2f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #24c8db;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #0f0f0f98;
|
||||||
|
}
|
||||||
|
button:active {
|
||||||
|
background-color: #0f0f0f69;
|
||||||
|
}
|
||||||
|
}
|
3
packages/cli/fragments/fragment-counter/.makepadignore
Normal file
3
packages/cli/fragments/fragment-counter/.makepadignore
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/src
|
||||||
|
/public
|
||||||
|
/Cargo.toml
|
3
packages/cli/fragments/fragment-counter/.vscode/extensions.json
vendored
Normal file
3
packages/cli/fragments/fragment-counter/.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
|
||||||
|
}
|
5
packages/cli/fragments/fragment-counter/.vscode/settings.json
vendored
Normal file
5
packages/cli/fragments/fragment-counter/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"emmet.includeLanguages": {
|
||||||
|
"rust": "html"
|
||||||
|
}
|
||||||
|
}
|
25
packages/cli/fragments/fragment-counter/Cargo.toml.lts
Normal file
25
packages/cli/fragments/fragment-counter/Cargo.toml.lts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
[package]
|
||||||
|
name = "{% package_name %}"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[profile.small]
|
||||||
|
inherits = "release"
|
||||||
|
#debug = true
|
||||||
|
opt-level = 'z' # Optimize for size
|
||||||
|
lto = true # Enable link-time optimization
|
||||||
|
codegen-units = 1 # Reduce number of codegen units to increase optimizations
|
||||||
|
panic = 'abort' # Abort on panic
|
||||||
|
strip = true
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = "s"
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
panic = "abort"
|
||||||
|
strip = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
makepad-widgets = { version = "0.6.0" }
|
34
packages/cli/fragments/fragment-counter/README.md
Normal file
34
packages/cli/fragments/fragment-counter/README.md
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# Makepad UI
|
||||||
|
|
||||||
|
This template should help get you started developing with Makepad Rust UI.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VS Code](https://code.visualstudio.com/) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer).
|
||||||
|
|
||||||
|
|
||||||
|
# Desktop in Debug Mode
|
||||||
|
cargo run
|
||||||
|
|
||||||
|
# Desktop in Release Mode
|
||||||
|
cargo run --release
|
||||||
|
|
||||||
|
# Desktop in small size
|
||||||
|
cargo run --profile=small
|
||||||
|
|
||||||
|
# Android
|
||||||
|
cargo makepad android run --release
|
||||||
|
|
||||||
|
# IOS Simulator
|
||||||
|
cargo makepad apple ios --org=my.test --app=makepad-template run-sim --release
|
||||||
|
|
||||||
|
# IOS Device
|
||||||
|
cargo makepad apple ios --org-id=123456 --org=my.test --app=makepad-template run-device makepad-template --release
|
||||||
|
|
||||||
|
# Cargo Check Builds
|
||||||
|
cargo makepad check install-toolchain
|
||||||
|
cargo makepad check all
|
||||||
|
|
||||||
|
cargo makepad wasm install-toolchain
|
||||||
|
cargo makepad apple ios install-toolchain
|
||||||
|
cargo makepad android --abi=all install-toolchain
|
11
packages/cli/fragments/fragment-counter/Trunk.toml.lts
Normal file
11
packages/cli/fragments/fragment-counter/Trunk.toml.lts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[build]
|
||||||
|
target = "./index.html"
|
||||||
|
|
||||||
|
[watch]
|
||||||
|
ignore = ["./src-tauri"]
|
||||||
|
|
||||||
|
[serve]
|
||||||
|
address = "{% if mobile %}0.0.0.0{% else %}127.0.0.1{% endif %}"
|
||||||
|
port = 1420
|
||||||
|
open = false{% if mobile %}
|
||||||
|
ws_protocol = "ws"{% endif %}
|
13
packages/cli/fragments/fragment-counter/_cta_manifest_
Normal file
13
packages/cli/fragments/fragment-counter/_cta_manifest_
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Copyright 2019-2022 Tauri Programme within The Commons Conservancy
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
beforeDevCommand = trunk serve
|
||||||
|
beforeBuildCommand = trunk build
|
||||||
|
devPath = http://localhost:1420
|
||||||
|
distDir = ../dist
|
||||||
|
withGlobalTauri = true
|
||||||
|
|
||||||
|
[files]
|
||||||
|
makepad.svg = resources/makepad.svg
|
||||||
|
styles.css = styles.css
|
3
packages/cli/fragments/fragment-counter/_gitignore
Normal file
3
packages/cli/fragments/fragment-counter/_gitignore
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/dist/
|
||||||
|
/target/
|
||||||
|
/Cargo.lock
|
34
packages/cli/fragments/fragment-counter/index.html.lts
Normal file
34
packages/cli/fragments/fragment-counter/index.html.lts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||||
|
<title>Makepad Template</title>
|
||||||
|
<script type='module'>
|
||||||
|
import {WasmWebGL} from "/{% package_name %}/platform/src/os/web/web_gl.js"
|
||||||
|
|
||||||
|
const wasm = await WasmWebGL.fetch_and_instantiate_wasm(
|
||||||
|
"/{% package_name %}/target/wasm32-unknown-unknown/release/{% package_name %}.wasm"
|
||||||
|
);
|
||||||
|
|
||||||
|
class MyWasmApp {
|
||||||
|
constructor(wasm) {
|
||||||
|
let canvas = document.getElementsByClassName('full_canvas')[0];
|
||||||
|
this.webgl = new WasmWebGL (wasm, this, canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let app = new MyWasmApp(wasm);
|
||||||
|
</script>
|
||||||
|
<script type='module' src='/{% package_name %}/platform/src/os/web/auto_reload.js'></script>
|
||||||
|
<link rel='stylesheet' type='text/css' href='/{% package_name %}/platform/src/os/web/full_canvas.css'>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas class='full_canvas'></canvas>
|
||||||
|
<div class='canvas_loader' >
|
||||||
|
<div style=''>
|
||||||
|
Loading..
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 7067 1035" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(1,0,0,1,-54.1122,-304.466)">
|
||||||
|
<rect id="ArtBoard5" x="54.112" y="304.466" width="7066.93" height="1034.07" style="fill:none;"/>
|
||||||
|
<g>
|
||||||
|
<clipPath id="_clip1">
|
||||||
|
<rect x="54.112" y="304.466" width="7066.93" height="1034.07"/>
|
||||||
|
</clipPath>
|
||||||
|
<g clip-path="url(#_clip1)">
|
||||||
|
<g transform="matrix(2.46433,0,0,0.355348,0.112216,246.617)">
|
||||||
|
<g transform="matrix(0.412987,0,0,2.51071,-14964.8,-23383.5)">
|
||||||
|
<path d="M37739.3,9977.06L38019.7,10537.9L37458.9,10537.9L37739.3,9977.06Z" style="fill:rgb(255,92,57);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(-0.412987,0,0,2.51071,15723.6,-23383.5)">
|
||||||
|
<path d="M37739.3,9977.06L38019.7,10537.9L37458.9,10537.9L37739.3,9977.06Z" style="fill:rgb(255,92,57);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.412987,0,0,2.51071,-15087.3,-24885.4)">
|
||||||
|
<path d="M37739.3,9977.06L38019.7,10537.9L37458.9,10537.9L37739.3,9977.06Z" style="fill:rgb(255,92,57);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(-0.412987,0,0,2.51071,15846,-24885.4)">
|
||||||
|
<path d="M37739.3,9977.06L38019.7,10537.9L37458.9,10537.9L37739.3,9977.06Z" style="fill:rgb(255,92,57);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(-0.412987,-3.50746e-16,4.43366e-17,-2.51071,16084.4,28088.3)">
|
||||||
|
<path d="M37739.3,9977.06L38019.7,10537.9L37458.9,10537.9L37739.3,9977.06Z" style="fill:rgb(255,92,57);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(0.412987,-3.50746e-16,-4.43366e-17,-2.51071,-15325.6,28088.3)">
|
||||||
|
<path d="M37739.3,9977.06L38019.7,10537.9L37458.9,10537.9L37739.3,9977.06Z" style="fill:rgb(255,92,57);"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(83.3091,0,0,83.3091,-43233.8,-47386.6)">
|
||||||
|
<path d="M544.422,582.292L546.018,582.292L546.63,579.016C546.75,578.38 546.87,577.936 547.026,577.672C547.302,577.204 547.746,576.952 548.31,576.952C548.718,576.952 549.054,577.096 549.222,577.348C549.318,577.492 549.366,577.684 549.366,577.948C549.366,578.14 549.33,578.368 549.282,578.644L548.61,582.292L550.206,582.292L550.818,579.016C550.95,578.296 551.07,577.924 551.274,577.624C551.574,577.192 552.018,576.952 552.534,576.952C552.918,576.952 553.23,577.084 553.398,577.336C553.506,577.48 553.554,577.672 553.554,577.948C553.554,578.14 553.518,578.368 553.47,578.644L552.798,582.292L554.394,582.292L555.102,578.452C555.162,578.116 555.198,577.804 555.198,577.528C555.198,577.168 555.15,576.856 555.054,576.604C554.766,575.896 553.998,575.488 552.954,575.488C552.018,575.488 551.31,575.8 550.638,576.52C550.314,575.788 549.798,575.488 548.874,575.488C548.094,575.488 547.578,575.704 547.002,576.268L547.122,575.644L545.658,575.644L544.422,582.292Z" style="fill:rgb(250,225,188);fill-rule:nonzero;"/>
|
||||||
|
<path d="M563.61,575.644L562.146,575.644L561.978,576.532C561.558,575.8 560.958,575.488 559.998,575.488C558.03,575.488 556.338,576.964 555.966,578.992C555.918,579.244 555.894,579.484 555.894,579.724C555.894,581.344 556.986,582.448 558.678,582.448C559.614,582.448 560.298,582.16 561.066,581.428L560.91,582.292L562.374,582.292L563.61,575.644ZM559.866,576.952C560.874,576.952 561.522,577.588 561.522,578.548C561.522,578.692 561.498,578.836 561.474,578.992C561.258,580.18 560.322,580.984 559.17,580.984C558.15,580.984 557.526,580.372 557.526,579.448C557.526,579.316 557.538,579.172 557.562,579.028C557.79,577.828 558.75,576.952 559.866,576.952Z" style="fill:rgb(250,225,188);fill-rule:nonzero;"/>
|
||||||
|
<path d="M563.67,582.292L565.266,582.292L565.842,579.184L567.534,582.292L569.682,582.292L567.546,578.896L570.534,575.644L568.626,575.644L565.914,578.776L566.91,573.424L565.314,573.424L563.67,582.292Z" style="fill:rgb(250,225,188);fill-rule:nonzero;"/>
|
||||||
|
<path d="M576.918,579.676C577.014,579.448 577.05,579.304 577.098,579.064C577.146,578.8 577.17,578.548 577.17,578.308C577.17,576.64 576.054,575.488 574.326,575.488C572.37,575.488 570.534,577.036 570.174,578.992C570.138,579.208 570.114,579.424 570.114,579.628C570.114,581.26 571.338,582.448 573.138,582.448C574.23,582.448 575.142,582.064 575.994,581.248C576.306,580.936 576.522,580.66 576.69,580.324L574.95,580.324C574.458,580.804 574.026,580.984 573.366,580.984C572.418,580.984 571.818,580.48 571.77,579.676L576.918,579.676ZM571.986,578.272C572.394,577.408 573.126,576.952 574.074,576.952C575.058,576.952 575.622,577.42 575.67,578.272L571.986,578.272Z" style="fill:rgb(250,225,188);fill-rule:nonzero;"/>
|
||||||
|
<path d="M577.074,584.512L578.67,584.512L579.198,581.668C579.714,582.208 580.278,582.448 581.094,582.448C583.014,582.448 584.694,580.96 585.066,578.944C585.114,578.692 585.138,578.44 585.138,578.2C585.138,576.592 584.07,575.488 582.39,575.488C581.49,575.488 580.662,575.812 580.038,576.424L580.182,575.644L578.718,575.644L577.074,584.512ZM581.874,576.952C582.858,576.952 583.494,577.612 583.494,578.548C583.494,578.68 583.47,578.836 583.446,578.98C583.242,580.108 582.246,580.984 581.178,580.984C580.206,580.984 579.558,580.312 579.558,579.388C579.558,579.256 579.57,579.124 579.594,578.992C579.81,577.828 580.794,576.952 581.874,576.952Z" style="fill:rgb(250,225,188);fill-rule:nonzero;"/>
|
||||||
|
<path d="M593.37,575.644L591.906,575.644L591.738,576.532C591.318,575.8 590.718,575.488 589.758,575.488C587.79,575.488 586.098,576.964 585.726,578.992C585.678,579.244 585.654,579.484 585.654,579.724C585.654,581.344 586.746,582.448 588.438,582.448C589.374,582.448 590.058,582.16 590.826,581.428L590.67,582.292L592.134,582.292L593.37,575.644ZM589.626,576.952C590.634,576.952 591.282,577.588 591.282,578.548C591.282,578.692 591.258,578.836 591.234,578.992C591.018,580.18 590.082,580.984 588.93,580.984C587.91,580.984 587.286,580.372 587.286,579.448C587.286,579.316 587.298,579.172 587.322,579.028C587.55,577.828 588.51,576.952 589.626,576.952Z" style="fill:rgb(250,225,188);fill-rule:nonzero;"/>
|
||||||
|
<path d="M601.758,573.424L600.162,573.424L599.646,576.232C599.274,575.716 598.554,575.404 597.714,575.404C595.854,575.404 594.114,576.964 593.742,578.956C593.694,579.196 593.67,579.436 593.67,579.664C593.67,581.284 594.774,582.448 596.43,582.448C597.33,582.448 598.026,582.148 598.806,581.428L598.65,582.292L600.114,582.292L601.758,573.424ZM597.654,576.868C598.662,576.868 599.298,577.528 599.298,578.5C599.298,578.644 599.286,578.788 599.262,578.944C599.046,580.084 598.026,580.984 596.946,580.984C595.974,580.984 595.314,580.276 595.314,579.316C595.314,579.184 595.326,579.052 595.35,578.908C595.566,577.756 596.574,576.868 597.654,576.868Z" style="fill:rgb(250,225,188);fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 7.2 KiB |
91
packages/cli/fragments/fragment-counter/src/app.rs
Normal file
91
packages/cli/fragments/fragment-counter/src/app.rs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
use makepad_widgets::*;
|
||||||
|
|
||||||
|
live_design!{
|
||||||
|
import makepad_widgets::base::*;
|
||||||
|
import makepad_widgets::theme_desktop_dark::*;
|
||||||
|
|
||||||
|
import crate::shared::styles::*;
|
||||||
|
|
||||||
|
LOGO_MAKEPAD = dep("crate://self/resources/makepad.svg")
|
||||||
|
|
||||||
|
|
||||||
|
App = {{App}} {
|
||||||
|
|
||||||
|
ui: <Window>{
|
||||||
|
show_bg: true
|
||||||
|
width: Fill,
|
||||||
|
height: Fill
|
||||||
|
|
||||||
|
draw_bg: {
|
||||||
|
fn pixel(self) -> vec4 {
|
||||||
|
//return #000
|
||||||
|
return mix(#7, #3, self.pos.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body = <View>{
|
||||||
|
|
||||||
|
flow: Down,
|
||||||
|
spacing: 20,
|
||||||
|
align: {
|
||||||
|
x: 0.5,
|
||||||
|
y: 0.5
|
||||||
|
},
|
||||||
|
avatar = <Image> {
|
||||||
|
source: (LOGO_MAKEPAD),
|
||||||
|
width: 50., height: 50.
|
||||||
|
}
|
||||||
|
button1 = <Button> {
|
||||||
|
text: "Hello world"
|
||||||
|
}
|
||||||
|
input1 = <TextInput> {
|
||||||
|
width: 100, height: 30
|
||||||
|
text: "Click to count"
|
||||||
|
}
|
||||||
|
|
||||||
|
label1 = <Label> {
|
||||||
|
draw_text: {
|
||||||
|
color: #f
|
||||||
|
},
|
||||||
|
text: "Counter: 0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app_main!(App);
|
||||||
|
|
||||||
|
#[derive(Live)]
|
||||||
|
pub struct App {
|
||||||
|
#[live] ui: WidgetRef,
|
||||||
|
#[rust] counter: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LiveHook for App {
|
||||||
|
fn before_live_design(cx: &mut Cx) {
|
||||||
|
crate::makepad_widgets::live_design(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App{
|
||||||
|
async fn _do_network_request(_cx:CxRef, _ui:WidgetRef, _url:&str)->String{
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppMain for App{
|
||||||
|
fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
|
||||||
|
if let Event::Draw(event) = event {
|
||||||
|
return self.ui.draw_widget_all(&mut Cx2d::new(cx, event));
|
||||||
|
}
|
||||||
|
let actions = self.ui.handle_widget_event(cx, event);
|
||||||
|
|
||||||
|
if self.ui.button(id!(button1)).clicked(&actions) {
|
||||||
|
log!("BUTTON CLICKED {}", self.counter);
|
||||||
|
self.counter += 1;
|
||||||
|
let label = self.ui.label(id!(label1));
|
||||||
|
label.set_text_and_redraw(cx,&format!("Counter: {}", self.counter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
packages/cli/fragments/fragment-counter/src/lib.rs
Normal file
2
packages/cli/fragments/fragment-counter/src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub use makepad_widgets;
|
||||||
|
pub mod app;
|
6
packages/cli/fragments/fragment-counter/src/main.rs.lts
Normal file
6
packages/cli/fragments/fragment-counter/src/main.rs.lts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// this stub is necessary because some platforms require building
|
||||||
|
// as dll (mobile / wasm) and some require to be built as executable
|
||||||
|
// unfortunately cargo doesn't facilitate this without a main.rs stub
|
||||||
|
fn main(){
|
||||||
|
{% package_name %}::app::app_main()
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod styles;
|
22
packages/cli/fragments/fragment-counter/src/shared/styles.rs
Normal file
22
packages/cli/fragments/fragment-counter/src/shared/styles.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
use makepad_widgets::*;
|
||||||
|
|
||||||
|
live_design! {
|
||||||
|
TITLE_TEXT = {
|
||||||
|
font_size: (14),
|
||||||
|
font: {path: dep("crate://makepad-widgets/resources/GoNotoKurrent-Regular.ttf")}
|
||||||
|
}
|
||||||
|
|
||||||
|
REGULAR_TEXT = {
|
||||||
|
font_size: (12),
|
||||||
|
font: {path: dep("crate://makepad-widgets/resources/GoNotoKurrent-Regular.ttf")}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEXT_SUB = {
|
||||||
|
font_size: (FONT_SIZE_SUB),
|
||||||
|
font: {path: dep("crate://makepad-widgets/resources/GoNotoKurrent-Regular.ttf")}
|
||||||
|
}
|
||||||
|
|
||||||
|
COLOR_PROFILE_CIRCLE = #xfff8ee
|
||||||
|
COLOR_DIVIDER = #x00000018
|
||||||
|
COLOR_DIVIDER_DARK = #x00000044
|
||||||
|
}
|
40
packages/cli/src/category.rs
Normal file
40
packages/cli/src/category.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use crate::package_manager::PackageManager;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Category {
|
||||||
|
Rust,
|
||||||
|
}
|
||||||
|
impl Default for Category {
|
||||||
|
fn default() -> Self {
|
||||||
|
Category::Rust
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Category {
|
||||||
|
pub const ALL: &'a [Self] = &[Category::Rust];
|
||||||
|
|
||||||
|
pub const fn package_managers(&self) -> &[PackageManager] {
|
||||||
|
match self {
|
||||||
|
Category::Rust => &[PackageManager::Cargo],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Category {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let managers = self
|
||||||
|
.package_managers()
|
||||||
|
.to_vec()
|
||||||
|
.iter()
|
||||||
|
.map(|p| p.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
match self {
|
||||||
|
Category::Rust => write!(f, "Rust - ({managers})"),
|
||||||
|
// Category::JsTs => write!(f, "TypeScript / JavaScript - ({managers})"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
93
packages/cli/src/cli.rs
Normal file
93
packages/cli/src/cli.rs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
use std::ffi::OsString;
|
||||||
|
|
||||||
|
use pico_args::Arguments;
|
||||||
|
|
||||||
|
use crate::{colors::*, package_manager::PackageManager, template::Template};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Args {
|
||||||
|
pub project_name: Option<String>,
|
||||||
|
pub manager: Option<PackageManager>,
|
||||||
|
pub template: Option<Template>,
|
||||||
|
pub skip: bool,
|
||||||
|
pub alpha: bool,
|
||||||
|
pub mobile: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Args {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
project_name: Some("makepad-app".to_string()),
|
||||||
|
manager: Some(PackageManager::Cargo),
|
||||||
|
template: Some(Template::MakepadCounter),
|
||||||
|
skip: false,
|
||||||
|
alpha: false,
|
||||||
|
mobile: Some(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(argv: Vec<OsString>, bin_name: Option<String>) -> anyhow::Result<Args> {
|
||||||
|
let mut pargs = Arguments::from_vec(argv);
|
||||||
|
|
||||||
|
if pargs.contains(["-h", "--help"]) {
|
||||||
|
let help = format!(
|
||||||
|
r#"
|
||||||
|
{GREEN}{name}{RESET} {version}
|
||||||
|
{authors}
|
||||||
|
{desc}
|
||||||
|
|
||||||
|
{YELLOW}USAGE:{RESET}
|
||||||
|
{name} [OPTIONS] [PROJECTNAME]
|
||||||
|
|
||||||
|
{YELLOW}ARGS:{RESET}
|
||||||
|
{GREEN}<PROJECTNAME>{RESET} Specify project name which is used for the directory, package.json and Cargo.toml
|
||||||
|
|
||||||
|
{YELLOW}OPTIONS:{RESET}
|
||||||
|
{GREEN}-m{RESET}, {GREEN}--manager <MANAGER>{RESET} Specify preferred package manager [{managers}]
|
||||||
|
{GREEN}-t{RESET}, {GREEN}--template <TEMPLATE>{RESET} Specify the UI template to use [{fragments}]
|
||||||
|
{GREEN}-y{RESET}, {GREEN}--yes{RESET} Skip prompts and use defaults where applicable
|
||||||
|
{GREEN}--alpha{RESET} Bootstraps a project using tauri@2.0-alpha
|
||||||
|
{GREEN}--mobile{RESET} Bootstraps a mobile project too. Only availabe with `--alpha` option.
|
||||||
|
{GREEN}-h{RESET}, {GREEN}--help{RESET} Prints help information
|
||||||
|
{GREEN}-v{RESET}, {GREEN}--version{RESET} Prints version information
|
||||||
|
"#,
|
||||||
|
name = bin_name.unwrap_or_else(|| env!("CARGO_PKG_NAME").to_string()),
|
||||||
|
version = env!("CARGO_PKG_VERSION"),
|
||||||
|
authors = env!("CARGO_PKG_AUTHORS"),
|
||||||
|
desc = env!("CARGO_PKG_DESCRIPTION"),
|
||||||
|
managers = PackageManager::ALL
|
||||||
|
.iter()
|
||||||
|
.map(|e| format!("{GREEN}{e}{RESET}"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "),
|
||||||
|
fragments = Template::ALL
|
||||||
|
.iter()
|
||||||
|
.map(|e| format!("{GREEN}{e}{RESET}"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "),
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("{help}");
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
if pargs.contains(["-v", "--version"]) {
|
||||||
|
println!("{}", env!("CARGO_PKG_VERSION"));
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let args = Args {
|
||||||
|
manager: pargs.opt_value_from_str(["-m", "--manager"])?,
|
||||||
|
template: pargs.opt_value_from_str(["-t", "--template"])?,
|
||||||
|
skip: pargs.contains(["-y", "--yes"]),
|
||||||
|
alpha: pargs.contains("--alpha"),
|
||||||
|
mobile: if pargs.contains("--mobile") {
|
||||||
|
Some(true)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
project_name: pargs.opt_free_from_str()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(args)
|
||||||
|
}
|
27
packages/cli/src/colors.rs
Normal file
27
packages/cli/src/colors.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
|
pub const BLACK: &str = "\x1b[30m";
|
||||||
|
pub const RED: &str = "\x1b[31m";
|
||||||
|
pub const GREEN: &str = "\x1b[32m";
|
||||||
|
pub const YELLOW: &str = "\x1b[33m";
|
||||||
|
pub const BLUE: &str = "\x1b[34m";
|
||||||
|
pub const WHITE: &str = "\x1b[37m";
|
||||||
|
pub const RESET: &str = "\x1b[0m";
|
||||||
|
pub const BOLD: &str = "\x1b[1m";
|
||||||
|
pub const ITALIC: &str = "\x1b[3m";
|
||||||
|
pub const DIM: &str = "\x1b[2m";
|
||||||
|
pub const DIMRESET: &str = "\x1b[22m";
|
||||||
|
|
||||||
|
pub fn remove_colors(s: &str) -> String {
|
||||||
|
s.replace(BLACK, "")
|
||||||
|
.replace(RED, "")
|
||||||
|
.replace(GREEN, "")
|
||||||
|
.replace(YELLOW, "")
|
||||||
|
.replace(BLUE, "")
|
||||||
|
.replace(WHITE, "")
|
||||||
|
.replace(RESET, "")
|
||||||
|
.replace(BOLD, "")
|
||||||
|
.replace(ITALIC, "")
|
||||||
|
.replace(DIM, "")
|
||||||
|
.replace(DIMRESET, "")
|
||||||
|
}
|
324
packages/cli/src/deps.rs
Normal file
324
packages/cli/src/deps.rs
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
use template::Template;
|
||||||
|
|
||||||
|
use crate::colors::*;
|
||||||
|
use crate::internal::template;
|
||||||
|
use crate::package_manager::PackageManager;
|
||||||
|
use std::process::{Command, Output};
|
||||||
|
|
||||||
|
fn is_rustc_installed() -> bool {
|
||||||
|
Command::new("rustc")
|
||||||
|
.arg("-V")
|
||||||
|
.output()
|
||||||
|
.map(|o| o.status.success())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
fn is_cargo_installed() -> bool {
|
||||||
|
Command::new("cargo")
|
||||||
|
.arg("-V")
|
||||||
|
.output()
|
||||||
|
.map(|o| o.status.success())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
fn is_node_installed() -> bool {
|
||||||
|
Command::new("node")
|
||||||
|
.arg("-v")
|
||||||
|
.output()
|
||||||
|
.map(|o| o.status.success())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_trunk_installed() -> bool {
|
||||||
|
Command::new("trunk")
|
||||||
|
.arg("-V")
|
||||||
|
.output()
|
||||||
|
.map(|o| o.status.success())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
fn is_appropriate_makepad_cli_installed(alpha: bool) -> bool {
|
||||||
|
let check = |o: Output| match o.status.success() {
|
||||||
|
true if alpha => String::from_utf8_lossy(&o.stderr)
|
||||||
|
.split_once(' ')
|
||||||
|
.map(|(_, v)| v.starts_with('2'))
|
||||||
|
.unwrap_or(false),
|
||||||
|
s => s,
|
||||||
|
};
|
||||||
|
Command::new("cargo")
|
||||||
|
.args(["makepad", "-V"])
|
||||||
|
.output()
|
||||||
|
.map(check)
|
||||||
|
.or_else(|_| Command::new("makepad").arg("-V").output().map(check))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
fn is_wasm32_installed() -> bool {
|
||||||
|
Command::new("rustup")
|
||||||
|
.args(["target", "list", "--installed"])
|
||||||
|
.output()
|
||||||
|
.map(|o| {
|
||||||
|
let s = String::from_utf8_lossy(&o.stdout);
|
||||||
|
s.contains("wasm32-unknown-unknown")
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn is_webview2_installed() -> bool {
|
||||||
|
let powershell_path = std::env::var("SYSTEMROOT").map_or_else(
|
||||||
|
|_| "powershell.exe".to_string(),
|
||||||
|
|p| format!("{p}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"),
|
||||||
|
);
|
||||||
|
// check 64bit per-system installation
|
||||||
|
let output = Command::new(&powershell_path)
|
||||||
|
.args(["-NoProfile", "-Command"])
|
||||||
|
.arg("Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\WOW6432Node\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
|
||||||
|
.output().map(|o|o.status.success());
|
||||||
|
if let Ok(o) = output {
|
||||||
|
if o {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check 32bit per-system installation
|
||||||
|
let output = Command::new(&powershell_path)
|
||||||
|
.args(["-NoProfile", "-Command"])
|
||||||
|
.arg("Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
|
||||||
|
.output().map(|o|o.status.success());
|
||||||
|
if let Ok(o) = output {
|
||||||
|
if o {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check per-user installation
|
||||||
|
let output = Command::new(&powershell_path)
|
||||||
|
.args(["-NoProfile", "-Command"])
|
||||||
|
.arg("Get-ItemProperty -Path 'HKCU:\\SOFTWARE\\Microsoft\\EdgeUpdate\\Clients\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}' | ForEach-Object {$_.pv}")
|
||||||
|
.output().map(|o|o.status.success());
|
||||||
|
if let Ok(o) = output {
|
||||||
|
if o {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "netbsd"
|
||||||
|
))]
|
||||||
|
fn is_webkit2gtk_installed(alpha: bool) -> bool {
|
||||||
|
Command::new("pkg-config")
|
||||||
|
.arg(if alpha {
|
||||||
|
"webkit2gtk-4.1"
|
||||||
|
} else {
|
||||||
|
"webkit2gtk-4.0"
|
||||||
|
})
|
||||||
|
.output()
|
||||||
|
.map(|o| o.status.success())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "netbsd"
|
||||||
|
))]
|
||||||
|
fn is_rsvg2_installed() -> bool {
|
||||||
|
Command::new("pkg-config")
|
||||||
|
.arg("librsvg-2.0")
|
||||||
|
.output()
|
||||||
|
.map(|o| o.status.success())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn is_xcode_command_line_tools_installed() -> bool {
|
||||||
|
Command::new("xcode-select")
|
||||||
|
.arg("-p")
|
||||||
|
.output()
|
||||||
|
.map(|o| o.status.success())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_missing_deps(pkg_manager: PackageManager, template: Template, alpha: bool) {
|
||||||
|
let rustc_installed = is_rustc_installed();
|
||||||
|
let cargo_installed = is_cargo_installed();
|
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "netbsd"
|
||||||
|
))]
|
||||||
|
let (webkit2gtk_installed, rsvg2_installed) =
|
||||||
|
(is_webkit2gtk_installed(alpha), is_rsvg2_installed());
|
||||||
|
|
||||||
|
let deps: &[(&str, String, &dyn Fn() -> bool, bool)] = &[
|
||||||
|
(
|
||||||
|
"Rust",
|
||||||
|
format!("Visit {BLUE}{BOLD}https://www.rust-lang.org/learn/get-started#installing-rust{RESET}"),
|
||||||
|
&|| rustc_installed && cargo_installed,
|
||||||
|
rustc_installed || cargo_installed,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"rustc",
|
||||||
|
format!("Visit {BLUE}{BOLD}https://www.rust-lang.org/learn/get-started#installing-rust{RESET} to install Rust"),
|
||||||
|
&|| rustc_installed,
|
||||||
|
!rustc_installed && !cargo_installed,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Cargo",
|
||||||
|
format!("Visit {BLUE}{BOLD}https://www.rust-lang.org/learn/get-started#installing-rust{RESET} to install Rust"),
|
||||||
|
&|| cargo_installed,
|
||||||
|
!rustc_installed && !cargo_installed,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Makepad CLI",
|
||||||
|
if alpha {
|
||||||
|
format!("Run `{BLUE}{BOLD}cargo install makepad-cli --version '^2.0.0-alpha'{RESET}`")
|
||||||
|
} else {
|
||||||
|
format!("Run `{BLUE}{BOLD}cargo install makepad-cli{RESET}`")
|
||||||
|
},
|
||||||
|
&|| is_appropriate_makepad_cli_installed(alpha),
|
||||||
|
!template.needs_makepad_cli(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Trunk",
|
||||||
|
if alpha {
|
||||||
|
format!("Run `{BLUE}{BOLD}cargo install trunk --git https://github.com/amrbashir/trunk{RESET}`")
|
||||||
|
} else {
|
||||||
|
format!("Run `{BLUE}{BOLD}cargo install trunk{RESET}`")
|
||||||
|
},
|
||||||
|
&is_trunk_installed,
|
||||||
|
!template.needs_trunk(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"wasm32 target",
|
||||||
|
format!("Run `{BLUE}{BOLD}rustup target add wasm32-unknown-unknown{RESET}`"),
|
||||||
|
&is_wasm32_installed,
|
||||||
|
!template.needs_wasm32_target(),
|
||||||
|
),
|
||||||
|
#[cfg(windows)]
|
||||||
|
(
|
||||||
|
"Webview2",
|
||||||
|
format!("Visit {BLUE}{BOLD}https://go.microsoft.com/fwlink/p/?LinkId=2124703{RESET}"),
|
||||||
|
&is_webview2_installed,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "netbsd"
|
||||||
|
))]
|
||||||
|
(
|
||||||
|
"webkit2gtk & rsvg2",
|
||||||
|
format!("Visit {BLUE}{BOLD}{}{RESET}", if alpha {
|
||||||
|
"https://next--tauri.netlify.app/next/guides/getting-started/prerequisites/linux#1-system-dependencies"
|
||||||
|
} else {
|
||||||
|
"https://tauri.app/v1/guides/getting-started/prerequisites#setting-up-linux"
|
||||||
|
}),
|
||||||
|
&|| webkit2gtk_installed && rsvg2_installed,
|
||||||
|
webkit2gtk_installed || rsvg2_installed,
|
||||||
|
),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "netbsd"
|
||||||
|
))]
|
||||||
|
(
|
||||||
|
"webkit2gtk",
|
||||||
|
format!("Visit {BLUE}{BOLD}{}{RESET}", if alpha {
|
||||||
|
"https://next--tauri.netlify.app/next/guides/getting-started/prerequisites/linux#1-system-dependencies"
|
||||||
|
} else {
|
||||||
|
"https://tauri.app/v1/guides/getting-started/prerequisites#setting-up-linux"
|
||||||
|
}),
|
||||||
|
&|| webkit2gtk_installed,
|
||||||
|
!rsvg2_installed && !webkit2gtk_installed,
|
||||||
|
),
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "netbsd"
|
||||||
|
))]
|
||||||
|
(
|
||||||
|
"rsvg2",
|
||||||
|
format!("Visit {BLUE}{BOLD}{}{RESET}", if alpha {
|
||||||
|
"https://next--tauri.netlify.app/next/guides/getting-started/prerequisites/linux#1-system-dependencies"
|
||||||
|
} else {
|
||||||
|
"https://tauri.app/v1/guides/getting-started/prerequisites#setting-up-linux"
|
||||||
|
}),
|
||||||
|
&|| rsvg2_installed,
|
||||||
|
!rsvg2_installed && !webkit2gtk_installed,
|
||||||
|
),
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
(
|
||||||
|
"Xcode Command Line Tools",
|
||||||
|
format!("Run `{BLUE}{BOLD}xcode-select --install{RESET}`"),
|
||||||
|
&is_xcode_command_line_tools_installed,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let missing_deps: Vec<(String, String)> = deps
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, _, exists, skip)| !skip && !exists())
|
||||||
|
.map(|(s, d, _, _)| (s.to_string(), d.clone()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let (largest_first_cell, largest_second_cell) =
|
||||||
|
missing_deps
|
||||||
|
.iter()
|
||||||
|
.fold((0, 0), |(mut prev_f, mut prev_s), (f, s)| {
|
||||||
|
let f_len = f.len();
|
||||||
|
if f_len > prev_f {
|
||||||
|
prev_f = f_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
let s_len = remove_colors(s).len();
|
||||||
|
if s_len > prev_s {
|
||||||
|
prev_s = s_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
(prev_f, prev_s)
|
||||||
|
});
|
||||||
|
|
||||||
|
if !missing_deps.is_empty() {
|
||||||
|
println!("\n\nYour system is {YELLOW}missing dependencies{RESET} (or they do not exist in {YELLOW}$PATH{RESET}):");
|
||||||
|
for (index, (name, instruction)) in missing_deps.iter().enumerate() {
|
||||||
|
if index == 0 {
|
||||||
|
println!(
|
||||||
|
"╭{}┬{}╮",
|
||||||
|
"─".repeat(largest_first_cell + 2),
|
||||||
|
"─".repeat(largest_second_cell + 2)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!(
|
||||||
|
"├{}┼{}┤",
|
||||||
|
"─".repeat(largest_first_cell + 2),
|
||||||
|
"─".repeat(largest_second_cell + 2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"│ {YELLOW}{name}{RESET}{} │ {instruction}{} │",
|
||||||
|
" ".repeat(largest_first_cell - name.len()),
|
||||||
|
" ".repeat(largest_second_cell - remove_colors(instruction).len()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"╰{}┴{}╯",
|
||||||
|
"─".repeat(largest_first_cell + 2),
|
||||||
|
"─".repeat(largest_second_cell + 2),
|
||||||
|
);
|
||||||
|
println!();
|
||||||
|
println!("Make sure you have installed the prerequisites for your OS: {BLUE}{BOLD}https://tauri.app/v1/guides/getting-started/prerequisites{RESET}, then run:");
|
||||||
|
} else {
|
||||||
|
println!(" To get started run:")
|
||||||
|
}
|
||||||
|
}
|
427
packages/cli/src/lib.rs
Normal file
427
packages/cli/src/lib.rs
Normal file
|
@ -0,0 +1,427 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
use dialoguer::{Confirm, Input, Select};
|
||||||
|
use std::{ffi::OsString, fs, process::exit};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
category::Category,
|
||||||
|
colors::*,
|
||||||
|
deps::print_missing_deps,
|
||||||
|
package_manager::PackageManager,
|
||||||
|
theme::ColorfulTheme,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod category;
|
||||||
|
mod cli;
|
||||||
|
mod colors;
|
||||||
|
mod deps;
|
||||||
|
mod lte;
|
||||||
|
mod manifest;
|
||||||
|
mod package_manager;
|
||||||
|
mod template;
|
||||||
|
mod theme;
|
||||||
|
|
||||||
|
pub mod internal {
|
||||||
|
//! Re-export of create-tauri-app internals
|
||||||
|
//!
|
||||||
|
//! ## Warning
|
||||||
|
//!
|
||||||
|
//! This is meant to be used internally only so use at your own risk
|
||||||
|
//! and expect APIs to break without a prior notice.
|
||||||
|
pub mod package_manager {
|
||||||
|
pub use crate::package_manager::*;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod template {
|
||||||
|
pub use crate::template::*;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<I, A>(args: I, bin_name: Option<String>, detected_manager: Option<String>)
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = A>,
|
||||||
|
A: Into<OsString> + Clone,
|
||||||
|
{
|
||||||
|
let _ = ctrlc::set_handler(move || {
|
||||||
|
eprint!("\x1b[?25h");
|
||||||
|
exit(0);
|
||||||
|
});
|
||||||
|
if let Err(e) = try_run(args, bin_name, detected_manager) {
|
||||||
|
eprintln!("{BOLD}{RED}error{RESET}: {e:#}");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_run<I, A>(
|
||||||
|
args: I,
|
||||||
|
bin_name: Option<String>,
|
||||||
|
detected_manager: Option<String>,
|
||||||
|
) -> anyhow::Result<()>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = A>,
|
||||||
|
A: Into<OsString> + Clone,
|
||||||
|
{
|
||||||
|
let detected_manager = detected_manager.and_then(|p| p.parse::<PackageManager>().ok());
|
||||||
|
let args = cli::parse(args.into_iter().map(Into::into).collect(), bin_name)?;
|
||||||
|
let defaults = cli::Args::default();
|
||||||
|
let cli::Args {
|
||||||
|
skip,
|
||||||
|
mobile,
|
||||||
|
alpha,
|
||||||
|
manager,
|
||||||
|
project_name,
|
||||||
|
template,
|
||||||
|
} = args;
|
||||||
|
let cwd = std::env::current_dir()?;
|
||||||
|
|
||||||
|
// Allow `--mobile` to only be used with `--alpha` for now
|
||||||
|
// TODO: remove this limitation once tauri@v2 is stable
|
||||||
|
if let Some(mobile) = mobile {
|
||||||
|
if mobile && !alpha {
|
||||||
|
eprintln!(
|
||||||
|
"{BOLD}{RED}error{RESET}: `{GREEN}--mobile{RESET}` option is only available if `{GREEN}--alpha{RESET}` option is also used"
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project name used for the project directory name and productName in tauri.conf.json
|
||||||
|
// and if valid, it will also be used in Cargo.toml, Package.json ...etc
|
||||||
|
let project_name = match project_name {
|
||||||
|
Some(name) => name,
|
||||||
|
None => {
|
||||||
|
if skip {
|
||||||
|
defaults
|
||||||
|
.project_name
|
||||||
|
.context("default project_name not set")?
|
||||||
|
} else {
|
||||||
|
Input::<String>::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Project name")
|
||||||
|
.default("tauri-app".into())
|
||||||
|
.interact_text()?
|
||||||
|
.trim()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let target_dir = cwd.join(&project_name);
|
||||||
|
|
||||||
|
// Package name used in Cargo.toml, Package.json ...etc
|
||||||
|
let package_name = if is_valid_pkg_name(&project_name) {
|
||||||
|
project_name.clone()
|
||||||
|
} else {
|
||||||
|
let valid_name = to_valid_pkg_name(&project_name);
|
||||||
|
if skip {
|
||||||
|
valid_name
|
||||||
|
} else {
|
||||||
|
Input::<String>::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Package name")
|
||||||
|
.default(valid_name.clone())
|
||||||
|
.with_initial_text(valid_name)
|
||||||
|
.validate_with(|input: &String| {
|
||||||
|
if is_valid_pkg_name(input) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Package name should only include lowercase alphanumeric character and hyphens \"-\" and doesn't start with numbers")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.interact_text()?
|
||||||
|
.trim().to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Confirm deleting the target project directory if not empty
|
||||||
|
if target_dir.exists() && target_dir.read_dir()?.next().is_some() {
|
||||||
|
let overrwite = if skip {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
Confirm::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt(format!(
|
||||||
|
"{} directory is not empty, do you want to overwrite?",
|
||||||
|
if target_dir == cwd {
|
||||||
|
"Current directory".to_string()
|
||||||
|
} else {
|
||||||
|
target_dir
|
||||||
|
.file_name()
|
||||||
|
.unwrap()
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.default(false)
|
||||||
|
.interact()?
|
||||||
|
};
|
||||||
|
if !overrwite {
|
||||||
|
eprintln!("{BOLD}{RED}✘{RESET} Operation Cancelled");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prompt for category if a package manger is not passed on the command line
|
||||||
|
let category = if manager.is_none() && !skip {
|
||||||
|
// Filter managers if a template is passed on the command line
|
||||||
|
let managers = PackageManager::ALL.to_vec();
|
||||||
|
let managers = args
|
||||||
|
.template
|
||||||
|
.map(|t| {
|
||||||
|
managers
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|p| p.templates_no_flavors().contains(&t.without_flavor()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.unwrap_or(managers);
|
||||||
|
|
||||||
|
// Filter categories based on the detected package mangers
|
||||||
|
let categories = Category::ALL.to_vec();
|
||||||
|
let mut categories = categories
|
||||||
|
.into_iter()
|
||||||
|
.filter(|c| c.package_managers().iter().any(|p| managers.contains(p)))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// sort categories so the most relevant category
|
||||||
|
// based on the auto-detected package manager is selected first
|
||||||
|
categories.sort_by(|a, b| {
|
||||||
|
detected_manager
|
||||||
|
.map(|p| b.package_managers().contains(&p))
|
||||||
|
.unwrap_or(false)
|
||||||
|
.cmp(
|
||||||
|
&detected_manager
|
||||||
|
.map(|p| a.package_managers().contains(&p))
|
||||||
|
.unwrap_or(false),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// If only one category is detected, skip prompt
|
||||||
|
if categories.len() == 1 {
|
||||||
|
Some(categories[0])
|
||||||
|
} else {
|
||||||
|
let index = Select::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Choose which language to use for your frontend")
|
||||||
|
.items(&categories)
|
||||||
|
.default(0)
|
||||||
|
.interact()?;
|
||||||
|
Some(categories[index])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Package manager which will be used for rendering the template
|
||||||
|
// and the after-render instructions
|
||||||
|
let pkg_manager = match manager {
|
||||||
|
Some(manager) => manager,
|
||||||
|
None => {
|
||||||
|
if skip {
|
||||||
|
defaults.manager.context("default manager not set")?
|
||||||
|
} else {
|
||||||
|
let category = category.context("category shouldn't be None at this point")?;
|
||||||
|
|
||||||
|
let mut managers = category.package_managers().to_owned();
|
||||||
|
// sort managers so the auto-detected package manager is selected first
|
||||||
|
managers.sort_by(|a, b| {
|
||||||
|
detected_manager
|
||||||
|
.map(|p| p == *b)
|
||||||
|
.unwrap_or(false)
|
||||||
|
.cmp(&detected_manager.map(|p| p == *a).unwrap_or(false))
|
||||||
|
});
|
||||||
|
// If only one package manager is detected, skip prompt
|
||||||
|
if managers.len() == 1 {
|
||||||
|
managers[0]
|
||||||
|
} else {
|
||||||
|
let index = Select::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Choose your package manager")
|
||||||
|
.items(&managers)
|
||||||
|
.default(0)
|
||||||
|
.interact()?;
|
||||||
|
managers[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let templates_no_flavors = pkg_manager.templates_no_flavors();
|
||||||
|
|
||||||
|
// Template to render
|
||||||
|
let template = match template {
|
||||||
|
Some(template) => template,
|
||||||
|
None => {
|
||||||
|
if skip {
|
||||||
|
defaults.template.context("default template not set")?
|
||||||
|
} else {
|
||||||
|
let index = Select::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Choose your UI template")
|
||||||
|
.items(
|
||||||
|
&templates_no_flavors
|
||||||
|
.iter()
|
||||||
|
.map(|t| t.select_text())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.default(0)
|
||||||
|
.interact()?;
|
||||||
|
|
||||||
|
let template = templates_no_flavors[index];
|
||||||
|
|
||||||
|
// Prompt for flavors if the template has more than one flavor
|
||||||
|
let flavors = template.flavors(pkg_manager);
|
||||||
|
if let Some(flavors) = flavors {
|
||||||
|
let index = Select::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Choose your UI flavor")
|
||||||
|
.items(flavors)
|
||||||
|
.default(0)
|
||||||
|
.interact()?;
|
||||||
|
template.from_flavor(flavors[index])
|
||||||
|
} else {
|
||||||
|
template
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prompt for wether to bootstrap a mobile-friendly tauri project
|
||||||
|
// This should only be prompted if `--alpha` is used on the command line and `--mobile` wasn't.
|
||||||
|
// TODO: remove this limitation once tauri@v2 is stable
|
||||||
|
let mobile = match mobile {
|
||||||
|
Some(mobile) => mobile,
|
||||||
|
None => {
|
||||||
|
Confirm::with_theme(&ColorfulTheme::default())
|
||||||
|
.with_prompt("Would you like to setup the project for mobile as well?")
|
||||||
|
.default(false)
|
||||||
|
.interact()?
|
||||||
|
// if skip || !alpha {
|
||||||
|
// defaults.mobile.context("default mobile not set")?
|
||||||
|
// } else {
|
||||||
|
// Confirm::with_theme(&ColorfulTheme::default())
|
||||||
|
// .with_prompt("Would you like to setup the project for mobile as well?")
|
||||||
|
// .default(false)
|
||||||
|
// .interact()?
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the package manager and the template are specified on the command line
|
||||||
|
// then almost all prompts are skipped so we need to make sure that the combination
|
||||||
|
// is valid, otherwise, we error and exit
|
||||||
|
if !pkg_manager.templates().contains(&template) {
|
||||||
|
eprintln!(
|
||||||
|
"{BOLD}{RED}error{RESET}: the {GREEN}{template}{RESET} template is not suppported for the {GREEN}{pkg_manager}{RESET} package manager\n possible templates for {GREEN}{pkg_manager}{RESET} are: [{}]\n or maybe you meant to use another package manager\n possible package managers for {GREEN}{template}{RESET} are: [{}]" ,
|
||||||
|
templates_no_flavors.iter().map(|e|format!("{GREEN}{e}{RESET}")).collect::<Vec<_>>().join(", "),
|
||||||
|
template.possible_package_managers().iter().map(|e|format!("{GREEN}{e}{RESET}")).collect::<Vec<_>>().join(", "),
|
||||||
|
);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the target dir contents before rendering the template
|
||||||
|
// SAFETY: Upon reaching this line, the user already accepted to overwrite
|
||||||
|
if target_dir.exists() {
|
||||||
|
#[inline(always)]
|
||||||
|
fn clean_dir(dir: &std::path::PathBuf) -> anyhow::Result<()> {
|
||||||
|
for entry in fs::read_dir(dir)?.flatten() {
|
||||||
|
let path = entry.path();
|
||||||
|
if entry.file_type()?.is_dir() {
|
||||||
|
if entry.file_name() != ".git" {
|
||||||
|
clean_dir(&path)?;
|
||||||
|
std::fs::remove_dir(path)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fs::remove_file(path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
clean_dir(&target_dir)?;
|
||||||
|
} else {
|
||||||
|
let _ = fs::create_dir_all(&target_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the template
|
||||||
|
template.render(
|
||||||
|
&target_dir,
|
||||||
|
pkg_manager,
|
||||||
|
&project_name,
|
||||||
|
&package_name,
|
||||||
|
alpha,
|
||||||
|
mobile,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Print post-render instructions
|
||||||
|
println!();
|
||||||
|
print!("Template created!");
|
||||||
|
print_missing_deps(pkg_manager, template, alpha);
|
||||||
|
if target_dir != cwd {
|
||||||
|
println!(
|
||||||
|
" cd {}",
|
||||||
|
if project_name.contains(' ') {
|
||||||
|
format!("\"{project_name}\"")
|
||||||
|
} else {
|
||||||
|
project_name.clone()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(cmd) = pkg_manager.install_cmd() {
|
||||||
|
println!(" {cmd}");
|
||||||
|
}
|
||||||
|
if !mobile {
|
||||||
|
println!(" {} run", pkg_manager.run_cmd());
|
||||||
|
println!();
|
||||||
|
println!("For Release, run:");
|
||||||
|
println!(" {} run --release", pkg_manager.run_cmd());
|
||||||
|
println!();
|
||||||
|
println!("For Small Release, run:");
|
||||||
|
println!(" {} run --profile=small", pkg_manager.run_cmd());
|
||||||
|
} else {
|
||||||
|
println!(" {} makepad android --abi=all install-toolchain", pkg_manager.run_cmd());
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
println!(" {} makepad apple ios install-toolchain", pkg_manager.run_cmd());
|
||||||
|
|
||||||
|
println!();
|
||||||
|
println!("For Desktop development, run:");
|
||||||
|
println!(" {} run", pkg_manager.run_cmd());
|
||||||
|
println!();
|
||||||
|
println!("For Release, run:");
|
||||||
|
println!(" {} run --release", pkg_manager.run_cmd());
|
||||||
|
println!();
|
||||||
|
println!("For Small Release, run:");
|
||||||
|
println!(" {} run --profile=small", pkg_manager.run_cmd());
|
||||||
|
println!();
|
||||||
|
println!("For Android development, run:");
|
||||||
|
println!(" {} makepad android run", pkg_manager.run_cmd());
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
println!();
|
||||||
|
println!("For iOS Simulator development, run:");
|
||||||
|
println!(" {} makepad --org-id=123456 --org={}.com --app={} run-sim --release", pkg_manager.run_cmd(), project_name, project_name);
|
||||||
|
println!();
|
||||||
|
println!("For iOS Device development, run:");
|
||||||
|
println!(" {} makepad --org-id=123456 --org={}.com --app={} run-device {}", pkg_manager.run_cmd(), project_name, project_name, project_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_pkg_name(project_name: &str) -> bool {
|
||||||
|
let mut chars = project_name.chars().peekable();
|
||||||
|
!project_name.is_empty()
|
||||||
|
&& !chars.peek().map(|c| c.is_ascii_digit()).unwrap_or_default()
|
||||||
|
&& !chars.any(|ch| !(ch.is_alphanumeric() || ch == '-' || ch == '_') || ch.is_uppercase())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_valid_pkg_name(project_name: &str) -> String {
|
||||||
|
let ret = project_name
|
||||||
|
.trim()
|
||||||
|
.to_lowercase()
|
||||||
|
.replace([':', ';', ' ', '~'], "-")
|
||||||
|
.replace(['.', '\\', '/'], "");
|
||||||
|
|
||||||
|
let ret = ret
|
||||||
|
.chars()
|
||||||
|
.skip_while(|ch| ch.is_ascii_digit() || *ch == '-')
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
if ret.is_empty() {
|
||||||
|
"tauri-app".to_string()
|
||||||
|
} else {
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
492
packages/cli/src/lte.rs
Normal file
492
packages/cli/src/lte.rs
Normal file
|
@ -0,0 +1,492 @@
|
||||||
|
// why was this module named `lte`? short for "Light Template Engine" and might be extracted into its own crate.
|
||||||
|
|
||||||
|
// TODOs:
|
||||||
|
// 1. negative boolean
|
||||||
|
// 2. multiple condition in the same if
|
||||||
|
// 3. equality checks
|
||||||
|
// 4. infinte loop when missing a closing `%}`
|
||||||
|
|
||||||
|
use std::{collections::HashMap, fmt::Display};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TemplateParseError {
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
impl TemplateParseError {
|
||||||
|
fn new(message: String) -> Self {
|
||||||
|
Self { message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for TemplateParseError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Failed to parse template: {}", self.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for TemplateParseError {}
|
||||||
|
type Result<T> = std::result::Result<T, TemplateParseError>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
enum Token<'a> {
|
||||||
|
OBracket,
|
||||||
|
If,
|
||||||
|
Var(&'a str),
|
||||||
|
Else,
|
||||||
|
EndIf,
|
||||||
|
CBracket,
|
||||||
|
Text(&'a str),
|
||||||
|
Invalid(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Token<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Token::OBracket => write!(f, "{{%"),
|
||||||
|
Token::If => write!(f, "if"),
|
||||||
|
Token::Var(var) => write!(f, "{} (variable)", var),
|
||||||
|
Token::Else => write!(f, "else"),
|
||||||
|
Token::EndIf => write!(f, "endif"),
|
||||||
|
Token::CBracket => write!(f, "%}}"),
|
||||||
|
Token::Text(_) => write!(f, "(text)"),
|
||||||
|
Token::Invalid(col) => write!(f, "invalid token at {col}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const KEYWORDS: &[(&str, Token)] = &[
|
||||||
|
("if", Token::If),
|
||||||
|
("else", Token::Else),
|
||||||
|
("endif", Token::EndIf),
|
||||||
|
];
|
||||||
|
|
||||||
|
struct Lexer<'a> {
|
||||||
|
input: &'a str,
|
||||||
|
bytes: &'a [u8],
|
||||||
|
len: usize,
|
||||||
|
cursor: usize,
|
||||||
|
in_bracket: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Lexer<'a> {
|
||||||
|
fn new(input: &'a str) -> Self {
|
||||||
|
let bytes = input.as_bytes();
|
||||||
|
let len = bytes.len();
|
||||||
|
Self {
|
||||||
|
len,
|
||||||
|
bytes,
|
||||||
|
input,
|
||||||
|
cursor: 0,
|
||||||
|
in_bracket: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_char(&self) -> char {
|
||||||
|
self.bytes[self.cursor] as char
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_char(&self) -> char {
|
||||||
|
self.bytes[self.cursor + 1] as char
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_whitespace(&mut self) {
|
||||||
|
while self.cursor < self.len && self.current_char().is_whitespace() {
|
||||||
|
self.cursor += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_symbol_start(&self) -> bool {
|
||||||
|
let c = self.current_char();
|
||||||
|
c.is_alphabetic() || c == '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_symbol(&self) -> bool {
|
||||||
|
let c = self.current_char();
|
||||||
|
c.is_alphanumeric() || c == '_'
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_symbol(&mut self) -> &'a str {
|
||||||
|
let start = self.cursor;
|
||||||
|
while self.is_symbol() {
|
||||||
|
self.cursor += 1;
|
||||||
|
}
|
||||||
|
let end = self.cursor - 1;
|
||||||
|
&self.input[start..=end]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Token<'a>> {
|
||||||
|
if self.in_bracket {
|
||||||
|
self.skip_whitespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.cursor >= self.len {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.current_char() == '{' && self.next_char() == '%' {
|
||||||
|
self.in_bracket = true;
|
||||||
|
self.cursor += 2;
|
||||||
|
return Some(Token::OBracket);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.current_char() == '%' && self.next_char() == '}' {
|
||||||
|
self.in_bracket = false;
|
||||||
|
self.cursor += 2;
|
||||||
|
return Some(Token::CBracket);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.in_bracket {
|
||||||
|
if self.is_symbol_start() {
|
||||||
|
let symbol = self.read_symbol();
|
||||||
|
for (keyword, t) in KEYWORDS {
|
||||||
|
if *keyword == symbol {
|
||||||
|
return Some(*t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(Token::Var(symbol));
|
||||||
|
} else {
|
||||||
|
self.cursor += 1;
|
||||||
|
return Some(Token::Invalid(self.cursor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.in_bracket {
|
||||||
|
let start = self.cursor;
|
||||||
|
while !(self.current_char() == '{' && self.next_char() == '%') {
|
||||||
|
self.cursor += 1;
|
||||||
|
|
||||||
|
if self.cursor >= self.len {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let end = self.cursor - 1;
|
||||||
|
return Some(Token::Text(&self.input[start..=end]));
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for Lexer<'a> {
|
||||||
|
type Item = Token<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
enum Stmt<'a> {
|
||||||
|
Text(&'a str),
|
||||||
|
Var(&'a str),
|
||||||
|
If {
|
||||||
|
var: &'a str,
|
||||||
|
condition: Vec<Stmt<'a>>,
|
||||||
|
else_condition: Option<Vec<Stmt<'a>>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Stmt<'a> {
|
||||||
|
fn execute<T: ToString>(&self, out: &mut String, data: &HashMap<&str, T>) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
Stmt::Text(t) => out.push_str(t),
|
||||||
|
Stmt::Var(v) => {
|
||||||
|
let value = data.get(v).ok_or_else(|| {
|
||||||
|
TemplateParseError::new(format!("Unrecognized variable: {v}"))
|
||||||
|
})?;
|
||||||
|
out.push_str(&value.to_string())
|
||||||
|
}
|
||||||
|
Stmt::If {
|
||||||
|
var,
|
||||||
|
condition,
|
||||||
|
else_condition,
|
||||||
|
} => {
|
||||||
|
let value = data
|
||||||
|
.get(var)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
TemplateParseError::new(format!("Unrecognized variable: {var}"))
|
||||||
|
})?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let evaluated = if value == "true" {
|
||||||
|
condition.as_slice()
|
||||||
|
} else if let Some(else_condition) = else_condition {
|
||||||
|
else_condition.as_slice()
|
||||||
|
} else {
|
||||||
|
&[]
|
||||||
|
};
|
||||||
|
|
||||||
|
for stmt in evaluated {
|
||||||
|
stmt.execute(out, data)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Parser<'a> {
|
||||||
|
tokens: &'a [Token<'a>],
|
||||||
|
len: usize,
|
||||||
|
cursor: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Parser<'a> {
|
||||||
|
fn new(tokens: &'a [Token<'a>]) -> Self {
|
||||||
|
Self {
|
||||||
|
len: tokens.len(),
|
||||||
|
tokens,
|
||||||
|
cursor: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_token(&self) -> Token<'a> {
|
||||||
|
self.tokens[self.cursor]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_brackets(&mut self) {
|
||||||
|
if self.cursor < self.len {
|
||||||
|
while self.current_token() == Token::OBracket || self.current_token() == Token::CBracket
|
||||||
|
{
|
||||||
|
self.cursor += 1;
|
||||||
|
|
||||||
|
if self.cursor >= self.len {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume_text(&mut self) -> Option<&'a str> {
|
||||||
|
if let Token::Text(text) = self.current_token() {
|
||||||
|
self.cursor += 1;
|
||||||
|
Some(text)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume_var(&mut self) -> Option<&'a str> {
|
||||||
|
if let Token::Var(var) = self.current_token() {
|
||||||
|
self.cursor += 1;
|
||||||
|
Some(var)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn consume_if(&mut self) -> Result<Option<Stmt<'a>>> {
|
||||||
|
if self.current_token() == Token::If {
|
||||||
|
self.cursor += 1;
|
||||||
|
|
||||||
|
let var = self.consume_var().ok_or_else(|| {
|
||||||
|
TemplateParseError::new(format!(
|
||||||
|
"expected variable after if, found: {}",
|
||||||
|
self.current_token()
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut condition = Vec::new();
|
||||||
|
while self.current_token() != Token::Else || self.current_token() != Token::EndIf {
|
||||||
|
match self.next()? {
|
||||||
|
Some(stmt) => condition.push(stmt),
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let else_condition = if self.current_token() == Token::Else {
|
||||||
|
self.cursor += 1;
|
||||||
|
|
||||||
|
let mut else_condition = Vec::new();
|
||||||
|
while self.current_token() != Token::EndIf {
|
||||||
|
match self.next()? {
|
||||||
|
Some(stmt) => else_condition.push(stmt),
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(else_condition)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.current_token() == Token::EndIf {
|
||||||
|
self.cursor += 1;
|
||||||
|
} else {
|
||||||
|
return Err(TemplateParseError::new(format!(
|
||||||
|
"expected endif, found: {}",
|
||||||
|
self.current_token()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(Stmt::If {
|
||||||
|
var,
|
||||||
|
condition,
|
||||||
|
else_condition,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) -> Result<Option<Stmt<'a>>> {
|
||||||
|
self.skip_brackets();
|
||||||
|
|
||||||
|
if self.cursor >= self.len {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let t @ Token::Invalid(_) = self.current_token() {
|
||||||
|
return Err(TemplateParseError {
|
||||||
|
message: t.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = self.consume_text();
|
||||||
|
if text.is_some() {
|
||||||
|
return Ok(text.map(Stmt::Text));
|
||||||
|
}
|
||||||
|
|
||||||
|
let var = self.consume_var();
|
||||||
|
if var.is_some() {
|
||||||
|
return Ok(var.map(Stmt::Var));
|
||||||
|
}
|
||||||
|
|
||||||
|
let if_ = self.consume_if()?;
|
||||||
|
if if_.is_some() {
|
||||||
|
return Ok(if_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render<T: ToString>(template: &str, data: &HashMap<&str, T>) -> Result<String> {
|
||||||
|
let tokens: Vec<Token> = Lexer::new(template).collect();
|
||||||
|
let mut parser = Parser::new(&tokens);
|
||||||
|
let mut stmts: Vec<Stmt> = Vec::new();
|
||||||
|
while let Some(stmt) = parser.next()? {
|
||||||
|
stmts.push(stmt);
|
||||||
|
}
|
||||||
|
let mut out = String::new();
|
||||||
|
for stmt in stmts {
|
||||||
|
stmt.execute(&mut out, data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_replaces_variable() {
|
||||||
|
let template = "<html>Hello {% name %}</html>";
|
||||||
|
let data: HashMap<&str, &str> = [("name", "world")].into();
|
||||||
|
let rendered = render(template, &data).expect("it should render");
|
||||||
|
assert_eq!(rendered, "<html>Hello world</html>")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_replaces_variable_with_new_lines() {
|
||||||
|
let template = r#"
|
||||||
|
<html>
|
||||||
|
<h1>Hello<h2>
|
||||||
|
<em>{% name %}</em>
|
||||||
|
</html>"#;
|
||||||
|
let data: HashMap<&str, &str> = [("name", "world")].into();
|
||||||
|
let rendered = render(template, &data).expect("it should render");
|
||||||
|
let expected = r#"
|
||||||
|
<html>
|
||||||
|
<h1>Hello<h2>
|
||||||
|
<em>world</em>
|
||||||
|
</html>"#;
|
||||||
|
assert_eq!(rendered, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_performs_condition() {
|
||||||
|
let template = "<html>Hello {% if alpha %}alpha{% else %}stable{% endif %}</html>";
|
||||||
|
let data: HashMap<&str, bool> = [("alpha", true)].into();
|
||||||
|
let rendered = render(template, &data).expect("it should render");
|
||||||
|
assert_eq!(rendered, "<html>Hello alpha</html>")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_performs_else_condition() {
|
||||||
|
let template = "<html>Hello {% if alpha %}alpha{% else %}stable{% endif %}</html>";
|
||||||
|
let data: HashMap<&str, bool> = [("alpha", false)].into();
|
||||||
|
let rendered = render(template, &data).expect("it should render");
|
||||||
|
assert_eq!(rendered, "<html>Hello stable</html>")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_performs_condition_with_new_lines() {
|
||||||
|
let template = r#"
|
||||||
|
<html>
|
||||||
|
<h1>Hello<h2>{% if alpha %}
|
||||||
|
<em>alpha</em>{% else %}
|
||||||
|
<em>stable</em>{% endif %}
|
||||||
|
</html>"#;
|
||||||
|
let data: HashMap<&str, bool> = [("alpha", true)].into();
|
||||||
|
let rendered = render(template, &data).expect("it should render");
|
||||||
|
let expected = r#"
|
||||||
|
<html>
|
||||||
|
<h1>Hello<h2>
|
||||||
|
<em>alpha</em>
|
||||||
|
</html>"#;
|
||||||
|
assert_eq!(rendered, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_replaces_variable_within_if() {
|
||||||
|
let template = r#"
|
||||||
|
<html>
|
||||||
|
<h1>Hello<h2>{% if alpha %}
|
||||||
|
<em>{% alpha_str %}</em>{% else %}
|
||||||
|
<em>stable</em>{% endif %}
|
||||||
|
</html>"#;
|
||||||
|
let data: HashMap<&str, &str> = [("alpha", "true"), ("alpha_str", "holla alpha")].into();
|
||||||
|
let rendered = render(template, &data).expect("it should render");
|
||||||
|
let expected = r#"
|
||||||
|
<html>
|
||||||
|
<h1>Hello<h2>
|
||||||
|
<em>holla alpha</em>
|
||||||
|
</html>"#;
|
||||||
|
assert_eq!(rendered, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_performs_nested_conditions() {
|
||||||
|
let template = r#"
|
||||||
|
<html>
|
||||||
|
<h1>Hello<h2>{% if alpha %}
|
||||||
|
<em>{% alpha_str %}</em>{% else %}
|
||||||
|
<em>{% if beta %}beta{%else%}stable{%endif%}</em>{% endif %}
|
||||||
|
</html>"#;
|
||||||
|
let data: HashMap<&str, &str> = [
|
||||||
|
("alpha", "false"),
|
||||||
|
("beta", "true"),
|
||||||
|
("alpha_str", "holla alpha"),
|
||||||
|
]
|
||||||
|
.into();
|
||||||
|
let rendered = render(template, &data).expect("it should render");
|
||||||
|
let expected = r#"
|
||||||
|
<html>
|
||||||
|
<h1>Hello<h2>
|
||||||
|
<em>beta</em>
|
||||||
|
</html>"#;
|
||||||
|
assert_eq!(rendered, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_panics() {
|
||||||
|
let template = "<html>Hello {% name }</html>";
|
||||||
|
let data: HashMap<&str, &str> = [("name", "world")].into();
|
||||||
|
let rendered = render(template, &data);
|
||||||
|
assert!(rendered.is_err())
|
||||||
|
}
|
||||||
|
}
|
33
packages/cli/src/main.rs
Normal file
33
packages/cli/src/main.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use std::{env::args_os, ffi::OsStr, path::Path};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = args_os().peekable();
|
||||||
|
let mut is_cargo = false;
|
||||||
|
let bin_name = match args
|
||||||
|
.next()
|
||||||
|
.as_deref()
|
||||||
|
.map(Path::new)
|
||||||
|
.and_then(Path::file_stem)
|
||||||
|
.and_then(OsStr::to_str)
|
||||||
|
{
|
||||||
|
Some("cargo-create-makepad-app") => {
|
||||||
|
is_cargo = true;
|
||||||
|
if args.peek().and_then(|s| s.to_str()) == Some("create-makepad-app") {
|
||||||
|
// remove the extra cargo subcommand
|
||||||
|
args.next();
|
||||||
|
Some("cargo create-makepad-app".into())
|
||||||
|
} else {
|
||||||
|
Some("cargo-create-makepad-app".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(stem) => Some(stem.to_string()),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
create_makepad_app::run(
|
||||||
|
args,
|
||||||
|
bin_name,
|
||||||
|
if is_cargo { Some("cargo".into()) } else { None },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
197
packages/cli/src/manifest.rs
Normal file
197
packages/cli/src/manifest.rs
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use anyhow::{bail, Context};
|
||||||
|
|
||||||
|
#[derive(Default, Clone, PartialEq, Eq, Debug)]
|
||||||
|
pub struct Manifest<'a> {
|
||||||
|
pub before_dev_command: Option<&'a str>,
|
||||||
|
pub before_build_command: Option<&'a str>,
|
||||||
|
pub dev_path: Option<&'a str>,
|
||||||
|
pub dist_dir: Option<&'a str>,
|
||||||
|
pub with_global_makepad: Option<bool>,
|
||||||
|
pub files: HashMap<&'a str, &'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Manifest<'a> {
|
||||||
|
pub fn parse(s: &'a str, mobile: bool) -> Result<Self, anyhow::Error> {
|
||||||
|
let mut manifest = Self::default();
|
||||||
|
|
||||||
|
let mut in_files_section = false;
|
||||||
|
let mut in_mobile_section = false;
|
||||||
|
|
||||||
|
for (i, line) in s.split('\n').enumerate() {
|
||||||
|
let line_number = i + 1;
|
||||||
|
|
||||||
|
// ignore the comment portion of the line
|
||||||
|
let line = line.split('#').next().unwrap().trim();
|
||||||
|
|
||||||
|
if line.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if line == "[files]" {
|
||||||
|
in_files_section = true;
|
||||||
|
in_mobile_section = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if line == "[mobile]" {
|
||||||
|
in_mobile_section = true;
|
||||||
|
in_files_section = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if line.contains('=') {
|
||||||
|
let mut s = line.split('=');
|
||||||
|
let (k, v) = (
|
||||||
|
s.next()
|
||||||
|
.with_context(|| {
|
||||||
|
format!("parsing manifest: key is not found in line {line_number}")
|
||||||
|
})?
|
||||||
|
.trim(),
|
||||||
|
s.next()
|
||||||
|
.with_context(|| {
|
||||||
|
format!("parsing manifest: value is not found in line {line_number}")
|
||||||
|
})?
|
||||||
|
.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if k.is_empty() {
|
||||||
|
bail!("parsing manifest: key is empty in line {line_number}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.is_empty() {
|
||||||
|
bail!("parsing manifest: value is empty in line {line_number}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::nonminimal_bool)]
|
||||||
|
let replace =
|
||||||
|
!in_files_section && (!in_mobile_section || (in_mobile_section && mobile));
|
||||||
|
|
||||||
|
match k {
|
||||||
|
"beforeDevCommand" if replace => manifest.before_dev_command = Some(v),
|
||||||
|
"beforeBuildCommand" if replace => manifest.before_build_command = Some(v),
|
||||||
|
"devPath" if replace => manifest.dev_path = Some(v),
|
||||||
|
"distDir" if replace => manifest.dist_dir = Some(v),
|
||||||
|
"withGlobalTauri" if replace => manifest.with_global_makepad = Some(v.parse()?),
|
||||||
|
_ if in_files_section => {
|
||||||
|
manifest.files.insert(k, v);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(manifest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_parses() {
|
||||||
|
let manifest_file = r#"
|
||||||
|
# Copyright 2019-2022 Tauri Programme within The Commons Conservancy
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
beforeDevCommand = npm start -- --port 1420
|
||||||
|
beforeBuildCommand = {% pkg_manager_run_command %} build # this comment should be stripped
|
||||||
|
devPath = http://localhost:1420
|
||||||
|
|
||||||
|
[mobile]
|
||||||
|
beforeBuildCommand = {% pkg_manager_run_command %} build mobile
|
||||||
|
|
||||||
|
[files]
|
||||||
|
makepad.svg = src/assets/makepad.svg
|
||||||
|
styles.css = src/styles.css
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(Manifest::parse(manifest_file, false).unwrap(), {
|
||||||
|
let mut files = HashMap::new();
|
||||||
|
files.insert("makepad.svg", "src/assets/makepad.svg");
|
||||||
|
files.insert("styles.css", "src/styles.css");
|
||||||
|
|
||||||
|
Manifest {
|
||||||
|
before_dev_command: Some("npm start -- --port 1420"),
|
||||||
|
before_build_command: Some("{% pkg_manager_run_command %} build"),
|
||||||
|
dev_path: Some("http://localhost:1420"),
|
||||||
|
dist_dir: None,
|
||||||
|
with_global_makepad: None,
|
||||||
|
files,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(Manifest::parse(manifest_file, true).unwrap(), {
|
||||||
|
let mut files = HashMap::new();
|
||||||
|
files.insert("makepad.svg", "src/assets/makepad.svg");
|
||||||
|
files.insert("styles.css", "src/styles.css");
|
||||||
|
|
||||||
|
Manifest {
|
||||||
|
before_dev_command: Some("npm start -- --port 1420"),
|
||||||
|
before_build_command: Some("{% pkg_manager_run_command %} build mobile"),
|
||||||
|
dev_path: Some("http://localhost:1420"),
|
||||||
|
dist_dir: None,
|
||||||
|
with_global_makepad: None,
|
||||||
|
files,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn it_panics_while_parsing() {
|
||||||
|
let manifest_file = r#"
|
||||||
|
# Copyright 2019-2022 Tauri Programme within The Commons Conservancy
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
beforeDevCommand = npm start -- --port 1420
|
||||||
|
beforeBuildCommand =
|
||||||
|
devPath = http://localhost:1420
|
||||||
|
|
||||||
|
[mobile]
|
||||||
|
beforeBuildCommand = {% pkg_manager_run_command %} build mobile
|
||||||
|
|
||||||
|
[files]
|
||||||
|
makepad.svg = src/assets/makepad.svg
|
||||||
|
styles.css = src/styles.css
|
||||||
|
"#;
|
||||||
|
|
||||||
|
Manifest::parse(manifest_file, false).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn later_should_override_former() {
|
||||||
|
let manifest_file = r#"
|
||||||
|
# Copyright 2019-2022 Tauri Programme within The Commons Conservancy
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
beforeDevCommand = npm start -- --port 1420
|
||||||
|
beforeBuildCommand = {% pkg_manager_run_command %} build # this comment should be stripped
|
||||||
|
devPath = http://localhost:1420
|
||||||
|
beforeBuildCommand = {% pkg_manager_run_command %} build mobile
|
||||||
|
|
||||||
|
[files]
|
||||||
|
makepad.svg = src/assets/makepad.svg
|
||||||
|
styles.css = src/styles.css
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(Manifest::parse(manifest_file, false).unwrap(), {
|
||||||
|
let mut files = HashMap::new();
|
||||||
|
files.insert("makepad.svg", "src/assets/makepad.svg");
|
||||||
|
files.insert("styles.css", "src/styles.css");
|
||||||
|
|
||||||
|
Manifest {
|
||||||
|
before_dev_command: Some("npm start -- --port 1420"),
|
||||||
|
before_build_command: Some("{% pkg_manager_run_command %} build mobile"),
|
||||||
|
dev_path: Some("http://localhost:1420"),
|
||||||
|
dist_dir: None,
|
||||||
|
with_global_makepad: None,
|
||||||
|
files,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
84
packages/cli/src/package_manager.rs
Normal file
84
packages/cli/src/package_manager.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
use std::{fmt::Display, str::FromStr};
|
||||||
|
|
||||||
|
use crate::{colors::*, template::Template};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum PackageManager {
|
||||||
|
Cargo,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PackageManager {
|
||||||
|
fn default() -> Self {
|
||||||
|
PackageManager::Cargo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PackageManager {
|
||||||
|
pub const ALL: &'a [PackageManager] = &[
|
||||||
|
PackageManager::Cargo
|
||||||
|
];
|
||||||
|
}
|
||||||
|
impl PackageManager {
|
||||||
|
/// Returns templates without flavors
|
||||||
|
pub const fn templates_no_flavors(&self) -> &[Template] {
|
||||||
|
match self {
|
||||||
|
PackageManager::Cargo => &[
|
||||||
|
Template::MakepadCounter,
|
||||||
|
Template::MakepadStackNavigation,
|
||||||
|
Template::MakepadLogin,
|
||||||
|
],
|
||||||
|
PackageManager::Unknown => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn templates(&self) -> &[Template] {
|
||||||
|
match self {
|
||||||
|
PackageManager::Cargo => &[
|
||||||
|
Template::MakepadCounter,
|
||||||
|
Template::MakepadStackNavigation,
|
||||||
|
Template::MakepadLogin,
|
||||||
|
],
|
||||||
|
PackageManager::Unknown => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn install_cmd(&self) -> Option<&str> {
|
||||||
|
match self {
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn run_cmd(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
PackageManager::Cargo => "cargo",
|
||||||
|
PackageManager::Unknown => "unknown",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for PackageManager {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
PackageManager::Cargo => write!(f, "cargo"),
|
||||||
|
PackageManager::Unknown => write!(f, "unknown"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for PackageManager {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"cargo" => Ok(PackageManager::Cargo),
|
||||||
|
_ => Err(format!(
|
||||||
|
"{YELLOW}{s}{RESET} is not a valid package manager. Valid package mangers are [{}]",
|
||||||
|
PackageManager::ALL
|
||||||
|
.iter()
|
||||||
|
.map(|e| format!("{GREEN}{e}{RESET}"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
334
packages/cli/src/template.rs
Normal file
334
packages/cli/src/template.rs
Normal file
|
@ -0,0 +1,334 @@
|
||||||
|
use std::{collections::HashMap, fmt::Display, fs, io::Write, path, str::FromStr};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
|
|
||||||
|
use crate::{colors::*, manifest::Manifest, package_manager::PackageManager};
|
||||||
|
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "fragments"]
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
struct FRAGMENTS;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Template {
|
||||||
|
MakepadCounter,
|
||||||
|
MakepadStackNavigation,
|
||||||
|
MakepadLogin,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Template {
|
||||||
|
fn default() -> Self {
|
||||||
|
Template::MakepadCounter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Template {
|
||||||
|
pub const fn select_text<'a>(&self) -> &'a str {
|
||||||
|
match self {
|
||||||
|
Template::MakepadCounter => "Counter",
|
||||||
|
Template::MakepadStackNavigation => "SimpleStackNavigation",
|
||||||
|
Template::MakepadLogin => "SimpleLogin",
|
||||||
|
Template::Unknown => "Unknown",
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Template {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Template::MakepadCounter => write!(f, "counter"),
|
||||||
|
Template::MakepadStackNavigation => write!(f, "simplestacknavigation"),
|
||||||
|
Template::MakepadLogin => write!(f, "simplelogin"),
|
||||||
|
Template::Unknown => write!(f, "unknown"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Template {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"counter" => Ok(Template::MakepadCounter),
|
||||||
|
"simplestacknavigation" => Ok(Template::MakepadStackNavigation),
|
||||||
|
"simplelogin" => Ok(Template::MakepadLogin),
|
||||||
|
_ => Err(format!(
|
||||||
|
"{YELLOW}{s}{RESET} is not a valid template. Valid templates are [{}]",
|
||||||
|
Template::ALL
|
||||||
|
.iter()
|
||||||
|
.map(|e| format!("{GREEN}{e}{RESET}"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Template {
|
||||||
|
pub const ALL: &'a [Template] = &[
|
||||||
|
Template::MakepadCounter,
|
||||||
|
Template::MakepadStackNavigation,
|
||||||
|
Template::MakepadLogin,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn flavors<'b>(&self, pkg_manager: PackageManager) -> Option<&'b [Flavor]> {
|
||||||
|
match self {
|
||||||
|
Template::MakepadCounter => {
|
||||||
|
if pkg_manager == PackageManager::Cargo {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(&[Flavor::Unknown])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Template::MakepadStackNavigation => {
|
||||||
|
if pkg_manager == PackageManager::Cargo {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(&[Flavor::Unknown])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_flavor(&self, flavor: Flavor) -> Self {
|
||||||
|
match (self, flavor) {
|
||||||
|
(Template::Unknown, Flavor::Unknown) => Template::Unknown,
|
||||||
|
_ => *self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn without_flavor(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Template::Unknown => Template::Unknown,
|
||||||
|
_ => *self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn possible_package_managers(&self) -> &[PackageManager] {
|
||||||
|
match self {
|
||||||
|
Template::MakepadCounter => &[PackageManager::Cargo,],
|
||||||
|
Template::MakepadStackNavigation => &[PackageManager::Cargo],
|
||||||
|
Template::MakepadLogin => &[PackageManager::Cargo],
|
||||||
|
Template::Unknown => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn needs_trunk(&self) -> bool {
|
||||||
|
matches!(self, Template::MakepadCounter | Template::MakepadStackNavigation | Template::MakepadLogin)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn needs_makepad_cli(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Template::MakepadCounter | Template::MakepadStackNavigation | Template::MakepadLogin
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn needs_wasm32_target(&self) -> bool {
|
||||||
|
matches!(self, Template::MakepadCounter | Template::MakepadStackNavigation | Template::MakepadLogin)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(
|
||||||
|
&self,
|
||||||
|
target_dir: &path::Path,
|
||||||
|
pkg_manager: PackageManager,
|
||||||
|
project_name: &str,
|
||||||
|
package_name: &str,
|
||||||
|
alpha: bool,
|
||||||
|
mobile: bool,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let manifest_bytes = FRAGMENTS::get(&format!("fragment-{self}/_cta_manifest_"))
|
||||||
|
.with_context(|| "Failed to get manifest bytes")?
|
||||||
|
.data;
|
||||||
|
let manifest_str = String::from_utf8(manifest_bytes.to_vec())?;
|
||||||
|
let manifest = Manifest::parse(&manifest_str, mobile)?;
|
||||||
|
|
||||||
|
let lib_name = format!("{}_lib", package_name.replace('-', "_"));
|
||||||
|
|
||||||
|
let manifest_template_data: HashMap<&str, &str> = [
|
||||||
|
("pkg_manager_run_command", pkg_manager.run_cmd()),
|
||||||
|
("lib_name", &lib_name),
|
||||||
|
("project_name", project_name),
|
||||||
|
("package_name", package_name),
|
||||||
|
(
|
||||||
|
"double_dash_with_space",
|
||||||
|
if pkg_manager == PackageManager::Unknown {
|
||||||
|
"-- "
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let template_data: HashMap<&str, String> = [
|
||||||
|
("stable", (!alpha).to_string()),
|
||||||
|
("alpha", alpha.to_string()),
|
||||||
|
("mobile", mobile.to_string()),
|
||||||
|
("project_name", project_name.to_string()),
|
||||||
|
("package_name", package_name.to_string()),
|
||||||
|
(
|
||||||
|
"before_dev_command",
|
||||||
|
crate::lte::render(
|
||||||
|
manifest.before_dev_command.unwrap_or_default(),
|
||||||
|
&manifest_template_data,
|
||||||
|
)?,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"before_build_command",
|
||||||
|
crate::lte::render(
|
||||||
|
manifest.before_build_command.unwrap_or_default(),
|
||||||
|
&manifest_template_data,
|
||||||
|
)?,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dev_path",
|
||||||
|
crate::lte::render(
|
||||||
|
manifest.dev_path.unwrap_or_default(),
|
||||||
|
&manifest_template_data,
|
||||||
|
)?,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"dist_dir",
|
||||||
|
crate::lte::render(
|
||||||
|
manifest.dist_dir.unwrap_or_default(),
|
||||||
|
&manifest_template_data,
|
||||||
|
)?,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"with_global_makepad",
|
||||||
|
manifest.with_global_makepad.unwrap_or_default().to_string(),
|
||||||
|
),
|
||||||
|
("lib_name", lib_name),
|
||||||
|
]
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let write_file = |file: &str, template_data| -> anyhow::Result<()> {
|
||||||
|
// remove the first component, which is certainly the fragment directory they were in before getting embeded into the binary
|
||||||
|
let p = path::PathBuf::from(file)
|
||||||
|
.components()
|
||||||
|
.skip(1)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.iter()
|
||||||
|
.collect::<path::PathBuf>();
|
||||||
|
|
||||||
|
let p = target_dir.join(p);
|
||||||
|
let file_name = p.file_name().unwrap().to_string_lossy();
|
||||||
|
|
||||||
|
let file_name = match &*file_name {
|
||||||
|
"_gitignore" => ".gitignore",
|
||||||
|
// skip manifest
|
||||||
|
"_cta_manifest_" => return Ok(()),
|
||||||
|
// conditional files:
|
||||||
|
// are files that start with a special syntax
|
||||||
|
// "%(<list of flags separated by `-`>%)<file_name>"
|
||||||
|
// flags are supported package managers, stable, alpha and mobile.
|
||||||
|
// example: "%(pnpm-npm-yarn-stable-alpha)%package.json"
|
||||||
|
name if name.starts_with("%(") && name[1..].contains(")%") => {
|
||||||
|
let mut s = name.strip_prefix("%(").unwrap().split(")%");
|
||||||
|
let (mut flags, name) = (
|
||||||
|
s.next().unwrap().split('-').collect::<Vec<_>>(),
|
||||||
|
s.next().unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let for_stable = flags.contains(&"stable");
|
||||||
|
let for_alpha = flags.contains(&"alpha");
|
||||||
|
let for_mobile = flags.contains(&"mobile");
|
||||||
|
|
||||||
|
// remove these flags to only keep package managers flags
|
||||||
|
flags.retain(|e| !["stable", "alpha", "mobile"].contains(e));
|
||||||
|
|
||||||
|
if ((for_stable && !alpha)
|
||||||
|
|| (for_alpha && alpha && !mobile)
|
||||||
|
|| (for_mobile && alpha && mobile)
|
||||||
|
|| (!for_stable && !for_alpha && !for_mobile))
|
||||||
|
&& (flags.contains(&pkg_manager.to_string().as_str()) || flags.is_empty())
|
||||||
|
{
|
||||||
|
name
|
||||||
|
} else {
|
||||||
|
// skip writing this file
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name => name,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only modify files that need to use the template engine
|
||||||
|
let (file_data, file_name) = if let Some(file_name) = file_name.strip_suffix(".lts") {
|
||||||
|
let file_data = FRAGMENTS::get(file).unwrap().data.to_vec();
|
||||||
|
let file_data_as_str = std::str::from_utf8(&file_data)?;
|
||||||
|
(
|
||||||
|
crate::lte::render(file_data_as_str, template_data)?.into_bytes(),
|
||||||
|
file_name,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(FRAGMENTS::get(file).unwrap().data.to_vec(), file_name)
|
||||||
|
};
|
||||||
|
|
||||||
|
let parent = p.parent().unwrap();
|
||||||
|
fs::create_dir_all(parent)?;
|
||||||
|
fs::write(parent.join(file_name), file_data)?;
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. write base files
|
||||||
|
for file in FRAGMENTS::iter().filter(|e| {
|
||||||
|
path::PathBuf::from(e.to_string())
|
||||||
|
.components()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.as_os_str()
|
||||||
|
== "_base_"
|
||||||
|
}) {
|
||||||
|
write_file(&file, &template_data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. write template files which can override files from base
|
||||||
|
for file in FRAGMENTS::iter().filter(|e| {
|
||||||
|
path::PathBuf::from(e.to_string())
|
||||||
|
.components()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.as_os_str()
|
||||||
|
== path::PathBuf::from(format!("fragment-{self}"))
|
||||||
|
}) {
|
||||||
|
write_file(&file, &template_data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. write extra files specified in the fragment manifest
|
||||||
|
for (src, dest) in manifest.files {
|
||||||
|
let data = FRAGMENTS::get(&format!("_assets_/{src}"))
|
||||||
|
.with_context(|| format!("Failed to get asset file bytes: {src}"))?
|
||||||
|
.data;
|
||||||
|
let dest = target_dir.join(dest);
|
||||||
|
let parent = dest.parent().unwrap();
|
||||||
|
fs::create_dir_all(parent)?;
|
||||||
|
let mut file = fs::OpenOptions::new()
|
||||||
|
.append(true)
|
||||||
|
.create(true)
|
||||||
|
.open(dest)?;
|
||||||
|
file.write_all(&data)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Flavor {
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Flavor {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Flavor::Unknown => write!(f, "Unknown"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
449
packages/cli/src/theme.rs
Normal file
449
packages/cli/src/theme.rs
Normal file
|
@ -0,0 +1,449 @@
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use dialoguer::{
|
||||||
|
console::{style, Style, StyledObject},
|
||||||
|
theme::Theme,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ColorfulTheme {
|
||||||
|
/// The style for default values
|
||||||
|
pub defaults_style: Style,
|
||||||
|
/// The style for prompt
|
||||||
|
pub prompt_style: Style,
|
||||||
|
/// Prompt prefix value and style
|
||||||
|
pub prompt_prefix: StyledObject<String>,
|
||||||
|
/// Prompt suffix value and style
|
||||||
|
pub prompt_suffix: StyledObject<String>,
|
||||||
|
/// Prompt on success prefix value and style
|
||||||
|
pub success_prefix: StyledObject<String>,
|
||||||
|
/// Prompt on success suffix value and style
|
||||||
|
pub success_suffix: StyledObject<String>,
|
||||||
|
/// Error prefix value and style
|
||||||
|
pub error_prefix: StyledObject<String>,
|
||||||
|
/// The style for error message
|
||||||
|
pub error_style: Style,
|
||||||
|
/// The style for hints
|
||||||
|
pub hint_style: Style,
|
||||||
|
/// The style for values on prompt success
|
||||||
|
pub values_style: Style,
|
||||||
|
/// The style for active items
|
||||||
|
pub active_item_style: Style,
|
||||||
|
/// The style for inactive items
|
||||||
|
pub inactive_item_style: Style,
|
||||||
|
/// Active item in select prefix value and style
|
||||||
|
pub active_item_prefix: StyledObject<String>,
|
||||||
|
/// Inctive item in select prefix value and style
|
||||||
|
pub inactive_item_prefix: StyledObject<String>,
|
||||||
|
/// Checked item in multi select prefix value and style
|
||||||
|
pub checked_item_prefix: StyledObject<String>,
|
||||||
|
/// Unchecked item in multi select prefix value and style
|
||||||
|
pub unchecked_item_prefix: StyledObject<String>,
|
||||||
|
/// Picked item in sort prefix value and style
|
||||||
|
pub picked_item_prefix: StyledObject<String>,
|
||||||
|
/// Unpicked item in sort prefix value and style
|
||||||
|
pub unpicked_item_prefix: StyledObject<String>,
|
||||||
|
/// Formats the cursor for a fuzzy select prompt
|
||||||
|
#[cfg(feature = "fuzzy-select")]
|
||||||
|
pub fuzzy_cursor_style: Style,
|
||||||
|
// Formats the highlighting if matched characters
|
||||||
|
#[cfg(feature = "fuzzy-select")]
|
||||||
|
pub fuzzy_match_highlight_style: Style,
|
||||||
|
/// Show the selections from certain prompts inline
|
||||||
|
pub inline_selections: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ColorfulTheme {
|
||||||
|
fn default() -> ColorfulTheme {
|
||||||
|
ColorfulTheme {
|
||||||
|
defaults_style: Style::new().for_stderr().cyan(),
|
||||||
|
prompt_style: Style::new().for_stderr().bold(),
|
||||||
|
prompt_prefix: style("?".to_string()).for_stderr().yellow(),
|
||||||
|
prompt_suffix: style("›".to_string()).for_stderr().black().bright(),
|
||||||
|
success_prefix: style("✔".to_string()).for_stderr().green(),
|
||||||
|
success_suffix: style("·".to_string()).for_stderr().black().bright(),
|
||||||
|
error_prefix: style("✘".to_string()).for_stderr().red(),
|
||||||
|
error_style: Style::new().for_stderr().red(),
|
||||||
|
hint_style: Style::new().for_stderr().black().bright(),
|
||||||
|
values_style: Style::new().for_stderr().green(),
|
||||||
|
active_item_style: Style::new().for_stderr().cyan(),
|
||||||
|
inactive_item_style: Style::new().for_stderr(),
|
||||||
|
active_item_prefix: style("❯".to_string()).for_stderr().green(),
|
||||||
|
inactive_item_prefix: style(" ".to_string()).for_stderr(),
|
||||||
|
checked_item_prefix: style("✔".to_string()).for_stderr().green(),
|
||||||
|
unchecked_item_prefix: style("✔".to_string()).for_stderr().black(),
|
||||||
|
picked_item_prefix: style("❯".to_string()).for_stderr().green(),
|
||||||
|
unpicked_item_prefix: style(" ".to_string()).for_stderr(),
|
||||||
|
#[cfg(feature = "fuzzy-select")]
|
||||||
|
fuzzy_cursor_style: Style::new().for_stderr().black().on_white(),
|
||||||
|
#[cfg(feature = "fuzzy-select")]
|
||||||
|
fuzzy_match_highlight_style: Style::new().for_stderr().bold(),
|
||||||
|
inline_selections: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Theme for ColorfulTheme {
|
||||||
|
/// Formats a prompt.
|
||||||
|
fn format_prompt(&self, f: &mut dyn fmt::Write, prompt: &str) -> fmt::Result {
|
||||||
|
if !prompt.is_empty() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} {} ",
|
||||||
|
&self.prompt_prefix,
|
||||||
|
self.prompt_style.apply_to(prompt)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, "{}", &self.prompt_suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats an error
|
||||||
|
fn format_error(&self, f: &mut dyn fmt::Write, err: &str) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} {}",
|
||||||
|
&self.error_prefix,
|
||||||
|
self.error_style.apply_to(err)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats an input prompt.
|
||||||
|
fn format_input_prompt(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn fmt::Write,
|
||||||
|
prompt: &str,
|
||||||
|
default: Option<&str>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
if !prompt.is_empty() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} {} ",
|
||||||
|
&self.prompt_prefix,
|
||||||
|
self.prompt_style.apply_to(prompt)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match default {
|
||||||
|
Some(default) => write!(
|
||||||
|
f,
|
||||||
|
"{} {} ",
|
||||||
|
self.hint_style.apply_to(&format!("({default})")),
|
||||||
|
&self.prompt_suffix
|
||||||
|
),
|
||||||
|
None => write!(f, "{} ", &self.prompt_suffix),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a confirm prompt.
|
||||||
|
fn format_confirm_prompt(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn fmt::Write,
|
||||||
|
prompt: &str,
|
||||||
|
default: Option<bool>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
if !prompt.is_empty() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} {} ",
|
||||||
|
&self.prompt_prefix,
|
||||||
|
self.prompt_style.apply_to(prompt)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match default {
|
||||||
|
None => write!(
|
||||||
|
f,
|
||||||
|
"{} {}",
|
||||||
|
self.hint_style.apply_to("(y/n)"),
|
||||||
|
&self.prompt_suffix
|
||||||
|
),
|
||||||
|
Some(true) => write!(
|
||||||
|
f,
|
||||||
|
"{} {} {}",
|
||||||
|
self.hint_style.apply_to("(y/n)"),
|
||||||
|
&self.prompt_suffix,
|
||||||
|
self.defaults_style.apply_to("yes")
|
||||||
|
),
|
||||||
|
Some(false) => write!(
|
||||||
|
f,
|
||||||
|
"{} {} {}",
|
||||||
|
self.hint_style.apply_to("(y/n)"),
|
||||||
|
&self.prompt_suffix,
|
||||||
|
self.defaults_style.apply_to("no")
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a confirm prompt after selection.
|
||||||
|
fn format_confirm_prompt_selection(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn fmt::Write,
|
||||||
|
prompt: &str,
|
||||||
|
selection: Option<bool>,
|
||||||
|
) -> fmt::Result {
|
||||||
|
if !prompt.is_empty() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} {} ",
|
||||||
|
&self.success_prefix,
|
||||||
|
self.prompt_style.apply_to(prompt)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
let selection = selection.map(|b| if b { "yes" } else { "no" });
|
||||||
|
|
||||||
|
match selection {
|
||||||
|
Some(selection) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} {}",
|
||||||
|
&self.success_suffix,
|
||||||
|
self.values_style.apply_to(selection)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
write!(f, "{}", &self.success_suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats an input prompt after selection.
|
||||||
|
fn format_input_prompt_selection(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn fmt::Write,
|
||||||
|
prompt: &str,
|
||||||
|
sel: &str,
|
||||||
|
) -> fmt::Result {
|
||||||
|
if !prompt.is_empty() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} {} ",
|
||||||
|
&self.success_prefix,
|
||||||
|
self.prompt_style.apply_to(prompt)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} {}",
|
||||||
|
&self.success_suffix,
|
||||||
|
self.values_style.apply_to(sel)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a password prompt after selection.
|
||||||
|
#[cfg(feature = "password")]
|
||||||
|
fn format_password_prompt_selection(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn fmt::Write,
|
||||||
|
prompt: &str,
|
||||||
|
) -> fmt::Result {
|
||||||
|
self.format_input_prompt_selection(f, prompt, "********")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a multi select prompt after selection.
|
||||||
|
fn format_multi_select_prompt_selection(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn fmt::Write,
|
||||||
|
prompt: &str,
|
||||||
|
selections: &[&str],
|
||||||
|
) -> fmt::Result {
|
||||||
|
if !prompt.is_empty() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} {} ",
|
||||||
|
&self.success_prefix,
|
||||||
|
self.prompt_style.apply_to(prompt)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, "{} ", &self.success_suffix)?;
|
||||||
|
|
||||||
|
if self.inline_selections {
|
||||||
|
for (idx, sel) in selections.iter().enumerate() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}{}",
|
||||||
|
if idx == 0 { "" } else { ", " },
|
||||||
|
self.values_style.apply_to(sel)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a select prompt item.
|
||||||
|
fn format_select_prompt_item(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn fmt::Write,
|
||||||
|
text: &str,
|
||||||
|
active: bool,
|
||||||
|
) -> fmt::Result {
|
||||||
|
let (text, desc) = text.split_once('-').unwrap_or((text, ""));
|
||||||
|
|
||||||
|
if active {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} {}{}",
|
||||||
|
self.active_item_prefix,
|
||||||
|
self.active_item_style.apply_to(text),
|
||||||
|
self.hint_style.apply_to(desc),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} {}",
|
||||||
|
self.inactive_item_prefix,
|
||||||
|
self.inactive_item_style.apply_to(text),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a multi select prompt item.
|
||||||
|
fn format_multi_select_prompt_item(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn fmt::Write,
|
||||||
|
text: &str,
|
||||||
|
checked: bool,
|
||||||
|
active: bool,
|
||||||
|
) -> fmt::Result {
|
||||||
|
let details = match (checked, active) {
|
||||||
|
(true, true) => (
|
||||||
|
&self.checked_item_prefix,
|
||||||
|
self.active_item_style.apply_to(text),
|
||||||
|
),
|
||||||
|
(true, false) => (
|
||||||
|
&self.checked_item_prefix,
|
||||||
|
self.inactive_item_style.apply_to(text),
|
||||||
|
),
|
||||||
|
(false, true) => (
|
||||||
|
&self.unchecked_item_prefix,
|
||||||
|
self.active_item_style.apply_to(text),
|
||||||
|
),
|
||||||
|
(false, false) => (
|
||||||
|
&self.unchecked_item_prefix,
|
||||||
|
self.inactive_item_style.apply_to(text),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{} {}", details.0, details.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a sort prompt item.
|
||||||
|
fn format_sort_prompt_item(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn fmt::Write,
|
||||||
|
text: &str,
|
||||||
|
picked: bool,
|
||||||
|
active: bool,
|
||||||
|
) -> fmt::Result {
|
||||||
|
let details = match (picked, active) {
|
||||||
|
(true, true) => (
|
||||||
|
&self.picked_item_prefix,
|
||||||
|
self.active_item_style.apply_to(text),
|
||||||
|
),
|
||||||
|
(false, true) => (
|
||||||
|
&self.unpicked_item_prefix,
|
||||||
|
self.active_item_style.apply_to(text),
|
||||||
|
),
|
||||||
|
(_, false) => (
|
||||||
|
&self.unpicked_item_prefix,
|
||||||
|
self.inactive_item_style.apply_to(text),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{} {}", details.0, details.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a fuzzy select prompt item.
|
||||||
|
#[cfg(feature = "fuzzy-select")]
|
||||||
|
fn format_fuzzy_select_prompt_item(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn fmt::Write,
|
||||||
|
text: &str,
|
||||||
|
active: bool,
|
||||||
|
highlight_matches: bool,
|
||||||
|
matcher: &SkimMatcherV2,
|
||||||
|
search_term: &str,
|
||||||
|
) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} ",
|
||||||
|
if active {
|
||||||
|
&self.active_item_prefix
|
||||||
|
} else {
|
||||||
|
&self.inactive_item_prefix
|
||||||
|
}
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if highlight_matches {
|
||||||
|
if let Some((_score, indices)) = matcher.fuzzy_indices(text, &search_term) {
|
||||||
|
for (idx, c) in text.chars().into_iter().enumerate() {
|
||||||
|
if indices.contains(&idx) {
|
||||||
|
if active {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
self.active_item_style
|
||||||
|
.apply_to(self.fuzzy_match_highlight_style.apply_to(c))
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{}", self.fuzzy_match_highlight_style.apply_to(c))?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if active {
|
||||||
|
write!(f, "{}", self.active_item_style.apply_to(c))?;
|
||||||
|
} else {
|
||||||
|
write!(f, "{}", c)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, "{}", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a fuzzy-selectprompt after selection.
|
||||||
|
#[cfg(feature = "fuzzy-select")]
|
||||||
|
fn format_fuzzy_select_prompt(
|
||||||
|
&self,
|
||||||
|
f: &mut dyn fmt::Write,
|
||||||
|
prompt: &str,
|
||||||
|
search_term: &str,
|
||||||
|
cursor_pos: usize,
|
||||||
|
) -> fmt::Result {
|
||||||
|
if !prompt.is_empty() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} {} ",
|
||||||
|
&self.prompt_prefix,
|
||||||
|
self.prompt_style.apply_to(prompt)
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if cursor_pos < search_term.len() {
|
||||||
|
let st_head = search_term[0..cursor_pos].to_string();
|
||||||
|
let st_tail = search_term[cursor_pos + 1..search_term.len()].to_string();
|
||||||
|
let st_cursor = self
|
||||||
|
.fuzzy_cursor_style
|
||||||
|
.apply_to(search_term.to_string().chars().nth(cursor_pos).unwrap());
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} {}{}{}",
|
||||||
|
&self.prompt_suffix, st_head, st_cursor, st_tail
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let cursor = self.fuzzy_cursor_style.apply_to(" ");
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{} {}{}",
|
||||||
|
&self.prompt_suffix,
|
||||||
|
search_term.to_string(),
|
||||||
|
cursor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue