Skip to content

Commit 441b0ba

Browse files
authored
feat: add --identifier flag and prompt (#752)
1 parent ab42e1f commit 441b0ba

10 files changed

Lines changed: 148 additions & 99 deletions

File tree

.changes/identifier.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"create-tauri-app": "minor"
3+
"create-tauri-app-js": "minor"
4+
---
5+
6+
Add `--identifier` flag and prompt to choose the identifier of your app.
7+

src/args.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ pub struct Args {
1313
pub project_name: Option<String>,
1414
pub manager: Option<PackageManager>,
1515
pub template: Option<Template>,
16+
pub identifier: Option<String>,
1617
pub skip: bool,
1718
pub force: bool,
1819
pub rc: bool,
@@ -22,6 +23,7 @@ impl Default for Args {
2223
fn default() -> Self {
2324
Self {
2425
project_name: Some("tauri-app".to_string()),
26+
identifier: Some("com.tauri.dev".to_string()),
2527
manager: Some(PackageManager::Npm),
2628
template: Some(Template::Vanilla),
2729
skip: false,
@@ -50,6 +52,7 @@ pub fn parse(argv: Vec<OsString>, bin_name: Option<String>) -> anyhow::Result<Ar
5052
{YELLOW}OPTIONS:{RESET}
5153
{GREEN}-m{RESET}, {GREEN}--manager <MANAGER>{RESET} Specify preferred package manager [{managers}]
5254
{GREEN}-t{RESET}, {GREEN}--template <TEMPLATE>{RESET} Specify the UI template to use [{templates}]
55+
{GREEN}--identifier <identifier>{RESET} Specify a unique identifier for your application
5356
{GREEN}-y{RESET}, {GREEN}--yes{RESET} Skip prompts and use defaults where applicable
5457
{GREEN}-f{RESET}, {GREEN}--force{RESET} Force create the directory even if it is not empty.
5558
{GREEN}--rc{RESET} Bootstraps a project using tauri@2.0-rc.
@@ -101,6 +104,7 @@ pub fn parse(argv: Vec<OsString>, bin_name: Option<String>) -> anyhow::Result<Ar
101104
skip: pargs.contains(["-y", "--yes"]),
102105
force: pargs.contains(["-f", "--force"]),
103106
rc,
107+
identifier: pargs.opt_value_from_str("--identifier")?,
104108
project_name: pargs.opt_free_from_str()?,
105109
};
106110

src/lib.rs

Lines changed: 35 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
category::Category,
1111
deps::print_missing_deps,
1212
package_manager::PackageManager,
13-
utils::{colors::*, theme::ColorfulTheme},
13+
utils::{colors::*, dialoguer_theme::ColorfulTheme},
1414
};
1515

1616
mod args;
@@ -71,6 +71,7 @@ where
7171
project_name,
7272
template,
7373
force,
74+
identifier,
7475
} = args;
7576
let cwd = std::env::current_dir()?;
7677

@@ -79,14 +80,15 @@ where
7980
let project_name = match project_name {
8081
Some(name) => name,
8182
None => {
83+
let default = defaults
84+
.project_name
85+
.context("default project_name not set")?;
8286
if skip {
83-
defaults
84-
.project_name
85-
.context("default project_name not set")?
87+
default
8688
} else {
8789
Input::<String>::with_theme(&ColorfulTheme::default())
8890
.with_prompt("Project name")
89-
.default("tauri-app".into())
91+
.default(default)
9092
.interact_text()?
9193
.trim()
9294
.into()
@@ -97,10 +99,10 @@ where
9799
let target_dir = cwd.join(&project_name);
98100

99101
// Package name used in Cargo.toml, Package.json ...etc
100-
let package_name = if is_valid_pkg_name(&project_name) {
102+
let package_name = if utils::is_valid_pkg_name(&project_name) {
101103
project_name.clone()
102104
} else {
103-
let valid_name = to_valid_pkg_name(&project_name);
105+
let valid_name = utils::to_valid_pkg_name(&project_name);
104106
if skip {
105107
valid_name
106108
} else {
@@ -109,7 +111,7 @@ where
109111
.default(valid_name.clone())
110112
.with_initial_text(valid_name)
111113
.validate_with(|input: &String| {
112-
if is_valid_pkg_name(input) {
114+
if utils::is_valid_pkg_name(input) {
113115
Ok(())
114116
} else {
115117
Err("Package name should only include lowercase alphanumeric character and hyphens \"-\" and doesn't start with numbers")
@@ -120,6 +122,23 @@ where
120122
}
121123
};
122124

125+
let identifier = match identifier {
126+
Some(name) => name,
127+
None => {
128+
let default = format!("com.{package_name}.app");
129+
if skip {
130+
default
131+
} else {
132+
Input::<String>::with_theme(&ColorfulTheme::default())
133+
.with_prompt("Identifier")
134+
.default(default)
135+
.interact_text()?
136+
.trim()
137+
.into()
138+
}
139+
}
140+
};
141+
123142
// Confirm deleting the target project directory if not empty
124143
if target_dir.exists() && target_dir.read_dir()?.next().is_some() {
125144
let overwrite = if force {
@@ -303,7 +322,14 @@ where
303322
}
304323

305324
// Render the template
306-
template.render(&target_dir, pkg_manager, &project_name, &package_name, rc)?;
325+
template.render(
326+
&target_dir,
327+
pkg_manager,
328+
&project_name,
329+
&package_name,
330+
&identifier,
331+
rc,
332+
)?;
307333

308334
// Print post-render instructions
309335
println!();
@@ -350,70 +376,3 @@ where
350376
println!();
351377
Ok(())
352378
}
353-
354-
fn is_valid_pkg_name(project_name: &str) -> bool {
355-
let mut chars = project_name.chars().peekable();
356-
!project_name.is_empty()
357-
&& !chars.peek().map(|c| c.is_ascii_digit()).unwrap_or_default()
358-
&& !chars.any(|ch| !(ch.is_alphanumeric() || ch == '-' || ch == '_') || ch.is_uppercase())
359-
}
360-
361-
fn to_valid_pkg_name(project_name: &str) -> String {
362-
let ret = project_name
363-
.trim()
364-
.to_lowercase()
365-
.replace([':', ';', ' ', '~'], "-")
366-
.replace(['.', '\\', '/'], "");
367-
368-
let ret = ret
369-
.chars()
370-
.skip_while(|ch| ch.is_ascii_digit() || *ch == '-')
371-
.collect::<String>();
372-
373-
if ret.is_empty() {
374-
"tauri-app".to_string()
375-
} else {
376-
ret
377-
}
378-
}
379-
380-
#[cfg(test)]
381-
mod test {
382-
use super::*;
383-
#[test]
384-
fn valiadtes_pkg_name() {
385-
assert!(is_valid_pkg_name("tauri-app"));
386-
assert!(is_valid_pkg_name("tauri_app"));
387-
assert!(is_valid_pkg_name("t2auriapp"));
388-
assert!(!is_valid_pkg_name("1tauriapp"));
389-
assert!(!is_valid_pkg_name("tauri app"));
390-
assert!(!is_valid_pkg_name("tauri:app"));
391-
assert!(!is_valid_pkg_name("tauri.app"));
392-
assert!(!is_valid_pkg_name("tauri/app"));
393-
assert!(!is_valid_pkg_name("tauri\\app"));
394-
assert!(!is_valid_pkg_name("tauri~app"));
395-
assert!(!is_valid_pkg_name("Tauriapp"));
396-
}
397-
398-
#[test]
399-
fn converts_to_valid_pkg_name() {
400-
assert_eq!(to_valid_pkg_name("tauri-app"), "tauri-app");
401-
assert_eq!(to_valid_pkg_name("tauri_app"), "tauri_app");
402-
assert_eq!(to_valid_pkg_name("t2auriapp"), "t2auriapp");
403-
assert_eq!(to_valid_pkg_name("1tauriapp"), "tauriapp");
404-
assert_eq!(to_valid_pkg_name("123tauriapp"), "tauriapp");
405-
assert_eq!(to_valid_pkg_name("123-tauriapp"), "tauriapp");
406-
assert_eq!(to_valid_pkg_name("tauri app"), "tauri-app");
407-
assert_eq!(to_valid_pkg_name("tauri:app"), "tauri-app");
408-
assert_eq!(to_valid_pkg_name("tauri;app"), "tauri-app");
409-
assert_eq!(to_valid_pkg_name("tauri.app"), "tauriapp");
410-
assert_eq!(to_valid_pkg_name("tauri/app"), "tauriapp");
411-
assert_eq!(to_valid_pkg_name("tauri\\app"), "tauriapp");
412-
assert_eq!(to_valid_pkg_name("tauri~app"), "tauri-app");
413-
assert_eq!(to_valid_pkg_name("-tauri.app"), "tauriapp");
414-
assert_eq!(to_valid_pkg_name("-123tauri.app"), "tauriapp");
415-
assert_eq!(to_valid_pkg_name("-2-123tau2ri-app-2"), "tau2ri-app-2");
416-
assert_eq!(to_valid_pkg_name("1-2-3tau2ri-app2-"), "tau2ri-app2-");
417-
assert_eq!(to_valid_pkg_name("Tauriapp"), "tauriapp");
418-
}
419-
}

src/template.rs

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rust_embed::RustEmbed;
1010
use crate::{
1111
manifest::Manifest,
1212
package_manager::PackageManager,
13-
utils::{colors::*, lte},
13+
utils::{self, colors::*, lte},
1414
};
1515

1616
const CTA_MANIFEST_FILENAME: &str = ".manifest";
@@ -256,6 +256,7 @@ impl<'a> Template {
256256
pkg_manager: PackageManager,
257257
project_name: &str,
258258
package_name: &str,
259+
identifier: &str,
259260
rc: bool,
260261
) -> anyhow::Result<()> {
261262
let manifest_bytes =
@@ -267,7 +268,7 @@ impl<'a> Template {
267268
let manifest = Manifest::parse(&manifest_str, rc)?;
268269

269270
let lib_name = format!("{}_lib", package_name.replace('-', "_"));
270-
let project_name_pascal_case = Self::transform_to_pascal_case(project_name.to_string());
271+
let project_name_pascal_case = utils::to_pascal_case(project_name);
271272

272273
let rc_str = rc.to_string();
273274
let manifest_template_data: HashMap<&str, &str> = [
@@ -276,6 +277,7 @@ impl<'a> Template {
276277
("lib_name", &lib_name),
277278
("package_name", package_name),
278279
("project_name", project_name),
280+
("identifier", identifier),
279281
("project_name_pascal_case", &project_name_pascal_case),
280282
(
281283
"double_dash_with_space",
@@ -304,6 +306,7 @@ impl<'a> Template {
304306
project_name_pascal_case.to_string(),
305307
),
306308
("package_name", package_name.to_string()),
309+
("identifier", identifier.to_string()),
307310
(
308311
"before_dev_command",
309312
lte::render(
@@ -460,22 +463,4 @@ impl<'a> Template {
460463

461464
Ok(())
462465
}
463-
464-
fn transform_to_pascal_case(s: String) -> String {
465-
let mut result = String::new();
466-
let mut capitalize_next = false;
467-
for (s, c) in s.chars().enumerate() {
468-
if s == 0 {
469-
result.push(c.to_ascii_uppercase());
470-
} else if capitalize_next {
471-
result.push(c.to_ascii_uppercase());
472-
capitalize_next = false;
473-
} else if ['_', '-'].contains(&c) {
474-
capitalize_next = true;
475-
} else {
476-
result.push(c);
477-
}
478-
}
479-
result
480-
}
481466
}

src/utils/colors.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub const ITALIC: &str = "\x1b[3m";
1616
pub const DIM: &str = "\x1b[2m";
1717
pub const DIMRESET: &str = "\x1b[22m";
1818

19+
#[inline]
1920
pub fn remove_colors(s: &str) -> String {
2021
s.replace(BLACK, "")
2122
.replace(RED, "")

src/utils/lte.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ impl<'a> Iterator for Lexer<'a> {
191191
}
192192
}
193193

194+
#[inline]
194195
fn is_truthy(value: &[u8]) -> bool {
195196
match value {
196197
TRUE => true,

src/utils/mod.rs

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,95 @@
11
pub mod colors;
2+
pub mod dialoguer_theme;
23
pub mod lte;
3-
pub mod theme;
4+
5+
pub fn is_valid_pkg_name(project_name: &str) -> bool {
6+
let mut chars = project_name.chars().peekable();
7+
!project_name.is_empty()
8+
&& !chars.peek().map(|c| c.is_ascii_digit()).unwrap_or_default()
9+
&& !chars.any(|ch| !(ch.is_alphanumeric() || ch == '-' || ch == '_') || ch.is_uppercase())
10+
}
11+
12+
pub fn to_valid_pkg_name(project_name: &str) -> String {
13+
let ret = project_name
14+
.trim()
15+
.to_lowercase()
16+
.replace([':', ';', ' ', '~'], "-")
17+
.replace(['.', '\\', '/'], "");
18+
19+
let ret = ret
20+
.chars()
21+
.skip_while(|ch| ch.is_ascii_digit() || *ch == '-')
22+
.collect::<String>();
23+
24+
if ret.is_empty() {
25+
"tauri-app".to_string()
26+
} else {
27+
ret
28+
}
29+
}
30+
31+
#[inline]
32+
pub fn to_pascal_case(s: &str) -> String {
33+
let mut result = String::new();
34+
let mut capitalize_next = false;
35+
for (s, c) in s.chars().enumerate() {
36+
if s == 0 {
37+
result.push(c.to_ascii_uppercase());
38+
} else if capitalize_next {
39+
result.push(c.to_ascii_uppercase());
40+
capitalize_next = false;
41+
} else if ['_', '-'].contains(&c) {
42+
capitalize_next = true;
43+
} else {
44+
result.push(c);
45+
}
46+
}
47+
result
48+
}
49+
50+
#[cfg(test)]
51+
mod test {
52+
use super::*;
53+
#[test]
54+
fn valiadtes_pkg_name() {
55+
assert!(is_valid_pkg_name("tauri-app"));
56+
assert!(is_valid_pkg_name("tauri_app"));
57+
assert!(is_valid_pkg_name("t2auriapp"));
58+
assert!(!is_valid_pkg_name("1tauriapp"));
59+
assert!(!is_valid_pkg_name("tauri app"));
60+
assert!(!is_valid_pkg_name("tauri:app"));
61+
assert!(!is_valid_pkg_name("tauri.app"));
62+
assert!(!is_valid_pkg_name("tauri/app"));
63+
assert!(!is_valid_pkg_name("tauri\\app"));
64+
assert!(!is_valid_pkg_name("tauri~app"));
65+
assert!(!is_valid_pkg_name("Tauriapp"));
66+
}
67+
68+
#[test]
69+
fn converts_to_valid_pkg_name() {
70+
assert_eq!(to_valid_pkg_name("tauri-app"), "tauri-app");
71+
assert_eq!(to_valid_pkg_name("tauri_app"), "tauri_app");
72+
assert_eq!(to_valid_pkg_name("t2auriapp"), "t2auriapp");
73+
assert_eq!(to_valid_pkg_name("1tauriapp"), "tauriapp");
74+
assert_eq!(to_valid_pkg_name("123tauriapp"), "tauriapp");
75+
assert_eq!(to_valid_pkg_name("123-tauriapp"), "tauriapp");
76+
assert_eq!(to_valid_pkg_name("tauri app"), "tauri-app");
77+
assert_eq!(to_valid_pkg_name("tauri:app"), "tauri-app");
78+
assert_eq!(to_valid_pkg_name("tauri;app"), "tauri-app");
79+
assert_eq!(to_valid_pkg_name("tauri.app"), "tauriapp");
80+
assert_eq!(to_valid_pkg_name("tauri/app"), "tauriapp");
81+
assert_eq!(to_valid_pkg_name("tauri\\app"), "tauriapp");
82+
assert_eq!(to_valid_pkg_name("tauri~app"), "tauri-app");
83+
assert_eq!(to_valid_pkg_name("-tauri.app"), "tauriapp");
84+
assert_eq!(to_valid_pkg_name("-123tauri.app"), "tauriapp");
85+
assert_eq!(to_valid_pkg_name("-2-123tau2ri-app-2"), "tau2ri-app-2");
86+
assert_eq!(to_valid_pkg_name("1-2-3tau2ri-app2-"), "tau2ri-app2-");
87+
assert_eq!(to_valid_pkg_name("Tauriapp"), "tauriapp");
88+
}
89+
90+
#[test]
91+
fn converts_to_pascal_case() {
92+
assert_eq!(to_pascal_case("tauri-app"), "TauriApp");
93+
assert_eq!(to_pascal_case("tau2ri-app"), "Tau2riApp");
94+
}
95+
}

templates/_base_/src-tauri/%(rc)%tauri.conf.json.lte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"productName": "{% package_name %}",
33
"version": "0.1.0",
4-
"identifier": "com.tauri.dev",
4+
"identifier": "{% identifier %}",
55
"build": {
66
{% if before_dev_command %}"beforeDevCommand": "{% before_dev_command %}",
77
{% endif %}{% if dev_path %}"devUrl": "{% dev_path %}",

0 commit comments

Comments
 (0)