package_json/
manager.rs

1use crate::fs;
2use crate::fs::write_options::WriteOptions;
3use crate::PackageJson;
4use anyhow::{format_err, Result};
5use std::env;
6use std::path::{Path, PathBuf};
7
8pub const PACKAGE_JSON_FILENAME: &str = "package.json";
9
10/// A manager for manipulating `package.json` file.
11#[derive(Debug)]
12pub struct PackageJsonManager {
13  file_path: Option<PathBuf>,
14  json: PackageJson,
15  write_options: Option<WriteOptions>,
16}
17
18impl Default for PackageJsonManager {
19  fn default() -> Self {
20    Self {
21      file_path: None,
22      json: Default::default(),
23      write_options: Some(WriteOptions::default()),
24    }
25  }
26}
27
28impl PackageJsonManager {
29  /// Constructs a new, empty `PackageJsonManager`.
30  pub fn new() -> Self {
31    Default::default()
32  }
33
34  /// Constructs a new, empty `PackageJsonManger` with the specified `package.json` file path.
35  /// ```
36  /// use package_json::PackageJsonManager;
37  /// let mut manager = PackageJsonManager::with_file_path("/path/to/package.json");
38  /// ```
39  pub fn with_file_path<FilePath>(path: FilePath) -> Self
40  where
41    FilePath: AsRef<Path>,
42  {
43    Self {
44      file_path: Some(path.as_ref().to_path_buf()),
45      ..Default::default()
46    }
47  }
48
49  /// Construct a new, empty `PackageJsonManager` with the specified `WriteOptions`.
50  /// ```
51  /// use package_json::{PackageJsonManager, WriteOptions, WriteOptionsBuilder};
52  /// let mut manager = PackageJsonManager::with_write_options(WriteOptions::default());
53  ///
54  /// let mut manager = PackageJsonManager::with_write_options(
55  ///   WriteOptionsBuilder::default().pretty(false).build().unwrap()
56  /// );
57  /// ```
58  pub fn with_write_options(options: WriteOptions) -> Self {
59    Self {
60      write_options: Some(options),
61      ..Default::default()
62    }
63  }
64
65  /// Try to locate the closest `package.json` file from [current working directory][std::env::current_dir] to sys root.
66  pub fn locate_closest(&mut self) -> Result<PathBuf> {
67    env::current_dir().map(|cwd| self.locate_closest_from(cwd))?
68  }
69
70  /// Try to locate the closest `package.json` file from specific directory to sys root.
71  pub fn locate_closest_from<P: AsRef<Path>>(&mut self, from: P) -> Result<PathBuf> {
72    fs::find_closest_file(PACKAGE_JSON_FILENAME, from).map(|file_path| {
73      self.file_path = Some(file_path);
74      self.file_path.as_ref().unwrap().to_owned()
75    })
76  }
77
78  /// Specify the `package.json` file path which is used to read and write.
79  pub fn set_file_path<FilePath: AsRef<Path>>(&mut self, file_path: FilePath) {
80    self.file_path = Some(file_path.as_ref().to_path_buf());
81  }
82
83  /// Get the located file path after `locate_closest` or `locate_closest_from` evaluated.
84  pub fn get_file_path(&mut self) -> Option<&Path> {
85    self.file_path.as_deref()
86  }
87
88  /// Take the located file path out of `PackageJsonManager`, leaving a `None` in its place.
89  pub fn take_file_path(&mut self) -> Option<PathBuf> {
90    self.file_path.take()
91  }
92
93  /// Call file reader to read `package.json` file.
94  fn read(&mut self) -> Result<()> {
95    self
96      .file_path
97      .as_ref()
98      .map(|file_path| {
99        fs::read_json(file_path).map(|json| {
100          self.json = json;
101        })
102      })
103      .unwrap_or_else(|| {
104        Err(format_err!(
105          "Couldn't find an available {} file.",
106          PACKAGE_JSON_FILENAME
107        ))
108      })
109  }
110
111  ///
112  /// Evaluate `package.json` parser and return the immutable `PackageJson` reference.
113  ///
114  /// Note: It always reads the file again. In the most case, you should call `as_ref` to get a immutable reference if you have read it before.
115  /// ```
116  /// use package_json::PackageJsonManager;
117  /// let mut manager = PackageJsonManager::new();
118  /// if manager.locate_closest().is_ok() {
119  ///   assert!(manager.read_ref().is_ok());
120  /// }
121  /// ```
122  pub fn read_ref(&mut self) -> Result<&PackageJson> {
123    self.read().map(|_| &self.json)
124  }
125
126  /// Evaluate `package.json` parser and return the mutable `PackageJson` reference.
127  ///
128  /// Note: It always reads the file again. In the most case, you should call `as_mut` to get a mutable reference if you have read it before.
129  /// ```
130  /// use package_json::PackageJsonManager;
131  /// let mut manager = PackageJsonManager::new();
132  /// if manager.locate_closest().is_ok() {
133  ///   assert!(manager.read_mut().is_ok());
134  /// }
135  /// ```
136  pub fn read_mut(&mut self) -> Result<&mut PackageJson> {
137    self.read().map(|_| &mut self.json)
138  }
139
140  /// Use the current `package.json` content to write the target `package.json` file.
141  /// ```
142  /// use package_json::PackageJsonManager;
143  /// let mut manager = PackageJsonManager::new();
144  /// if manager.locate_closest().is_ok() {
145  ///   if let Ok(mut json) = manager.read_mut() {
146  ///     json.name = "new name".to_string();
147  ///     json.version = "1.0.0".to_string();
148  ///   }
149  ///   manager.write().expect("Couldn't write package.json");
150  /// }
151  /// ```
152  pub fn write(&mut self) -> Result<()> {
153    self
154      .file_path
155      .as_ref()
156      .map(|file_path| {
157        fs::write_json(
158          file_path,
159          &self.json,
160          self
161            .write_options
162            .as_ref()
163            .expect("self.write_options should not be None"),
164        )
165      })
166      .unwrap_or_else(|| {
167        Err(format_err!(
168          "Couldn't find an available {} file.",
169          PACKAGE_JSON_FILENAME
170        ))
171      })
172  }
173
174  /// Write the current `package.json` content to the specific `package.json` file.
175  /// ```
176  /// use package_json::PackageJsonManager;
177  /// use std::path::Path;
178  /// let mut manager = PackageJsonManager::new();
179  /// if manager.locate_closest().is_ok() {
180  ///   if let Ok(mut json) = manager.read_mut() {
181  ///     json.name = "new name".to_string();
182  ///     json.version = "1.0.0".to_string();
183  ///   }
184  ///   manager
185  ///     .write_to(&Path::new("/path/to/package.json"))
186  ///     .expect("Couldn't write package.json");
187  /// }
188  /// ```
189  pub fn write_to(&mut self, file_path: &Path) -> Result<()> {
190    fs::write_json(
191      file_path,
192      &self.json,
193      self
194        .write_options
195        .as_ref()
196        .expect("self.write_options should not be None"),
197    )
198  }
199}
200
201impl AsRef<PackageJson> for PackageJsonManager {
202  /// Return a immutable reference to the current `PackageJson` struct.
203  fn as_ref(&self) -> &PackageJson {
204    &self.json
205  }
206}
207
208impl AsMut<PackageJson> for PackageJsonManager {
209  /// Return a mutable reference to the current `PackageJson` struct.
210  fn as_mut(&mut self) -> &mut PackageJson {
211    &mut self.json
212  }
213}
214
215#[test]
216fn test_new() {
217  use std::path::PathBuf;
218
219  let mut manager = PackageJsonManager::new();
220  assert_eq!(manager.file_path, None);
221
222  let file_path = PathBuf::from("/path/to/package.json");
223  manager.set_file_path(&file_path);
224  assert_eq!(manager.file_path, Some(file_path.to_owned()));
225  assert_eq!(manager.get_file_path(), Some(file_path.as_ref()));
226  assert_eq!(manager.take_file_path(), Some(file_path.to_owned()));
227  assert_eq!(manager.file_path, None);
228}
229
230#[test]
231fn test_readable() {
232  use crate::PACKAGE_JSON_FILENAME;
233  use std::env::current_dir;
234  use std::fs::{create_dir_all, File};
235  use std::io::Write;
236  use tempfile::tempdir_in;
237
238  let mut manager = PackageJsonManager::new();
239  assert!(manager.read_ref().is_err(), "found an available file.");
240  assert!(
241    manager.get_file_path().is_none(),
242    "found an available file."
243  );
244  assert!(
245    manager.locate_closest().is_err(),
246    "found an available file."
247  );
248
249  let dir = tempdir_in(current_dir().unwrap()).expect("create temp_dir failed!");
250  let file_path = dir.path().join(format!("a/b/c/{}", PACKAGE_JSON_FILENAME));
251  let file_dir = file_path.parent().unwrap();
252  let deeper_file_dir = file_dir.join("d/e/f");
253  create_dir_all(file_dir).expect("create a/b/c dir failed!");
254  create_dir_all(deeper_file_dir.as_path()).expect("create d/e/f dir field!");
255
256  let mut valid_file = File::create(&file_path).expect("create file failed!");
257  let valid_json = r#"{
258"name": "test",
259"version": "0.0.1"
260}"#;
261
262  valid_file
263    .write_all(valid_json.as_bytes())
264    .expect("write json failed");
265
266  for (dir, expect) in [
267    (dir.path(), None),
268    (file_dir, Some(file_path.to_owned())),
269    (deeper_file_dir.as_path(), Some(file_path.to_owned())),
270  ] {
271    assert_eq!(manager.locate_closest_from(dir).ok(), expect);
272    if expect.is_some() {
273      assert!(manager.read_ref().is_ok(), "read file failed.");
274      assert_eq!(manager.get_file_path().map(|p| p.to_path_buf()), expect);
275
276      if let Ok(json) = manager.read_ref() {
277        assert_eq!(json.name, "test");
278        assert_eq!(json.version, "0.0.1");
279      }
280
281      let handler = manager.as_ref();
282      assert_eq!(handler.name, "test");
283      assert_eq!(handler.version, "0.0.1");
284      assert!(!handler.private);
285    } else {
286      assert!(manager.read_ref().is_err(), "read field successful.")
287    }
288  }
289}
290
291#[test]
292fn test_writable() {
293  use crate::PACKAGE_JSON_FILENAME;
294  use std::env::current_dir;
295  use std::fs::{create_dir_all, File};
296  use std::io::Write;
297  use tempfile::tempdir_in;
298
299  let mut manager = PackageJsonManager::new();
300  assert!(manager.write().is_err(), "found an available file.");
301
302  let dir = tempdir_in(current_dir().unwrap()).expect("create temp_dir failed!");
303  let file_path = dir.path().join(format!("a/b/c/{}", PACKAGE_JSON_FILENAME));
304  let file_dir = file_path.parent().unwrap();
305  create_dir_all(file_dir).expect("create a/b/c dir failed!");
306
307  let mut valid_file = File::create(&file_path).expect("create file failed!");
308  let valid_json = r#"{
309"name": "test",
310"version": "0.0.1"
311}"#;
312
313  valid_file
314    .write_all(valid_json.as_bytes())
315    .expect("write json failed");
316
317  manager.set_file_path(&file_path);
318
319  // case `read_mut`
320  {
321    let file_writer = manager.read_mut();
322    assert!(
323      file_writer.is_ok(),
324      "{}",
325      format!("create file writer failed: {:?}", file_writer)
326    );
327    if let Ok(json) = file_writer {
328      json.name = "test2".to_string();
329      json.version = "0.0.2".to_string();
330      assert!(manager.write().is_ok());
331    }
332    let file_reader = manager.as_ref();
333    assert_eq!(file_reader.name, "test2");
334    assert_eq!(file_reader.version, "0.0.2");
335  }
336
337  // case `as_mut`
338  {
339    let mutable_handler = manager.as_mut();
340    mutable_handler.name = "test3".to_string();
341    mutable_handler.version = "0.0.3".to_string();
342    let file_reader = manager.as_ref();
343    assert_eq!(file_reader.name, "test3");
344    assert_eq!(file_reader.version, "0.0.3");
345  }
346}