Эх сурвалжийг харах

feat(tauri) add app CLI interface config (#670)

Lucas Fernandes Nogueira 5 жил өмнө
parent
commit
14a1ddfe18

+ 7 - 0
.changes/arg_parsing_feature.md

@@ -0,0 +1,7 @@
+---
+"tauri.js": minor
+"tauri": minor
+"tauri-api": minor
+---
+
+Adds a command line interface option to tauri apps, configurable under tauri.conf.json > tauri > cli.

+ 2 - 2
.github/workflows/check-on-push.yml

@@ -21,8 +21,8 @@ jobs:
         with:
           token: ${{ secrets.GITHUB_TOKEN }}
         env:
-          TAURI_DIST_DIR: ${{ runner.workspace }}/tauri/tauri/test/fixture/dist
-          TAURI_DIR: ${{ runner.workspace }}/tauri/tauri/test/fixture/src-tauri
+          TAURI_DIST_DIR: ${{ runner.workspace }}/tauri/tauri/examples/communication/dist
+          TAURI_DIR: ${{ runner.workspace }}/tauri/tauri/examples/communication/src-tauri
 
   eslint-check:
     runs-on: ubuntu-latest

+ 6 - 6
.github/workflows/release-cargo.yml

@@ -58,8 +58,8 @@ jobs:
         if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
         working-directory: ${{ matrix.package.path }}
         env:
-          TAURI_DIST_DIR: ${{ runner.workspace }}/tauri/tauri/test/fixture/dist
-          TAURI_DIR: ${{ runner.workspace }}/tauri/tauri/test/fixture/src-tauri
+          TAURI_DIST_DIR: ${{ runner.workspace }}/tauri/tauri/examples/communication/dist
+          TAURI_DIR: ${{ runner.workspace }}/tauri/tauri/examples/communication/src-tauri
         run: |
           cargo package --no-verify
           echo "We will publish:" $PACKAGE_VERSION
@@ -68,8 +68,8 @@ jobs:
         if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
         working-directory: ${{ matrix.package.path }}
         env:
-          TAURI_DIST_DIR: ${{ runner.workspace }}/tauri/tauri/test/fixture/dist
-          TAURI_DIR: ${{ runner.workspace }}/tauri/tauri/test/fixture/src-tauri
+          TAURI_DIST_DIR: ${{ runner.workspace }}/tauri/tauri/examples/communication/dist
+          TAURI_DIR: ${{ runner.workspace }}/tauri/tauri/examples/communication/src-tauri
         run: |
           cargo install cargo-audit
           echo "# Cargo Audit" | tee -a ${{runner.workspace }}/notes.md
@@ -80,8 +80,8 @@ jobs:
         if: env.PACKAGE_VERSION != env.PUBLISHED_VERSION
         working-directory: ${{ matrix.package.path }}
         env:
-          TAURI_DIST_DIR: ${{ runner.workspace }}/tauri/tauri/test/fixture/dist
-          TAURI_DIR: ${{ runner.workspace }}/tauri/tauri/test/fixture/src-tauri
+          TAURI_DIST_DIR: ${{ runner.workspace }}/tauri/tauri/examples/communication/dist
+          TAURI_DIR: ${{ runner.workspace }}/tauri/tauri/examples/communication/src-tauri
         run: |
           echo "# Cargo Publish" | tee -a ${{runner.workspace }}/notes.md
           echo "\`\`\`" >> ${{runner.workspace }}/notes.md

+ 4 - 4
.github/workflows/test-on-pr.yml

@@ -27,14 +27,14 @@ jobs:
           cd ./tauri
           cargo build
         env:
-          TAURI_DIST_DIR: ${{ runner.workspace }}/tauri/tauri/test/fixture/dist
-          TAURI_DIR: ${{ runner.workspace }}/tauri/tauri/test/fixture/src-tauri
+          TAURI_DIST_DIR: ${{ runner.workspace }}/tauri/tauri/examples/communication/dist
+          TAURI_DIR: ${{ runner.workspace }}/tauri/tauri/examples/communication/src-tauri
       - name: test
         run: |
           cargo test
         env:
-          TAURI_DIST_DIR: ${{ runner.workspace }}/tauri/tauri/test/fixture/dist
-          TAURI_DIR: ${{ runner.workspace }}/tauri/tauri/test/fixture/src-tauri
+          TAURI_DIST_DIR: ${{ runner.workspace }}/tauri/tauri/examples/communication/dist
+          TAURI_DIR: ${{ runner.workspace }}/tauri/tauri/examples/communication/src-tauri
 
   build-tauri-bundler:
     runs-on: ${{ matrix.platform }}

+ 12 - 0
cli/tauri.js/api/cli.js

@@ -0,0 +1,12 @@
+import tauri from './tauri'
+
+/**
+ * gets the CLI matches
+ */
+function getMatches() {
+  return tauri.cliMatches()
+}
+
+export {
+  getMatches
+}

+ 4 - 0
cli/tauri.js/src/runner.ts

@@ -500,6 +500,10 @@ class Runner {
       tomlFeatures.push('edge')
     }
 
+    if (cfg.tauri.cli) {
+        tomlFeatures.push('cli')
+    }
+
     if (typeof manifest.dependencies.tauri === 'string') {
       manifest.dependencies.tauri = {
         version: manifest.dependencies.tauri,

+ 1 - 0
cli/tauri.js/src/template/defaultConfig.ts

@@ -7,6 +7,7 @@ export default {
   },
   ctx: {},
   tauri: {
+    cli: null,
     embeddedServer: {
       active: true
     },

+ 34 - 0
cli/tauri.js/src/types/config.ts

@@ -1,6 +1,39 @@
 // TODO: Clean up types, properly mark which ones are optional
 // May need to have different types for each stage of config generation process
 
+export interface CliArg {
+  short?: string
+  name: string
+  description?: string
+  longDescription?: string
+  takesValue?: boolean
+  multiple?: boolean
+  possibleValues?: string[]
+  minValues?: number
+  maxValues?: number
+  required?: boolean
+  requiredUnless?: string
+  requiredUnlessAll?: string[]
+  requiredUnlessOne?: string[]
+  conflictsWith?: string
+  conflictsWithAll?: string
+  requires?: string
+  requiresAll?: string[]
+  requiresIf?: [string, string]
+  requiredIf?: [string, string]
+  requireEquals?: boolean
+  global?: boolean
+}
+
+export interface CliConfig {
+  args?: CliArg[]
+  description?: string
+  longDescription?: string
+  beforeHelp?: string
+  afterHelp?: string
+  subcommands?: { [name: string]: CliConfig }
+}
+
 export interface TauriConfig {
   build: {
     distDir: string
@@ -17,6 +50,7 @@ export interface TauriConfig {
     exitOnPanic?: boolean
   }
   tauri: {
+    cli: CliConfig
     inlinedAssets: string[]
     devPath: string
     embeddedServer: {

+ 15 - 0
cli/tauri.js/templates/tauri.js

@@ -655,6 +655,21 @@ window.tauri = {
       asset: assetName,
       assetType: assetType || 'unknown'
     })
+  },
+
+  cliMatches: function () {
+    <% if (tauri.cli) { %>
+      return this.promisified({
+        cmd: 'cliMatches'
+      })
+    <% } else { %>
+      <% if (ctx.dev) { %>
+        console.error('You must add the CLI args configuration under tauri.conf.json > tauri > cli')
+        return __reject()
+      <% } %>
+        return __reject()
+      <% } %>
+    
   }
 };
 

+ 2 - 1
cli/tauri.js/tsconfig.json

@@ -11,7 +11,8 @@
     "baseUrl": ".",
     "paths": {
       "types": ["src/types"]
-    }
+    },
+    "resolveJsonModule": true
   },
   "include": ["src"]
 }

+ 5 - 0
tauri-api/Cargo.toml

@@ -29,8 +29,13 @@ nfd = "0.0.4"
 attohttpc = {version = "0.14.0", features=["json", "form" ]}
 http = "0.2"
 tauri-utils = {version = "0.5", path = "../tauri-utils"}
+envmnt = "0.8.2"
+clap = { git = "https://github.com/clap-rs/clap", rev = "1a276f8", version = "3.0.0-beta.1", optional = true }
 
 [dev-dependencies]
 quickcheck = "0.9.2"
 quickcheck_macros = "0.9.1"
 totems = "0.2.7"
+
+[features]
+cli = ["clap"]

+ 153 - 0
tauri-api/src/cli.rs

@@ -0,0 +1,153 @@
+use crate::config::{Cli, Config};
+
+use clap::{App, Arg, ArgMatches};
+use serde::Serialize;
+use serde_json::Value;
+use std::collections::HashMap;
+
+#[macro_use]
+mod macros;
+
+#[derive(Default, Debug, Serialize)]
+pub struct ArgData {
+  value: Value,
+  occurrences: u64,
+}
+
+#[derive(Default, Debug, Serialize)]
+pub struct SubcommandMatches {
+  name: String,
+  matches: Matches,
+}
+
+#[derive(Default, Debug, Serialize)]
+pub struct Matches {
+  args: HashMap<String, ArgData>,
+  subcommand: Option<Box<SubcommandMatches>>,
+}
+
+impl Matches {
+  pub(crate) fn set_arg(&mut self, name: String, value: ArgData) {
+    self.args.insert(name, value);
+  }
+
+  pub(crate) fn set_subcommand(&mut self, name: String, matches: Matches) {
+    self.subcommand = Some(Box::new(SubcommandMatches { name, matches }));
+  }
+}
+
+pub fn get_matches(config: Config) -> Matches {
+  let cli = config.tauri.cli.unwrap();
+
+  let about = cli
+    .description()
+    .unwrap_or(&crate_description!().to_string())
+    .to_string();
+  let app = get_app(crate_name!(), Some(&about), &cli);
+  let matches = app.get_matches();
+  get_matches_internal(&cli, &matches)
+}
+
+fn get_matches_internal<T: Cli + 'static>(config: &T, matches: &ArgMatches) -> Matches {
+  let mut cli_matches = Matches::default();
+  map_matches(config, matches, &mut cli_matches);
+
+  let (subcommand_name, subcommand_matches_option) = matches.subcommand();
+  if let Some(subcommand_matches) = subcommand_matches_option {
+    let mut subcommand_cli_matches = Matches::default();
+    map_matches(
+      config.subcommands().unwrap().get(subcommand_name).unwrap(),
+      subcommand_matches,
+      &mut subcommand_cli_matches,
+    );
+    cli_matches.set_subcommand(subcommand_name.to_string(), subcommand_cli_matches);
+  }
+
+  cli_matches
+}
+
+fn map_matches<T: Cli + 'static>(config: &T, matches: &ArgMatches, cli_matches: &mut Matches) {
+  if let Some(args) = config.args() {
+    for arg in args {
+      let occurrences = matches.occurrences_of(arg.name.clone());
+      let value = if occurrences == 0 || !arg.takes_value.unwrap_or(false) {
+        Value::Null
+      } else if arg.multiple.unwrap_or(false) {
+        matches
+          .values_of(arg.name.clone())
+          .map(|v| {
+            let mut values = Vec::new();
+            for value in v {
+              values.push(Value::String(value.to_string()));
+            }
+            Value::Array(values)
+          })
+          .unwrap_or(Value::Null)
+      } else {
+        matches
+          .value_of(arg.name.clone())
+          .map(|v| Value::String(v.to_string()))
+          .unwrap_or(Value::Null)
+      };
+
+      cli_matches.set_arg(arg.name.clone(), ArgData { value, occurrences });
+    }
+  }
+}
+
+fn get_app<'a, T: Cli + 'static>(name: &str, about: Option<&'a String>, config: &'a T) -> App<'a> {
+  let mut app = App::new(name)
+    .author(crate_authors!())
+    .version(crate_version!());
+
+  if let Some(about) = about {
+    app = app.about(&**about);
+  }
+  if let Some(long_description) = config.long_description() {
+    app = app.long_about(&**long_description);
+  }
+
+  if let Some(args) = config.args() {
+    for arg in args {
+      let arg_name = arg.name.as_ref();
+      let mut clap_arg = Arg::new(arg_name).long(arg_name);
+
+      if let Some(short) = arg.short {
+        clap_arg = clap_arg.short(short);
+      }
+
+      clap_arg = bind_string_arg!(arg, clap_arg, description, about);
+      clap_arg = bind_string_arg!(arg, clap_arg, long_description, long_about);
+      clap_arg = bind_value_arg!(arg, clap_arg, takes_value);
+      clap_arg = bind_value_arg!(arg, clap_arg, multiple);
+      clap_arg = bind_value_arg!(arg, clap_arg, multiple_occurrences);
+      clap_arg = bind_value_arg!(arg, clap_arg, number_of_values);
+      clap_arg = bind_string_slice_arg!(arg, clap_arg, possible_values);
+      clap_arg = bind_value_arg!(arg, clap_arg, min_values);
+      clap_arg = bind_value_arg!(arg, clap_arg, max_values);
+      clap_arg = bind_string_arg!(arg, clap_arg, required_unless, required_unless);
+      clap_arg = bind_value_arg!(arg, clap_arg, required);
+      clap_arg = bind_string_arg!(arg, clap_arg, required_unless, required_unless);
+      clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_all);
+      clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_one);
+      clap_arg = bind_string_arg!(arg, clap_arg, conflicts_with, conflicts_with);
+      clap_arg = bind_string_slice_arg!(arg, clap_arg, conflicts_with_all);
+      clap_arg = bind_string_arg!(arg, clap_arg, requires, requires);
+      clap_arg = bind_string_slice_arg!(arg, clap_arg, requires_all);
+      clap_arg = bind_if_arg!(arg, clap_arg, requires_if);
+      clap_arg = bind_if_arg!(arg, clap_arg, required_if);
+      clap_arg = bind_value_arg!(arg, clap_arg, require_equals);
+
+      app = app.arg(clap_arg);
+    }
+  }
+
+  if let Some(subcommands) = config.subcommands() {
+    for (subcommand_name, subcommand) in subcommands {
+      let clap_subcommand = get_app(&subcommand_name, subcommand.description(), subcommand);
+      app = app.subcommand(clap_subcommand);
+    }
+  }
+
+  app
+}

+ 45 - 0
tauri-api/src/cli/macros.rs

@@ -0,0 +1,45 @@
+macro_rules! bind_string_arg {
+  ($arg:expr, $clap_arg:expr, $arg_name:ident, $clap_field:ident) => {{
+    let arg = $arg;
+    let mut clap_arg = $clap_arg;
+    if let Some(value) = &arg.$arg_name {
+      clap_arg = clap_arg.$clap_field(value);
+    }
+    clap_arg
+  }}
+}
+
+macro_rules! bind_value_arg {
+  ($arg:expr, $clap_arg:expr, $field:ident) => {{
+    let arg = $arg;
+    let mut clap_arg = $clap_arg;
+    if let Some(value) = arg.$field {
+      clap_arg = clap_arg.$field(value);
+    }
+    clap_arg
+  }}
+}
+
+macro_rules! bind_string_slice_arg {
+  ($arg:expr, $clap_arg:expr, $field:ident) => {{
+    let arg = $arg;
+    let mut clap_arg = $clap_arg;
+    if let Some(value) = &arg.$field {
+      let v: Vec<&str> = value.iter().map(|x| &**x).collect();
+      clap_arg = clap_arg.$field(&v);
+    }
+    clap_arg
+  }}
+}
+
+macro_rules! bind_if_arg {
+  ($arg:expr, $clap_arg:expr, $field:ident) => {{
+    let arg = $arg;
+    let mut clap_arg = $clap_arg;
+    if let Some(value) = &arg.$field {
+      let v: Vec<&str> = value.iter().map(|x| &**x).collect();
+      clap_arg = clap_arg.$field(&v[0], &v[1]);
+    }
+    clap_arg
+  }}
+}

+ 152 - 2
tauri/src/config.rs → tauri-api/src/config.rs

@@ -1,5 +1,6 @@
 use serde::Deserialize;
 
+use std::collections::HashMap;
 use std::{fs, path};
 
 #[derive(PartialEq, Deserialize, Clone, Debug)]
@@ -67,6 +68,99 @@ fn default_embedded_server() -> EmbeddedServerConfig {
   }
 }
 
+#[derive(PartialEq, Deserialize, Clone, Debug, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct CliArg {
+  pub short: Option<char>,
+  pub name: String,
+  pub description: Option<String>,
+  pub long_description: Option<String>,
+  pub takes_value: Option<bool>,
+  pub multiple: Option<bool>,
+  pub multiple_occurrences: Option<bool>,
+  pub number_of_values: Option<u64>,
+  pub possible_values: Option<Vec<String>>,
+  pub min_values: Option<u64>,
+  pub max_values: Option<u64>,
+  pub required: Option<bool>,
+  pub required_unless: Option<String>,
+  pub required_unless_all: Option<Vec<String>>,
+  pub required_unless_one: Option<Vec<String>>,
+  pub conflicts_with: Option<String>,
+  pub conflicts_with_all: Option<Vec<String>>,
+  pub requires: Option<String>,
+  pub requires_all: Option<Vec<String>>,
+  pub requires_if: Option<Vec<String>>,
+  pub required_if: Option<Vec<String>>,
+  pub require_equals: Option<bool>,
+}
+
+#[derive(PartialEq, Deserialize, Clone, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct CliSubcommand {
+  description: Option<String>,
+  long_description: Option<String>,
+  before_help: Option<String>,
+  after_help: Option<String>,
+  args: Option<Vec<CliArg>>,
+  subcommands: Option<HashMap<String, CliSubcommand>>,
+}
+
+#[derive(PartialEq, Deserialize, Clone, Debug)]
+#[serde(tag = "cli", rename_all = "camelCase")]
+pub struct CliConfig {
+  description: Option<String>,
+  long_description: Option<String>,
+  before_help: Option<String>,
+  after_help: Option<String>,
+  args: Option<Vec<CliArg>>,
+  subcommands: Option<HashMap<String, CliSubcommand>>,
+}
+
+pub trait Cli {
+  fn args(&self) -> Option<&Vec<CliArg>>;
+  fn subcommands(&self) -> Option<&HashMap<String, CliSubcommand>>;
+  fn description(&self) -> Option<&String>;
+  fn long_description(&self) -> Option<&String>;
+  fn before_help(&self) -> Option<&String>;
+  fn after_help(&self) -> Option<&String>;
+}
+
+macro_rules! impl_cli {
+  ( $($field_name:ident),+ $(,)?) => {
+    $(
+      impl Cli for $field_name {
+
+        fn args(&self) -> Option<&Vec<CliArg>> {
+          self.args.as_ref()
+        }
+
+        fn subcommands(&self) -> Option<&HashMap<String, CliSubcommand>> {
+          self.subcommands.as_ref()
+        }
+
+        fn description(&self) -> Option<&String> {
+          self.description.as_ref()
+        }
+
+        fn long_description(&self) -> Option<&String> {
+          self.description.as_ref()
+        }
+
+        fn before_help(&self) -> Option<&String> {
+          self.before_help.as_ref()
+        }
+
+        fn after_help(&self) -> Option<&String> {
+          self.after_help.as_ref()
+        }
+      }
+    )+
+  }
+}
+
+impl_cli!(CliSubcommand, CliConfig);
+
 #[derive(PartialEq, Deserialize, Clone, Debug)]
 #[serde(tag = "tauri", rename_all = "camelCase")]
 pub struct TauriConfig {
@@ -74,6 +168,8 @@ pub struct TauriConfig {
   pub window: WindowConfig,
   #[serde(default = "default_embedded_server")]
   pub embedded_server: EmbeddedServerConfig,
+  #[serde(default)]
+  pub cli: Option<CliConfig>,
 }
 
 #[derive(PartialEq, Deserialize, Clone, Debug)]
@@ -100,6 +196,7 @@ fn default_tauri() -> TauriConfig {
   TauriConfig {
     window: default_window(),
     embedded_server: default_embedded_server(),
+    cli: None,
   }
 }
 
@@ -127,22 +224,74 @@ mod test {
   use super::*;
   // generate a test_config based on the test fixture
   fn create_test_config() -> Config {
+    let mut subcommands = std::collections::HashMap::new();
+    subcommands.insert(
+      "update".to_string(),
+      CliSubcommand {
+        description: Some("Updates the app".to_string()),
+        long_description: None,
+        before_help: None,
+        after_help: None,
+        args: Some(vec![CliArg {
+          short: Some('b'),
+          name: "background".to_string(),
+          description: Some("Update in background".to_string()),
+          ..Default::default()
+        }]),
+        subcommands: None,
+      },
+    );
     Config {
       tauri: TauriConfig {
         window: WindowConfig {
           width: 800,
           height: 600,
           resizable: true,
-          title: String::from("Tauri App"),
+          title: String::from("Tauri API Validation"),
           fullscreen: false,
         },
         embedded_server: EmbeddedServerConfig {
           host: String::from("http://127.0.0.1"),
           port: String::from("random"),
         },
+        cli: Some(CliConfig {
+          description: Some("Tauri communication example".to_string()),
+          long_description: None,
+          before_help: None,
+          after_help: None,
+          args: Some(vec![
+            CliArg {
+              short: Some('c'),
+              name: "config".to_string(),
+              takes_value: Some(true),
+              description: Some("Config path".to_string()),
+              ..Default::default()
+            },
+            CliArg {
+              short: Some('t'),
+              name: "theme".to_string(),
+              takes_value: Some(true),
+              description: Some("App theme".to_string()),
+              possible_values: Some(vec![
+                "light".to_string(),
+                "dark".to_string(),
+                "system".to_string(),
+              ]),
+              ..Default::default()
+            },
+            CliArg {
+              short: Some('v'),
+              name: "verbose".to_string(),
+              multiple_occurrences: Some(true),
+              description: Some("Verbosity level".to_string()),
+              ..Default::default()
+            },
+          ]),
+          subcommands: Some(subcommands),
+        }),
       },
       build: BuildConfig {
-        dev_path: String::from("http://localhost:4000"),
+        dev_path: String::from("../dist"),
       },
     }
   }
@@ -196,6 +345,7 @@ mod test {
         host: String::from("http://127.0.0.1"),
         port: String::from("random"),
       },
+      cli: None,
     };
 
     // create a build config

+ 7 - 0
tauri-api/src/lib.rs

@@ -4,6 +4,7 @@
 )]
 
 pub mod command;
+pub mod config;
 pub mod dialog;
 pub mod dir;
 pub mod file;
@@ -13,6 +14,12 @@ pub mod rpc;
 pub mod tcp;
 pub mod version;
 
+#[cfg(feature = "cli")]
+pub mod cli;
+#[cfg(feature = "cli")]
+#[macro_use]
+extern crate clap;
+
 pub use tauri_utils::*;
 
 pub use anyhow::Result;

+ 1 - 1
tauri/Cargo.toml

@@ -39,6 +39,7 @@ tauri = {path = ".", features = [ "all-api", "edge" ]}
 serde = { version = "1.0", features = [ "derive" ] }
 
 [features]
+cli = ["tauri-api/cli"]
 edge = ["web-view/edge"]
 embedded-server = ["tiny_http"]
 no-server = []
@@ -66,4 +67,3 @@ features = ["all-api"]
 [[example]]
 name = "communication"
 path = "examples/communication/src-tauri/src/main.rs"
-

+ 5 - 0
tauri/examples/communication/dist/cli.js

@@ -0,0 +1,5 @@
+document.getElementById('cli-matches').addEventListener('click', function () {
+  window.tauri.cliMatches()
+    .then(registerResponse)
+    .catch(registerResponse)
+})

+ 293 - 260
tauri/examples/communication/dist/index.html

@@ -1,279 +1,312 @@
 <!DOCTYPE html>
 <html>
-  <head>
-    <style>
-      * {
-        font-family:Arial, Helvetica, sans-serif;
-      }
-      body {
-        background: #889;
-      }
-      .logo-container {
-        width: 95%;
-        margin: 0px auto;
-        overflow: hidden;
-      }
-
-      .logo-link {
-        font-weight: 700;
-        position: absolute;
-        top:150px;
-        right: 10px;
-      }
-
-      .logo {
-        width: 32px;
-        height: 32px;
-        cursor: pointer;
-        position: fixed;
-        z-index: 10;
-        top:7px;
-        right: 10px;
-      }
-      #response {
-        position: absolute;
-        left:10px;
-        right:10px;
-        top:440px;
-        min-height:110px;
-        background: #aab;
-        font-family: 'Courier New', Courier, monospace;
-        font-size: 12px;
-        word-wrap: break-word;
-        padding: 5px;
-        border-radius:5px;
-        overflow-y:auto;
-      }
-
-      input, select {
-        background: white;
-        font-family: system-ui, sans-serif;
-        border: 0;
-        border-radius: 0.25rem;
-        font-size: 1rem;
-        line-height: 1.2;
-        padding: 0.25rem 0.5rem;
-        margin: 0.25rem;
-      }
-
-      button:hover,
-      button:focus {
-          background: #0053ba;
-      }
-
-      button:focus {
-          outline: 1px solid #fff;
-          outline-offset: -4px;
-      }
-
-      button:active {
-          transform: scale(0.99);
-      }
-      .button {
-        border: 0;
-        border-radius: 0.25rem;
-        background: #1E88E5;
-        color: white;
-        font-family: system-ui, sans-serif;
-        font-size: 1rem;
-        line-height: 1.2;
-        white-space: nowrap;
-        text-decoration: none;
-        padding: 0.25rem 0.5rem;
-        margin: 0.25rem;
-        cursor: pointer;
-      }
-      .bottom {
-        position:fixed;
-        bottom:0;
-        left:0;
-        text-align: center;
-        width: 100%;
-        padding: 5px;
-        background: #333;
-        color: #eef;
-      }
-      .dark-link {
-        color: white;
-        text-decoration: none!important;
-      }
-
-      .tabs-container {
-        position:fixed;
-        height: 400px;
-        top:20px;
-        left:10px;
-        right:10px;
-        z-index: 9;
-      }
-      .tabs {
-        position: relative;
-        min-height: 400px;
-        clear: both;
-      }
-      .tab {
-        float: left;
-      }
-      .tab > label {
-        background: #eee;
-        padding: 10px;
-        border: 1px solid transparent;
-        margin-left: -1px;
-        position: relative;
-        left: 1px;
-      }
-      .tabs > .tabber {
-        border-top-left-radius: 5px;
-      }
-      .tabs > .tabber ~ .tabber {
-        border-top-left-radius: none;
-      }
-      .tab [type=radio] {
-        display: none;
-      }
-      .content {
-        position: absolute;
-        top: 28px;
-        left: 0;
-        background: #bbc;
-        right: 0;
-        bottom: 0;
-        padding: 20px;
-        border: 1px solid transparent;
-        border-top-right-radius: 5px;
-        border-bottom-left-radius: 5px;
-        border-bottom-right-radius: 5px;
-      }
-      [type=radio]:checked ~ label {
-        background: #bbc;
-        border-bottom: 1px solid transparent;
-        z-index: 2;
-      }
-      [type=radio]:checked ~ label ~ .content {
-        z-index: 1;
-      }
-    </style>
-  </head>
-  <body>
-    <div class="logo-container">
-      <img src="icon.png" class="logo">
-    </div>
 
-    <div class="tabs-container">
-      <div class="tabs">
-        <div class="tab">
-          <input type="radio" id="tab-1" name="tab-group-1" checked>
-          <label class="tabber" for="tab-1">Messages</label>
-          <div class="content">
-            <button class="button" id="log">Call Log API</button>
-            <button class="button" id="request">Call Request (async) API</button>
-            <button class="button" id="event">Send event to Rust</button>
-
-            <div style="margin-top: 24px">
-              <input id="title" value="Awesome Tauri Example!">
-              <button class="button" id="set-title">Set title</button>
-            </div>
+<head>
+  <style>
+    * {
+      font-family: Arial, Helvetica, sans-serif;
+    }
+
+    body {
+      background: #889;
+    }
+
+    .logo-container {
+      width: 95%;
+      margin: 0px auto;
+      overflow: hidden;
+    }
+
+    .logo-link {
+      font-weight: 700;
+      position: absolute;
+      top: 150px;
+      right: 10px;
+    }
+
+    .logo {
+      width: 32px;
+      height: 32px;
+      cursor: pointer;
+      position: fixed;
+      z-index: 10;
+      top: 7px;
+      right: 10px;
+    }
+
+    #response {
+      position: absolute;
+      left: 10px;
+      right: 10px;
+      top: 440px;
+      min-height: 110px;
+      background: #aab;
+      font-family: 'Courier New', Courier, monospace;
+      font-size: 12px;
+      word-wrap: break-word;
+      padding: 5px;
+      border-radius: 5px;
+      overflow-y: auto;
+    }
+
+    input,
+    select {
+      background: white;
+      font-family: system-ui, sans-serif;
+      border: 0;
+      border-radius: 0.25rem;
+      font-size: 1rem;
+      line-height: 1.2;
+      padding: 0.25rem 0.5rem;
+      margin: 0.25rem;
+    }
+
+    button:hover,
+    button:focus {
+      background: #0053ba;
+    }
+
+    button:focus {
+      outline: 1px solid #fff;
+      outline-offset: -4px;
+    }
+
+    button:active {
+      transform: scale(0.99);
+    }
+
+    .button {
+      border: 0;
+      border-radius: 0.25rem;
+      background: #1E88E5;
+      color: white;
+      font-family: system-ui, sans-serif;
+      font-size: 1rem;
+      line-height: 1.2;
+      white-space: nowrap;
+      text-decoration: none;
+      padding: 0.25rem 0.5rem;
+      margin: 0.25rem;
+      cursor: pointer;
+    }
+
+    .bottom {
+      position: fixed;
+      bottom: 0;
+      left: 0;
+      text-align: center;
+      width: 100%;
+      padding: 5px;
+      background: #333;
+      color: #eef;
+    }
+
+    .dark-link {
+      color: white;
+      text-decoration: none !important;
+    }
+
+    .tabs-container {
+      position: fixed;
+      height: 400px;
+      top: 20px;
+      left: 10px;
+      right: 10px;
+      z-index: 9;
+    }
+
+    .tabs {
+      position: relative;
+      min-height: 400px;
+      clear: both;
+    }
+
+    .tab {
+      float: left;
+    }
+
+    .tab>label {
+      background: #eee;
+      padding: 10px;
+      border: 1px solid transparent;
+      margin-left: -1px;
+      position: relative;
+      left: 1px;
+    }
+
+    .tabs>.tabber {
+      border-top-left-radius: 5px;
+    }
+
+    .tabs>.tabber~.tabber {
+      border-top-left-radius: none;
+    }
+
+    .tab [type=radio] {
+      display: none;
+    }
+
+    .content {
+      position: absolute;
+      top: 28px;
+      left: 0;
+      background: #bbc;
+      right: 0;
+      bottom: 0;
+      padding: 20px;
+      border: 1px solid transparent;
+      border-top-right-radius: 5px;
+      border-bottom-left-radius: 5px;
+      border-bottom-right-radius: 5px;
+    }
+
+    [type=radio]:checked~label {
+      background: #bbc;
+      border-bottom: 1px solid transparent;
+      z-index: 2;
+    }
+
+    [type=radio]:checked~label~.content {
+      z-index: 1;
+    }
+
+  </style>
+</head>
+
+<body>
+  <div class="logo-container">
+    <img src="icon.png" class="logo">
+  </div>
+
+  <div class="tabs-container">
+    <div class="tabs">
+      <div class="tab">
+        <input type="radio" id="tab-1" name="tab-group-1" checked>
+        <label class="tabber" for="tab-1">Messages</label>
+        <div class="content">
+          <button class="button" id="log">Call Log API</button>
+          <button class="button" id="request">Call Request (async) API</button>
+          <button class="button" id="event">Send event to Rust</button>
+
+          <div style="margin-top: 24px">
+            <input id="title" value="Awesome Tauri Example!">
+            <button class="button" id="set-title">Set title</button>
           </div>
         </div>
-        <div class="tab">
-          <input type="radio" id="tab-2" name="tab-group-1">
-          <label class="tabber" for="tab-2">File System</label>
-          <div class="content">
-            <div style="margin-top: 24px">
-              <select class="button"  id="dir">
-                <option value="">None</option>
-              </select>
-              <input id="path-to-read" placeholder="Type the path to read...">
-              <button class="button" id="read">Read</button>
+      </div>
+      <div class="tab">
+        <input type="radio" id="tab-2" name="tab-group-1">
+        <label class="tabber" for="tab-2">File System</label>
+        <div class="content">
+          <div style="margin-top: 24px">
+            <select class="button" id="dir">
+              <option value="">None</option>
+            </select>
+            <input id="path-to-read" placeholder="Type the path to read...">
+            <button class="button" id="read">Read</button>
+          </div>
+          <div style="margin-top: 24px">
+            <input id="dialog-default-path" placeholder="Default path">
+            <input id="dialog-filter" placeholder="Extensions filter">
+            <div>
+              <input type="checkbox" id="dialog-multiple">
+              <label>Multiple</label>
             </div>
-            <div style="margin-top: 24px">
-              <input id="dialog-default-path" placeholder="Default path">
-              <input id="dialog-filter" placeholder="Extensions filter">
-              <div>
-                <input type="checkbox" id="dialog-multiple">
-                <label>Multiple</label>
-              </div>
-              <div>
-                <input type="checkbox" id="dialog-directory">
-                <label>Directory</label>
-              </div>
-
-              <button class="button" id="open-dialog">Open dialog</button>
-              <button class="button" id="save-dialog">Open save dialog</button>
+            <div>
+              <input type="checkbox" id="dialog-directory">
+              <label>Directory</label>
             </div>
+
+            <button class="button" id="open-dialog">Open dialog</button>
+            <button class="button" id="save-dialog">Open save dialog</button>
           </div>
         </div>
+      </div>
 
-        <div class="tab">
-          <input type="radio" id="tab-3" name="tab-group-1">
-          <label class="tabber" for="tab-3">Communication</label>
-          <div class="content">
-            <div style="margin-top: 24px">
-              <input id="url" value="https://tauri.studio">
-              <button class="button" id="open-url">Open URL</button>
-            </div>
+      <div class="tab">
+        <input type="radio" id="tab-3" name="tab-group-1">
+        <label class="tabber" for="tab-3">Communication</label>
+        <div class="content">
+          <div style="margin-top: 24px">
+            <input id="url" value="https://tauri.studio">
+            <button class="button" id="open-url">Open URL</button>
+          </div>
 
-            <div style="margin-top: 24px">
-              <select class="button"  id="request-method">
-                <option value="GET">GET</option>
-                <option value="POST">POST</option>
-                <option value="PUT">PUT</option>
-                <option value="PATCH">PATCH</option>
-                <option value="DELETE">DELETE</option>
-              </select>
-              <input id="request-url" placeholder="Type the request URL...">
-              <br/>
-              <textarea id="request-body" placeholder="Request body" rows="5"  style="width:100%;margin-right:10px;font-size:12px"></textarea>
-              </br>
-              <button class="button"  id="make-request">Make request</button>
-            </div>
+          <div style="margin-top: 24px">
+            <select class="button" id="request-method">
+              <option value="GET">GET</option>
+              <option value="POST">POST</option>
+              <option value="PUT">PUT</option>
+              <option value="PATCH">PATCH</option>
+              <option value="DELETE">DELETE</option>
+            </select>
+            <input id="request-url" placeholder="Type the request URL...">
+            <br />
+            <textarea id="request-body" placeholder="Request body" rows="5"
+              style="width:100%;margin-right:10px;font-size:12px"></textarea>
+            </br>
+            <button class="button" id="make-request">Make request</button>
+          </div>
+        </div>
+      </div>
+      <div class="tab">
+        <input type="radio" id="tab-4" name="tab-group-1">
+        <label class="tabber" for="tab-4">CLI</label>
+        <div class="content">
+          <div style="margin-top: 24px">
+            <button class="button" id="cli-matches">Get matches</button>
           </div>
         </div>
       </div>
     </div>
-    <div id="response"></div>
-    <div class="bottom">
-      <a class="dark-link" target="_blank"  href="https://tauri.studio">Tauri Documentation</a>&nbsp;&nbsp;&nbsp;
-      <a class="dark-link" target="_blank" href="https://github.com/tauri-apps/tauri">Github Repo</a>&nbsp;&nbsp;&nbsp;
-      <a class="dark-link" target="_blank" href="https://github.com/tauri-apps/tauri/tree/dev/tauri/examples/communication">Source for this App</a>
-    </div>
-    <script>
-      function registerResponse (response) {
-        document.getElementById('response').innerHTML = typeof response === 'object'
-          ? JSON.stringify(response)
-          : response
-      }
-
-      function addClickEnterHandler (button, input, handler) {
-        button.addEventListener('click', handler)
-        input.addEventListener('keyup', function (e) {
-          if (e.keyCode === 13) {
-            handler()
-          }
-        })
-      }
-
-      window.tauri.listen('rust-event', function (res) {
-        document.getElementById('response').innerHTML = JSON.stringify(res)
-      })
+  </div>
+  <div id="response"></div>
+  <div class="bottom">
+    <a class="dark-link" target="_blank" href="https://tauri.studio">Tauri Documentation</a>&nbsp;&nbsp;&nbsp;
+    <a class="dark-link" target="_blank" href="https://github.com/tauri-apps/tauri">Github Repo</a>&nbsp;&nbsp;&nbsp;
+    <a class="dark-link" target="_blank"
+      href="https://github.com/tauri-apps/tauri/tree/dev/tauri/examples/communication">Source for this App</a>
+  </div>
+  <script>
+    function registerResponse(response) {
+      document.getElementById('response').innerHTML = typeof response === 'object' ?
+        JSON.stringify(response) :
+        response
+    }
 
-      document.querySelector('.logo').addEventListener('click', function () {
-        window.tauri.open('https://tauri.studio/')
+    function addClickEnterHandler(button, input, handler) {
+      button.addEventListener('click', handler)
+      input.addEventListener('keyup', function (e) {
+        if (e.keyCode === 13) {
+          handler()
+        }
       })
+    }
+
+    window.tauri.listen('rust-event', function (res) {
+      document.getElementById('response').innerHTML = JSON.stringify(res)
+    })
+
+    document.querySelector('.logo').addEventListener('click', function () {
+      window.tauri.open('https://tauri.studio/')
+    })
+
+    var dirSelect = document.getElementById('dir')
+    for (var key in window.tauri.Dir) {
+      var value = window.tauri.Dir[key]
+      var opt = document.createElement("option")
+      opt.value = value
+      opt.innerHTML = key
+      dirSelect.appendChild(opt)
+    }
+
+  </script>
+  <script src="communication.js"></script>
+  <script src="fs.js"></script>
+  <script src="window.js"></script>
+  <script src="dialog.js"></script>
+  <script src="http.js"></script>
+  <script src="cli.js"></script>
+</body>
 
-      var dirSelect = document.getElementById('dir')
-      for (var key in window.tauri.Dir) {
-        var value = window.tauri.Dir[key]
-        var opt = document.createElement("option")
-        opt.value = value
-        opt.innerHTML = key
-        dirSelect.appendChild(opt)
-      }
-    </script>
-    <script src="communication.js"></script>
-    <script src="fs.js"></script>
-    <script src="window.js"></script>
-    <script src="dialog.js"></script>
-    <script src="http.js"></script>
-  </body>
 </html>

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 9 - 0
tauri/examples/communication/dist/index.tauri.html


+ 1 - 1
tauri/examples/communication/src-tauri/Cargo.toml

@@ -24,7 +24,7 @@ icon = [
 [dependencies]
 serde_json = "1.0"
 serde = { version = "1.0", features = [ "derive" ] }
-tauri = { path = "../../..", features = [ "all-api", "edge" ] }
+tauri = { path = "../../..", features = [ "all-api", "edge", "cli" ] }
 
 [target."cfg(windows)".build-dependencies]
 winres = "0.1"

+ 38 - 1
tauri/examples/communication/src-tauri/tauri.conf.json

@@ -5,6 +5,43 @@
   },
   "ctx": {},
   "tauri": {
+    "cli": {
+      "description": "Tauri communication example",
+      "longDescription": null,
+      "beforeHelp": null,
+      "afterHelp": null,
+      "args": [{
+        "short": "c",
+        "name": "config",
+        "takesValue": true,
+        "description": "Config path"
+      }, {
+        "short": "t",
+        "name": "theme",
+        "takesValue": true,
+        "description": "App theme",
+        "possibleValues": ["light", "dark", "system"]
+      }, {
+        "short": "v",
+        "name": "verbose",
+        "multipleOccurrences": true,
+        "description": "Verbosity level"
+      }],
+      "subcommands": {
+        "update": {
+          "description": "Updates the app",
+          "longDescription": null,
+          "beforeHelp": null,
+          "afterHelp": null,
+          "args": [{
+            "short": "b",
+            "name": "background",
+            "description": "Update in background"
+          }],
+          "subcommands": null
+        }
+      }
+    },
     "embeddedServer": {
       "active": false
     },
@@ -31,4 +68,4 @@
       "active": true
     }
   }
-}
+}

+ 9 - 0
tauri/examples/communication/src-tauri/tauri.js

@@ -570,6 +570,15 @@ window.tauri = {
       asset: assetName,
       assetType: assetType || 'unknown'
     })
+  },
+
+  cliMatches: function () {
+    
+      return this.promisified({
+        cmd: 'cliMatches'
+      })
+    
+    
   }
 };
 

+ 28 - 13
tauri/src/app/runner.rs

@@ -12,13 +12,22 @@ use web_view::{builder, Content, WebView};
 use super::App;
 #[cfg(feature = "embedded-server")]
 use crate::api::tcp::{get_available_port, port_is_available};
-use crate::config::{get, Config};
+use tauri_api::config::{get, Config};
+
+#[cfg(feature = "cli")]
+use tauri_api::cli::get_matches;
 
 // Main entry point function for running the Webview
 pub(crate) fn run(application: &mut App) -> crate::Result<()> {
   // get the tauri config struct
   let config = get()?;
 
+  #[cfg(feature = "cli")]
+  {
+    let matches = get_matches(config.clone());
+    crate::cli::set_matches(matches)?;
+  }
+
   // setup the content using the config struct depending on the compile target
   let main_content = setup_content(config.clone())?;
 
@@ -72,7 +81,10 @@ fn setup_content(config: Config) -> crate::Result<Content<String>> {
     let dev_dir = config.build.dev_path;
     let dev_path = Path::new(&dev_dir).join("index.tauri.html");
     if !dev_path.exists() {
-      panic!("Couldn't find 'index.tauri.html' inside {}; did you forget to run 'tauri dev'?", dev_dir);
+      panic!(
+        "Couldn't find 'index.tauri.html' inside {}; did you forget to run 'tauri dev'?",
+        dev_dir
+      );
     }
     Ok(Content::Html(read_to_string(dev_path)?))
   }
@@ -280,8 +292,8 @@ mod test {
   #[cfg(not(feature = "embedded-server"))]
   use std::{env, fs::read_to_string, path::Path};
 
-  fn init_config() -> crate::config::Config {
-    crate::config::get().expect("unable to setup default config")
+  fn init_config() -> tauri_api::config::Config {
+    tauri_api::config::get().expect("unable to setup default config")
   }
 
   #[test]
@@ -289,6 +301,15 @@ mod test {
     let config = init_config();
     let _c = config.clone();
 
+    let tauri_dir = match option_env!("TAURI_DIR") {
+      Some(d) => d.to_string(),
+      None => env::current_dir()
+        .unwrap()
+        .into_os_string()
+        .into_string()
+        .expect("Unable to convert to normal String"),
+    };
+    env::set_current_dir(tauri_dir).expect("failed to change cwd");
     let res = super::setup_content(config);
 
     #[cfg(feature = "embedded-server")]
@@ -320,17 +341,11 @@ mod test {
     match res {
       Ok(Content::Url(dp)) => assert_eq!(dp, _c.build.dev_path),
       Ok(Content::Html(s)) => {
-        let dist_dir = match option_env!("TAURI_DIST_DIR") {
-          Some(d) => d.to_string(),
-          None => env::current_dir()
-            .unwrap()
-            .into_os_string()
-            .into_string()
-            .expect("Unable to convert to normal String"),
-        };
+        let dev_dir = _c.build.dev_path;
+        let dev_path = Path::new(&dev_dir).join("index.tauri.html");
         assert_eq!(
           s,
-          read_to_string(Path::new(&dist_dir).join("index.tauri.html")).unwrap()
+          read_to_string(dev_path).expect("failed to read dev path")
         );
       }
       _ => assert!(false),

+ 14 - 0
tauri/src/cli.rs

@@ -0,0 +1,14 @@
+use once_cell::sync::OnceCell;
+use tauri_api::cli::Matches;
+
+static MATCHES: OnceCell<Matches> = OnceCell::new();
+
+pub(crate) fn set_matches(matches: Matches) -> crate::Result<()> {
+  MATCHES
+    .set(matches)
+    .map_err(|_| anyhow::anyhow!("failed to set once_cell matches"))
+}
+
+pub fn get_matches() -> Option<&'static Matches> {
+  MATCHES.get()
+}

+ 10 - 0
tauri/src/endpoints.rs

@@ -178,6 +178,16 @@ pub(crate) fn handle<T: 'static>(webview: &mut WebView<'_, T>, arg: &str) -> cra
         } => {
           load_asset(webview, asset, asset_type, callback, error)?;
         }
+        #[cfg(feature = "cli")]
+        CliMatches { callback, error } => crate::execute_promise(
+          webview,
+          move || match crate::cli::get_matches() {
+            Some(matches) => Ok(serde_json::to_string(matches)?),
+            None => Err(anyhow::anyhow!(r#""failed to get matches""#)),
+          },
+          callback,
+          error,
+        ),
       }
       Ok(())
     }

+ 5 - 0
tauri/src/endpoints/cmd.rs

@@ -160,4 +160,9 @@ pub enum Cmd {
     callback: String,
     error: String,
   },
+  #[cfg(feature = "cli")]
+  CliMatches {
+    callback: String,
+    error: String,
+  },
 }

+ 1 - 4
tauri/src/endpoints/dialog.rs

@@ -41,10 +41,7 @@ pub fn save<T: 'static>(
 ) {
   crate::execute_promise_sync(
     webview,
-    move || {
-      save_file(options.filter, options.default_path)
-        .map(map_response)
-    },
+    move || save_file(options.filter, options.default_path).map(map_response),
     callback,
     error,
   );

+ 4 - 4
tauri/src/endpoints/http.rs

@@ -12,12 +12,12 @@ pub fn make_request<T: 'static>(
     webview,
     move || {
       let response_type = options.response_type.clone();
-      request(options).map(|response| {
-        match response_type.unwrap_or(ResponseType::Json) {
+      request(options).map(
+        |response| match response_type.unwrap_or(ResponseType::Json) {
           ResponseType::Text => format!(r#""{}""#, response),
           _ => response,
-        }
-      })
+        },
+      )
     },
     callback,
     error,

+ 4 - 6
tauri/src/event.rs

@@ -3,8 +3,8 @@ use std::collections::HashMap;
 use std::sync::{Arc, Mutex};
 
 use lazy_static::lazy_static;
-use web_view::Handle;
 use once_cell::sync::Lazy;
+use web_view::Handle;
 
 struct EventHandler {
   on_event: Box<dyn FnMut(Option<String>) + Send>,
@@ -74,10 +74,8 @@ pub fn on_event(event: String, data: Option<String>) {
     .lock()
     .expect("Failed to lock listeners: on_event()");
 
-  let key = event.clone();
-
-  if l.contains_key(&key) {
-    let handler = l.get_mut(&key).expect("Failed to get mutable handler");
+  if l.contains_key(&event) {
+    let handler = l.get_mut(&event).expect("Failed to get mutable handler");
     (handler.on_event)(data);
   }
 }
@@ -151,4 +149,4 @@ mod test {
       assert!(l.contains_key(&key));
     }
   }
-}
+}

+ 3 - 1
tauri/src/lib.rs

@@ -5,11 +5,13 @@
 
 #[cfg(any(feature = "embedded-server", feature = "no-server"))]
 pub mod assets;
-pub mod config;
 pub mod event;
 #[cfg(feature = "embedded-server")]
 pub mod server;
 
+#[cfg(feature = "cli")]
+pub mod cli;
+
 mod app;
 mod endpoints;
 #[allow(dead_code)]

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно