help.rs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. use anyhow::{anyhow, bail, Context, Result};
  2. use nanoid::nanoid;
  3. use serde::{de::DeserializeOwned, Serialize};
  4. use serde_yaml::{Mapping, Value};
  5. use std::{fs, path::PathBuf, str::FromStr};
  6. /// read data from yaml as struct T
  7. pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
  8. if !path.exists() {
  9. bail!("file not found \"{}\"", path.display());
  10. }
  11. let yaml_str = fs::read_to_string(&path)
  12. .with_context(|| format!("failed to read the file \"{}\"", path.display()))?;
  13. serde_yaml::from_str::<T>(&yaml_str).with_context(|| {
  14. format!(
  15. "failed to read the file with yaml format \"{}\"",
  16. path.display()
  17. )
  18. })
  19. }
  20. /// read mapping from yaml fix #165
  21. pub fn read_merge_mapping(path: &PathBuf) -> Result<Mapping> {
  22. let mut val: Value = read_yaml(path)?;
  23. val.apply_merge()
  24. .with_context(|| format!("failed to apply merge \"{}\"", path.display()))?;
  25. Ok(val
  26. .as_mapping()
  27. .ok_or(anyhow!(
  28. "failed to transform to yaml mapping \"{}\"",
  29. path.display()
  30. ))?
  31. .to_owned())
  32. }
  33. /// save the data to the file
  34. /// can set `prefix` string to add some comments
  35. pub fn save_yaml<T: Serialize>(path: &PathBuf, data: &T, prefix: Option<&str>) -> Result<()> {
  36. let data_str = serde_yaml::to_string(data)?;
  37. let yaml_str = match prefix {
  38. Some(prefix) => format!("{prefix}\n\n{data_str}"),
  39. None => data_str,
  40. };
  41. let path_str = path.as_os_str().to_string_lossy().to_string();
  42. fs::write(path, yaml_str.as_bytes())
  43. .with_context(|| format!("failed to save file \"{path_str}\""))
  44. }
  45. const ALPHABET: [char; 62] = [
  46. '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
  47. 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
  48. 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
  49. 'V', 'W', 'X', 'Y', 'Z',
  50. ];
  51. /// generate the uid
  52. pub fn get_uid(prefix: &str) -> String {
  53. let id = nanoid!(11, &ALPHABET);
  54. format!("{prefix}{id}")
  55. }
  56. /// parse the string
  57. /// xxx=123123; => 123123
  58. pub fn parse_str<T: FromStr>(target: &str, key: &str) -> Option<T> {
  59. target.find(key).and_then(|idx| {
  60. let idx = idx + key.len();
  61. let value = &target[idx..];
  62. match value.split(';').nth(0) {
  63. Some(value) => value.trim().parse(),
  64. None => value.trim().parse(),
  65. }
  66. .ok()
  67. })
  68. }
  69. /// open file
  70. /// use vscode by default
  71. pub fn open_file(path: PathBuf) -> Result<()> {
  72. #[cfg(target_os = "macos")]
  73. let code = "Visual Studio Code";
  74. #[cfg(not(target_os = "macos"))]
  75. let code = "code";
  76. // use vscode first
  77. if let Err(err) = open::with(&path, code) {
  78. log::error!(target: "app", "failed to open file with VScode `{err}`");
  79. // default open
  80. open::that(path)?;
  81. }
  82. Ok(())
  83. }
  84. #[macro_export]
  85. macro_rules! error {
  86. ($result: expr) => {
  87. log::error!(target: "app", "{}", $result);
  88. };
  89. }
  90. #[macro_export]
  91. macro_rules! log_err {
  92. ($result: expr) => {
  93. if let Err(err) = $result {
  94. log::error!(target: "app", "{err}");
  95. }
  96. };
  97. ($result: expr, $err_str: expr) => {
  98. if let Err(_) = $result {
  99. log::error!(target: "app", "{}", $err_str);
  100. }
  101. };
  102. }
  103. #[macro_export]
  104. macro_rules! trace_err {
  105. ($result: expr, $err_str: expr) => {
  106. if let Err(err) = $result {
  107. log::trace!(target: "app", "{}, err {}", $err_str, err);
  108. }
  109. }
  110. }
  111. /// wrap the anyhow error
  112. /// transform the error to String
  113. #[macro_export]
  114. macro_rules! wrap_err {
  115. ($stat: expr) => {
  116. match $stat {
  117. Ok(a) => Ok(a),
  118. Err(err) => {
  119. log::error!(target: "app", "{}", err.to_string());
  120. Err(format!("{}", err.to_string()))
  121. }
  122. }
  123. };
  124. }
  125. /// return the string literal error
  126. #[macro_export]
  127. macro_rules! ret_err {
  128. ($str: expr) => {
  129. return Err($str.into())
  130. };
  131. }
  132. #[test]
  133. fn test_parse_value() {
  134. let test_1 = "upload=111; download=2222; total=3333; expire=444";
  135. let test_2 = "attachment; filename=Clash.yaml";
  136. assert_eq!(parse_str::<usize>(test_1, "upload=").unwrap(), 111);
  137. assert_eq!(parse_str::<usize>(test_1, "download=").unwrap(), 2222);
  138. assert_eq!(parse_str::<usize>(test_1, "total=").unwrap(), 3333);
  139. assert_eq!(parse_str::<usize>(test_1, "expire=").unwrap(), 444);
  140. assert_eq!(
  141. parse_str::<String>(test_2, "filename=").unwrap(),
  142. format!("Clash.yaml")
  143. );
  144. assert_eq!(parse_str::<usize>(test_1, "aaa="), None);
  145. assert_eq!(parse_str::<usize>(test_1, "upload1="), None);
  146. assert_eq!(parse_str::<usize>(test_1, "expire1="), None);
  147. assert_eq!(parse_str::<usize>(test_2, "attachment="), None);
  148. }