first commit
This commit is contained in:
		
						commit
						117a0fc5ed
					
				
					 34 changed files with 3100 additions and 0 deletions
				
			
		
							
								
								
									
										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