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…
	
	Add table
		Add a link
		
	
		Reference in a new issue