diff --git a/Cargo.lock b/Cargo.lock index db45e38209..2eaa2a4a6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1099,6 +1099,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "futf" version = "0.1.5" @@ -2235,7 +2241,6 @@ dependencies = [ "encoding_rs", "filesize", "fs_extra", - "glob", "hamcrest2", "htmlescape", "ical", @@ -2250,6 +2255,7 @@ dependencies = [ "nu-ansi-term", "nu-color-config", "nu-engine", + "nu-glob", "nu-json", "nu-parser", "nu-path", @@ -2299,12 +2305,20 @@ name = "nu-engine" version = "0.59.1" dependencies = [ "chrono", - "glob", "itertools", + "nu-glob", "nu-path", "nu-protocol", ] +[[package]] +name = "nu-glob" +version = "0.59.1" +dependencies = [ + "doc-comment", + "tempdir", +] + [[package]] name = "nu-json" version = "0.59.1" @@ -2423,9 +2437,9 @@ dependencies = [ "bigdecimal", "chrono", "getset", - "glob", "hamcrest2", "indexmap", + "nu-glob", "nu-path", "nu-protocol", "num-bigint 0.4.3", @@ -3238,6 +3252,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + [[package]] name = "rand" version = "0.7.3" @@ -3283,6 +3310,21 @@ dependencies = [ "rand_core 0.6.3", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.5.1" @@ -3363,6 +3405,15 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -4132,6 +4183,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + [[package]] name = "tempfile" version = "3.3.0" diff --git a/Cargo.toml b/Cargo.toml index 1d897121c3..427d386c5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] default-run = "nu" description = "A new type of shell" documentation = "https://www.nushell.sh/book/" diff --git a/Cargo.toml.old b/Cargo.toml.old index 5f55694a04..4c9b1d73f1 100644 --- a/Cargo.toml.old +++ b/Cargo.toml.old @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] default-run = "nu" description = "A new type of shell" documentation = "https://www.nushell.sh/book/" diff --git a/LICENSE b/LICENSE index 70f227d78f..084f31e5c2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 - 2021 Nushell Project +Copyright (c) 2019 - 2021 The Nushell Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/nu-ansi-term/Cargo.toml b/crates/nu-ansi-term/Cargo.toml index 48ac4dc3c5..3f8a264357 100644 --- a/crates/nu-ansi-term/Cargo.toml +++ b/crates/nu-ansi-term/Cargo.toml @@ -3,7 +3,7 @@ authors = [ "ogham@bsago.me", "Ryan Scheel (Havvy) ", "Josh Triplett ", - "The Nu Project Contributors", + "The Nushell Project Developers", ] description = "Library for ANSI terminal colors and styles (bold, underline)" edition = "2018" diff --git a/crates/nu-ansi-term/LICENCE b/crates/nu-ansi-term/LICENCE index 3228cc99b2..f392dfc93b 100644 --- a/crates/nu-ansi-term/LICENCE +++ b/crates/nu-ansi-term/LICENCE @@ -1,6 +1,7 @@ The MIT License (MIT) Copyright (c) 2014 Benjamin Sago +Copyright (c) 2021-2022 The Nushell Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 2098ffa644..b46e301058 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -13,6 +13,7 @@ nu-ansi-term = "0.42.0" nu-color-config = { path = "../nu-color-config", version = "0.59.1" } nu-engine = { path = "../nu-engine", version = "0.59.1" } +nu-glob = { path = "../nu-glob", version = "0.59.1" } nu-json = { path = "../nu-json", version = "0.59.1" } nu-parser = { path = "../nu-parser", version = "0.59.1" } nu-path = { path = "../nu-path", version = "0.59.1" } @@ -39,7 +40,6 @@ eml-parser = "0.1.0" encoding_rs = "0.8.30" filesize = "0.2.0" fs_extra = "1.2.0" -glob = "0.3.0" htmlescape = "0.3.1" ical = "0.7.0" indexmap = { version="1.7", features=["serde-1"] } diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index f835716c52..fa420dae81 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -9,7 +9,7 @@ use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanne use crate::filesystem::util::FileStructure; -const GLOB_PARAMS: glob::MatchOptions = glob::MatchOptions { +const GLOB_PARAMS: nu_glob::MatchOptions = nu_glob::MatchOptions { case_sensitive: true, require_literal_separator: false, require_literal_leading_dot: false, @@ -58,7 +58,7 @@ impl Command for Cp { let source = path.join(src.item.as_str()); let destination = path.join(dst.item.as_str()); - let sources: Vec<_> = match glob::glob_with(&source.to_string_lossy(), GLOB_PARAMS) { + let sources: Vec<_> = match nu_glob::glob_with(&source.to_string_lossy(), GLOB_PARAMS) { Ok(files) => files.collect(), Err(e) => { return Err(ShellError::SpannedLabeledError( @@ -193,7 +193,7 @@ impl Command for Cp { } // let mut sources = - // glob::glob(&source.to_string_lossy()).map_or_else(|_| Vec::new(), Iterator::collect); + // nu_glob::glob(&source.to_string_lossy()).map_or_else(|_| Vec::new(), Iterator::collect); // if sources.is_empty() { // return Err(ShellError::FileNotFound(call.positional[0].span)); // } diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index 66316f3fc8..7888d39f4b 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -9,7 +9,7 @@ use nu_protocol::{ Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, }; -const GLOB_PARAMS: glob::MatchOptions = glob::MatchOptions { +const GLOB_PARAMS: nu_glob::MatchOptions = nu_glob::MatchOptions { case_sensitive: true, require_literal_separator: false, require_literal_leading_dot: false, @@ -62,7 +62,7 @@ impl Command for Mv { let source = path.join(spanned_source.item.as_str()); let destination = path.join(spanned_destination.item.as_str()); - let mut sources = glob::glob_with(&source.to_string_lossy(), GLOB_PARAMS) + let mut sources = nu_glob::glob_with(&source.to_string_lossy(), GLOB_PARAMS) .map_or_else(|_| Vec::new(), Iterator::collect); if sources.is_empty() { diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index bc578a93a0..8d7dbaca0d 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -16,7 +16,7 @@ use nu_protocol::{ Spanned, SyntaxShape, Type, Value, }; -const GLOB_PARAMS: glob::MatchOptions = glob::MatchOptions { +const GLOB_PARAMS: nu_glob::MatchOptions = nu_glob::MatchOptions { case_sensitive: true, require_literal_separator: false, require_literal_leading_dot: false, @@ -165,9 +165,9 @@ fn rm( } let path = path.join(&target.item); - match glob::glob_with( + match nu_glob::glob_with( &path.to_string_lossy(), - glob::MatchOptions { + nu_glob::MatchOptions { require_literal_leading_dot: true, ..GLOB_PARAMS }, diff --git a/crates/nu-command/src/platform/dir_info.rs b/crates/nu-command/src/platform/dir_info.rs index a18081510e..8f8f761115 100644 --- a/crates/nu-command/src/platform/dir_info.rs +++ b/crates/nu-command/src/platform/dir_info.rs @@ -1,5 +1,5 @@ use filesize::file_real_size_fast; -use glob::Pattern; +use nu_glob::Pattern; use nu_protocol::{ShellError, Span, Value}; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; diff --git a/crates/nu-command/src/platform/du.rs b/crates/nu-command/src/platform/du.rs index 7acd997f3a..700d23085e 100644 --- a/crates/nu-command/src/platform/du.rs +++ b/crates/nu-command/src/platform/du.rs @@ -1,6 +1,6 @@ use crate::{DirBuilder, DirInfo, FileInfo}; -use glob::{GlobError, MatchOptions, Pattern}; use nu_engine::CallExt; +use nu_glob::{GlobError, MatchOptions, Pattern}; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, @@ -103,9 +103,9 @@ impl Command for Du { let mut paths = match args.path { Some(p) => { let p = p.item.to_str().expect("Why isn't this encoded properly?"); - glob::glob_with(p, GLOB_PARAMS) + nu_glob::glob_with(p, GLOB_PARAMS) } - None => glob::glob_with("*", GLOB_PARAMS), + None => nu_glob::glob_with("*", GLOB_PARAMS), } .map_err(|e| { ShellError::SpannedLabeledError(e.msg.to_string(), "glob error".to_string(), tag) diff --git a/crates/nu-command/tests/commands/sort_by.rs b/crates/nu-command/tests/commands/sort_by.rs index 87398661fe..719cfc2c98 100644 --- a/crates/nu-command/tests/commands/sort_by.rs +++ b/crates/nu-command/tests/commands/sort_by.rs @@ -75,7 +75,7 @@ fn sort_primitive_values() { "# )); - assert_eq!(actual.out, "authors = [\"The Nu Project Contributors\"]"); + assert_eq!(actual.out, "authors = [\"The Nushell Project Developers\"]"); } #[test] diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index f6e3f2b9fa..b9477b41bf 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -6,9 +6,10 @@ edition = "2021" [dependencies] nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.59.1" } nu-path = { path = "../nu-path", version = "0.59.1" } +nu-glob = { path = "../nu-glob", version = "0.59.1" } + itertools = "0.10.1" chrono = { version="0.4.19", features=["serde"] } -glob = "0.3.0" [features] plugin = [] diff --git a/crates/nu-engine/src/glob_from.rs b/crates/nu-engine/src/glob_from.rs index 054ca6be26..89360a9e56 100644 --- a/crates/nu-engine/src/glob_from.rs +++ b/crates/nu-engine/src/glob_from.rs @@ -5,7 +5,7 @@ use std::path::{Component, Path, PathBuf}; use nu_path::{canonicalize_with, expand_path_with}; use nu_protocol::{ShellError, Span, Spanned}; -/// This function is like `glob::glob` from the `glob` crate, except it is relative to a given cwd. +/// This function is like `nu_glob::glob` from the `glob` crate, except it is relative to a given cwd. /// /// It returns a tuple of two values: the first is an optional prefix that the expanded filenames share. /// This prefix can be removed from the front of each value to give an approximation of the relative path @@ -81,7 +81,7 @@ pub fn glob_from( let pattern = pattern.to_string_lossy().to_string(); - let glob = glob::glob(&pattern).map_err(|err| { + let glob = nu_glob::glob(&pattern).map_err(|err| { nu_protocol::ShellError::SpannedLabeledError( "Error extracting glob pattern".into(), err.to_string(), diff --git a/crates/nu-glob/Cargo.toml b/crates/nu-glob/Cargo.toml new file mode 100644 index 0000000000..d4d47ce07b --- /dev/null +++ b/crates/nu-glob/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "nu-glob" +version = "0.59.1" +authors = ["The Nushell Project Developers", "The Rust Project Developers"] +license = "MIT/Apache-2.0" +description = """ +Fork of glob. Support for matching file paths against Unix shell style patterns. +""" +edition = "2021" +categories = ["filesystem"] + +[dev-dependencies] +tempdir = "0.3" +doc-comment = "0.3" \ No newline at end of file diff --git a/crates/nu-glob/LICENSE-APACHE b/crates/nu-glob/LICENSE-APACHE new file mode 100644 index 0000000000..f8e5e5ea03 --- /dev/null +++ b/crates/nu-glob/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/crates/nu-glob/LICENSE-MIT b/crates/nu-glob/LICENSE-MIT new file mode 100644 index 0000000000..c028b7762e --- /dev/null +++ b/crates/nu-glob/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) 2014 The Rust Project Developers +Copyright (c) 2022 The Nushell Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/crates/nu-glob/README.md b/crates/nu-glob/README.md new file mode 100644 index 0000000000..71939732b4 --- /dev/null +++ b/crates/nu-glob/README.md @@ -0,0 +1,28 @@ +nu-glob +==== + +Support for matching file paths against Unix shell style patterns. + +## Usage + +To use `nu-glob`, add this to your `Cargo.toml`: + +```toml +[dependencies] +nu-glob = "0.59.1" +``` + +## Examples + +Print all jpg files in /media/ and all of its subdirectories. + +```rust +use nu_nu_glob::glob; + +for entry in glob("/media/**/*.jpg").expect("Failed to read glob pattern") { + match entry { + Ok(path) => println!("{:?}", path.display()), + Err(e) => println!("{:?}", e), + } +} +``` \ No newline at end of file diff --git a/crates/nu-glob/src/lib.rs b/crates/nu-glob/src/lib.rs new file mode 100644 index 0000000000..b51767d714 --- /dev/null +++ b/crates/nu-glob/src/lib.rs @@ -0,0 +1,1196 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Copyright 2022 The Nushell Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Support for matching file paths against Unix shell style patterns. +//! +//! The `glob` and `glob_with` functions allow querying the filesystem for all +//! files that match a particular pattern (similar to the libc `glob` function). +//! The methods on the `Pattern` type provide functionality for checking if +//! individual paths match a particular pattern (similar to the libc `fnmatch` +//! function). +//! +//! For consistency across platforms, and for Windows support, this module +//! is implemented entirely in Rust rather than deferring to the libc +//! `glob`/`fnmatch` functions. +//! +//! # Examples +//! +//! To print all jpg files in `/media/` and all of its subdirectories. +//! +//! ```rust,no_run +//! use nu_glob::glob; +//! +//! for entry in glob("/media/**/*.jpg").expect("Failed to read glob pattern") { +//! match entry { +//! Ok(path) => println!("{:?}", path.display()), +//! Err(e) => println!("{:?}", e), +//! } +//! } +//! ``` +//! +//! To print all files containing the letter "a", case insensitive, in a `local` +//! directory relative to the current working directory. This ignores errors +//! instead of printing them. +//! +//! ```rust,no_run +//! use nu_glob::glob_with; +//! use nu_glob::MatchOptions; +//! +//! let options = MatchOptions { +//! case_sensitive: false, +//! require_literal_separator: false, +//! require_literal_leading_dot: false, +//! }; +//! for entry in glob_with("local/*a*", options).unwrap() { +//! if let Ok(path) = entry { +//! println!("{:?}", path.display()) +//! } +//! } +//! ``` + +#![doc( + html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "https://www.rust-lang.org/favicon.ico", + html_root_url = "https://docs.rs/glob/0.3.0" +)] +#![deny(missing_docs)] + +#[cfg(test)] +#[macro_use] +extern crate doc_comment; + +#[cfg(test)] +doctest!("../README.md"); + +use std::cmp; +use std::error::Error; +use std::fmt; +use std::fs; +use std::io; +use std::path::{self, Component, Path, PathBuf}; +use std::str::FromStr; + +use MatchResult::{EntirePatternDoesntMatch, Match, SubPatternDoesntMatch}; +use PatternToken::{AnyChar, AnyRecursiveSequence, AnySequence, Char}; + +/// An iterator that yields `Path`s from the filesystem that match a particular +/// pattern. +/// +/// Note that it yields `GlobResult` in order to report any `IoErrors` that may +/// arise during iteration. If a directory matches but is unreadable, +/// thereby preventing its contents from being checked for matches, a +/// `GlobError` is returned to express this. +/// +/// See the `glob` function for more details. +#[derive(Debug)] +pub struct Paths { + dir_patterns: Vec, + require_dir: bool, + options: MatchOptions, + todo: Vec>, + scope: Option, +} + +/// Return an iterator that produces all the `Path`s that match the given +/// pattern using default match options, which may be absolute or relative to +/// the current working directory. +/// +/// This may return an error if the pattern is invalid. +/// +/// This method uses the default match options and is equivalent to calling +/// `glob_with(pattern, MatchOptions::new())`. Use `glob_with` directly if you +/// want to use non-default match options. +/// +/// When iterating, each result is a `GlobResult` which expresses the +/// possibility that there was an `IoError` when attempting to read the contents +/// of the matched path. In other words, each item returned by the iterator +/// will either be an `Ok(Path)` if the path matched, or an `Err(GlobError)` if +/// the path (partially) matched _but_ its contents could not be read in order +/// to determine if its contents matched. +/// +/// See the `Paths` documentation for more information. +/// +/// # Examples +/// +/// Consider a directory `/media/pictures` containing only the files +/// `kittens.jpg`, `puppies.jpg` and `hamsters.gif`: +/// +/// ```rust,no_run +/// use nu_glob::glob; +/// +/// for entry in glob("/media/pictures/*.jpg").unwrap() { +/// match entry { +/// Ok(path) => println!("{:?}", path.display()), +/// +/// // if the path matched but was unreadable, +/// // thereby preventing its contents from matching +/// Err(e) => println!("{:?}", e), +/// } +/// } +/// ``` +/// +/// The above code will print: +/// +/// ```ignore +/// /media/pictures/kittens.jpg +/// /media/pictures/puppies.jpg +/// ``` +/// +/// If you want to ignore unreadable paths, you can use something like +/// `filter_map`: +/// +/// ```rust +/// use nu_glob::glob; +/// use std::result::Result; +/// +/// for path in glob("/media/pictures/*.jpg").unwrap().filter_map(Result::ok) { +/// println!("{}", path.display()); +/// } +/// ``` +/// Paths are yielded in alphabetical order. +pub fn glob(pattern: &str) -> Result { + glob_with(pattern, MatchOptions::new()) +} + +/// Return an iterator that produces all the `Path`s that match the given +/// pattern using the specified match options, which may be absolute or relative +/// to the current working directory. +/// +/// This may return an error if the pattern is invalid. +/// +/// This function accepts Unix shell style patterns as described by +/// `Pattern::new(..)`. The options given are passed through unchanged to +/// `Pattern::matches_with(..)` with the exception that +/// `require_literal_separator` is always set to `true` regardless of the value +/// passed to this function. +/// +/// Paths are yielded in alphabetical order. +pub fn glob_with(pattern: &str, options: MatchOptions) -> Result { + #[cfg(windows)] + fn check_windows_verbatim(p: &Path) -> bool { + match p.components().next() { + Some(Component::Prefix(ref p)) => p.kind().is_verbatim(), + _ => false, + } + } + #[cfg(not(windows))] + fn check_windows_verbatim(_: &Path) -> bool { + false + } + + #[cfg(windows)] + fn to_scope(p: &Path) -> PathBuf { + // FIXME handle volume relative paths here + p.to_path_buf() + } + #[cfg(not(windows))] + fn to_scope(p: &Path) -> PathBuf { + p.to_path_buf() + } + + // make sure that the pattern is valid first, else early return with error + if let Err(err) = Pattern::new(pattern) { + return Err(err); + } + + let mut components = Path::new(pattern).components().peekable(); + while let Some(&Component::Prefix(..)) | Some(&Component::RootDir) = components.peek() { + components.next(); + } + + let rest = components.map(|s| s.as_os_str()).collect::(); + let normalized_pattern = Path::new(pattern).iter().collect::(); + let root_len = normalized_pattern + .to_str() + .expect("internal error: expected string") + .len() + - rest + .to_str() + .expect("internal error: expected string") + .len(); + let root = if root_len > 0 { + Some(Path::new(&pattern[..root_len])) + } else { + None + }; + + if root_len > 0 + && check_windows_verbatim(root.expect("internal error: already checked for len > 0")) + { + // FIXME: How do we want to handle verbatim paths? I'm inclined to + // return nothing, since we can't very well find all UNC shares with a + // 1-letter server name. + return Ok(Paths { + dir_patterns: Vec::new(), + require_dir: false, + options, + todo: Vec::new(), + scope: None, + }); + } + + let scope = root.map_or_else(|| PathBuf::from("."), to_scope); + + let mut dir_patterns = Vec::new(); + let components = + pattern[cmp::min(root_len, pattern.len())..].split_terminator(path::is_separator); + + for component in components { + dir_patterns.push(Pattern::new(component)?); + } + + if root_len == pattern.len() { + dir_patterns.push(Pattern { + original: "".to_string(), + tokens: Vec::new(), + is_recursive: false, + }); + } + + let last_is_separator = pattern.chars().next_back().map(path::is_separator); + let require_dir = last_is_separator == Some(true); + let todo = Vec::new(); + + Ok(Paths { + dir_patterns, + require_dir, + options, + todo, + scope: Some(scope), + }) +} + +/// A glob iteration error. +/// +/// This is typically returned when a particular path cannot be read +/// to determine if its contents match the glob pattern. This is possible +/// if the program lacks the appropriate permissions, for example. +#[derive(Debug)] +pub struct GlobError { + path: PathBuf, + error: io::Error, +} + +impl GlobError { + /// The Path that the error corresponds to. + pub fn path(&self) -> &Path { + &self.path + } + + /// The error in question. + pub fn error(&self) -> &io::Error { + &self.error + } + + /// Consumes self, returning the _raw_ underlying `io::Error` + pub fn into_error(self) -> io::Error { + self.error + } +} + +impl Error for GlobError { + #[allow(unknown_lints, bare_trait_objects)] + fn cause(&self) -> Option<&dyn Error> { + Some(&self.error) + } +} + +impl fmt::Display for GlobError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "attempting to read `{}` resulted in an error: {}", + self.path.display(), + self.error + ) + } +} + +fn is_dir(p: &Path) -> bool { + fs::metadata(p).map(|m| m.is_dir()).unwrap_or(false) +} + +/// An alias for a glob iteration result. +/// +/// This represents either a matched path or a glob iteration error, +/// such as failing to read a particular directory's contents. +pub type GlobResult = Result; + +impl Iterator for Paths { + type Item = GlobResult; + + fn next(&mut self) -> Option { + // the todo buffer hasn't been initialized yet, so it's done at this + // point rather than in glob() so that the errors are unified that is, + // failing to fill the buffer is an iteration error construction of the + // iterator (i.e. glob()) only fails if it fails to compile the Pattern + if let Some(scope) = self.scope.take() { + if !self.dir_patterns.is_empty() { + // Shouldn't happen, but we're using -1 as a special index. + assert!(self.dir_patterns.len() < !0 as usize); + + fill_todo(&mut self.todo, &self.dir_patterns, 0, &scope, self.options); + } + } + + loop { + if self.dir_patterns.is_empty() || self.todo.is_empty() { + return None; + } + + let (path, mut idx) = match self + .todo + .pop() + .expect("internal error: already checked for non-empty") + { + Ok(pair) => pair, + Err(e) => return Some(Err(e)), + }; + + // idx -1: was already checked by fill_todo, maybe path was '.' or + // '..' that we can't match here because of normalization. + if idx == !0 as usize { + if self.require_dir && !is_dir(&path) { + continue; + } + return Some(Ok(path)); + } + + if self.dir_patterns[idx].is_recursive { + let mut next = idx; + + // collapse consecutive recursive patterns + while (next + 1) < self.dir_patterns.len() + && self.dir_patterns[next + 1].is_recursive + { + next += 1; + } + + if is_dir(&path) { + // the path is a directory, so it's a match + + // push this directory's contents + fill_todo( + &mut self.todo, + &self.dir_patterns, + next, + &path, + self.options, + ); + + if next == self.dir_patterns.len() - 1 { + // pattern ends in recursive pattern, so return this + // directory as a result + return Some(Ok(path)); + } else { + // advanced to the next pattern for this path + idx = next + 1; + } + } else if next == self.dir_patterns.len() - 1 { + // not a directory and it's the last pattern, meaning no + // match + continue; + } else { + // advanced to the next pattern for this path + idx = next + 1; + } + } + + // not recursive, so match normally + if self.dir_patterns[idx].matches_with( + { + match path.file_name().and_then(|s| s.to_str()) { + // FIXME (#9639): How do we handle non-utf8 filenames? + // Ignore them for now; ideally we'd still match them + // against a * + None => continue, + Some(x) => x, + } + }, + self.options, + ) { + if idx == self.dir_patterns.len() - 1 { + // it is not possible for a pattern to match a directory + // *AND* its children so we don't need to check the + // children + + if !self.require_dir || is_dir(&path) { + return Some(Ok(path)); + } + } else { + fill_todo( + &mut self.todo, + &self.dir_patterns, + idx + 1, + &path, + self.options, + ); + } + } + } + } +} + +/// A pattern parsing error. +#[derive(Debug)] +#[allow(missing_copy_implementations)] +pub struct PatternError { + /// The approximate character index of where the error occurred. + pub pos: usize, + + /// A message describing the error. + pub msg: &'static str, +} + +impl Error for PatternError { + fn description(&self) -> &str { + self.msg + } +} + +impl fmt::Display for PatternError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Pattern syntax error near position {}: {}", + self.pos, self.msg + ) + } +} + +/// A compiled Unix shell style pattern. +/// +/// - `?` matches any single character. +/// +/// - `*` matches any (possibly empty) sequence of characters. +/// +/// - `**` matches the current directory and arbitrary subdirectories. This +/// sequence **must** form a single path component, so both `**a` and `b**` +/// are invalid and will result in an error. A sequence of more than two +/// consecutive `*` characters is also invalid. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub struct Pattern { + original: String, + tokens: Vec, + is_recursive: bool, +} + +/// Show the original glob pattern. +impl fmt::Display for Pattern { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.original.fmt(f) + } +} + +impl FromStr for Pattern { + type Err = PatternError; + + fn from_str(s: &str) -> Result { + Self::new(s) + } +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +enum PatternToken { + Char(char), + AnyChar, + AnySequence, + AnyRecursiveSequence, +} + +#[allow(clippy::enum_variant_names)] +#[derive(Copy, Clone, PartialEq)] +enum MatchResult { + Match, + SubPatternDoesntMatch, + EntirePatternDoesntMatch, +} + +const ERROR_WILDCARDS: &str = "wildcards are either regular `*` or recursive `**`"; +const ERROR_RECURSIVE_WILDCARDS: &str = "recursive wildcards must form a single path \ + component"; + +impl Pattern { + /// This function compiles Unix shell style patterns. + /// + /// An invalid glob pattern will yield a `PatternError`. + pub fn new(pattern: &str) -> Result { + let chars = pattern.chars().collect::>(); + let mut tokens = Vec::new(); + let mut is_recursive = false; + let mut i = 0; + + while i < chars.len() { + match chars[i] { + '?' => { + tokens.push(AnyChar); + i += 1; + } + '*' => { + let old = i; + + while i < chars.len() && chars[i] == '*' { + i += 1; + } + + let count = i - old; + + #[allow(clippy::comparison_chain)] + if count > 2 { + return Err(PatternError { + pos: old + 2, + msg: ERROR_WILDCARDS, + }); + } else if count == 2 { + // ** can only be an entire path component + // i.e. a/**/b is valid, but a**/b or a/**b is not + // invalid matches are treated literally + let is_valid = if i == 2 || path::is_separator(chars[i - count - 1]) { + // it ends in a '/' + if i < chars.len() && path::is_separator(chars[i]) { + i += 1; + true + // or the pattern ends here + // this enables the existing globbing mechanism + } else if i == chars.len() { + true + // `**` ends in non-separator + } else { + return Err(PatternError { + pos: i, + msg: ERROR_RECURSIVE_WILDCARDS, + }); + } + // `**` begins with non-separator + } else { + return Err(PatternError { + pos: old - 1, + msg: ERROR_RECURSIVE_WILDCARDS, + }); + }; + + if is_valid { + // collapse consecutive AnyRecursiveSequence to a + // single one + + let tokens_len = tokens.len(); + + if !(tokens_len > 1 && tokens[tokens_len - 1] == AnyRecursiveSequence) { + is_recursive = true; + tokens.push(AnyRecursiveSequence); + } + } + } else { + tokens.push(AnySequence); + } + } + c => { + tokens.push(Char(c)); + i += 1; + } + } + } + + Ok(Self { + tokens, + original: pattern.to_string(), + is_recursive, + }) + } + + /// Return if the given `str` matches this `Pattern` using the default + /// match options (i.e. `MatchOptions::new()`). + /// + /// # Examples + /// + /// ```rust + /// use nu_glob::Pattern; + /// + /// assert!(Pattern::new("c?t").unwrap().matches("cat")); + /// assert!(Pattern::new("d*g").unwrap().matches("doog")); + /// ``` + pub fn matches(&self, str: &str) -> bool { + self.matches_with(str, MatchOptions::new()) + } + + /// Return if the given `Path`, when converted to a `str`, matches this + /// `Pattern` using the default match options (i.e. `MatchOptions::new()`). + pub fn matches_path(&self, path: &Path) -> bool { + // FIXME (#9639): This needs to handle non-utf8 paths + path.to_str().map_or(false, |s| self.matches(s)) + } + + /// Return if the given `str` matches this `Pattern` using the specified + /// match options. + pub fn matches_with(&self, str: &str, options: MatchOptions) -> bool { + self.matches_from(true, str.chars(), 0, options) == Match + } + + /// Return if the given `Path`, when converted to a `str`, matches this + /// `Pattern` using the specified match options. + pub fn matches_path_with(&self, path: &Path, options: MatchOptions) -> bool { + // FIXME (#9639): This needs to handle non-utf8 paths + path.to_str() + .map_or(false, |s| self.matches_with(s, options)) + } + + /// Access the original glob pattern. + pub fn as_str(&self) -> &str { + &self.original + } + + fn matches_from( + &self, + mut follows_separator: bool, + mut file: std::str::Chars, + i: usize, + options: MatchOptions, + ) -> MatchResult { + for (ti, token) in self.tokens[i..].iter().enumerate() { + match *token { + AnySequence | AnyRecursiveSequence => { + // ** must be at the start. + debug_assert!(match *token { + AnyRecursiveSequence => follows_separator, + _ => true, + }); + + // Empty match + match self.matches_from(follows_separator, file.clone(), i + ti + 1, options) { + SubPatternDoesntMatch => (), // keep trying + m => return m, + }; + + while let Some(c) = file.next() { + if follows_separator && options.require_literal_leading_dot && c == '.' { + return SubPatternDoesntMatch; + } + follows_separator = path::is_separator(c); + match *token { + AnyRecursiveSequence if !follows_separator => continue, + AnySequence + if options.require_literal_separator && follows_separator => + { + return SubPatternDoesntMatch + } + _ => (), + } + match self.matches_from( + follows_separator, + file.clone(), + i + ti + 1, + options, + ) { + SubPatternDoesntMatch => (), // keep trying + m => return m, + } + } + } + _ => { + let c = match file.next() { + Some(c) => c, + None => return EntirePatternDoesntMatch, + }; + + let is_sep = path::is_separator(c); + + if !match *token { + AnyChar + if (options.require_literal_separator && is_sep) + || (follows_separator + && options.require_literal_leading_dot + && c == '.') => + { + false + } + AnyChar => true, + Char(c2) => chars_eq(c, c2, options.case_sensitive), + AnySequence | AnyRecursiveSequence => unreachable!(), + } { + return SubPatternDoesntMatch; + } + follows_separator = is_sep; + } + } + } + + // Iter is fused. + if file.next().is_none() { + Match + } else { + SubPatternDoesntMatch + } + } +} + +// Fills `todo` with paths under `path` to be matched by `patterns[idx]`, +// special-casing patterns to match `.` and `..`, and avoiding `readdir()` +// calls when there are no metacharacters in the pattern. +fn fill_todo( + todo: &mut Vec>, + patterns: &[Pattern], + idx: usize, + path: &Path, + options: MatchOptions, +) { + // convert a pattern that's just many Char(_) to a string + fn pattern_as_str(pattern: &Pattern) -> Option { + let mut s = String::new(); + for token in &pattern.tokens { + match *token { + Char(c) => s.push(c), + _ => return None, + } + } + + Some(s) + } + + let add = |todo: &mut Vec<_>, next_path: PathBuf| { + if idx + 1 == patterns.len() { + // We know it's good, so don't make the iterator match this path + // against the pattern again. In particular, it can't match + // . or .. globs since these never show up as path components. + todo.push(Ok((next_path, !0 as usize))); + } else { + fill_todo(todo, patterns, idx + 1, &next_path, options); + } + }; + + let pattern = &patterns[idx]; + let is_dir = is_dir(path); + let curdir = path == Path::new("."); + match pattern_as_str(pattern) { + Some(s) => { + // This pattern component doesn't have any metacharacters, so we + // don't need to read the current directory to know where to + // continue. So instead of passing control back to the iterator, + // we can just check for that one entry and potentially recurse + // right away. + let special = "." == s || ".." == s; + let next_path = if curdir { + PathBuf::from(s) + } else { + path.join(&s) + }; + if (special && is_dir) || (!special && fs::metadata(&next_path).is_ok()) { + add(todo, next_path); + } + } + None if is_dir => { + let dirs = fs::read_dir(path).and_then(|d| { + d.map(|e| { + e.map(|e| { + if curdir { + PathBuf::from( + e.path() + .file_name() + .expect("internal error: missing filename"), + ) + } else { + e.path() + } + }) + }) + .collect::, _>>() + }); + match dirs { + Ok(mut children) => { + children.sort_by(|p1, p2| p2.file_name().cmp(&p1.file_name())); + todo.extend(children.into_iter().map(|x| Ok((x, idx)))); + + // Matching the special directory entries . and .. that + // refer to the current and parent directory respectively + // requires that the pattern has a leading dot, even if the + // `MatchOptions` field `require_literal_leading_dot` is not + // set. + if !pattern.tokens.is_empty() && pattern.tokens[0] == Char('.') { + for &special in &[".", ".."] { + if pattern.matches_with(special, options) { + add(todo, path.join(special)); + } + } + } + } + Err(e) => { + todo.push(Err(GlobError { + path: path.to_path_buf(), + error: e, + })); + } + } + } + None => { + // not a directory, nothing more to find + } + } +} + +/// A helper function to determine if two chars are (possibly case-insensitively) equal. +fn chars_eq(a: char, b: char, case_sensitive: bool) -> bool { + if cfg!(windows) && path::is_separator(a) && path::is_separator(b) { + true + } else if !case_sensitive && a.is_ascii() && b.is_ascii() { + // FIXME: work with non-ascii chars properly (issue #9084) + a.to_ascii_lowercase() == b.to_ascii_lowercase() + } else { + a == b + } +} + +/// Configuration options to modify the behaviour of `Pattern::matches_with(..)`. +#[allow(missing_copy_implementations)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct MatchOptions { + /// Whether or not patterns should be matched in a case-sensitive manner. + /// This currently only considers upper/lower case relationships between + /// ASCII characters, but in future this might be extended to work with + /// Unicode. + pub case_sensitive: bool, + + /// Whether or not path-component separator characters (e.g. `/` on + /// Posix) must be matched by a literal `/`, rather than by `*` or `?` or + /// `[...]`. + pub require_literal_separator: bool, + + /// Whether or not paths that contain components that start with a `.` + /// will require that `.` appears literally in the pattern; `*`, `?`, `**`, + /// or `[...]` will not match. This is useful because such files are + /// conventionally considered hidden on Unix systems and it might be + /// desirable to skip them when listing files. + pub require_literal_leading_dot: bool, +} + +impl MatchOptions { + /// Constructs a new `MatchOptions` with default field values. This is used + /// when calling functions that do not take an explicit `MatchOptions` + /// parameter. + /// + /// This function always returns this value: + /// + /// ```rust,ignore + /// MatchOptions { + /// case_sensitive: true, + /// require_literal_separator: false, + /// require_literal_leading_dot: false + /// } + /// ``` + pub fn new() -> Self { + Self { + case_sensitive: true, + require_literal_separator: false, + require_literal_leading_dot: false, + } + } +} + +#[cfg(test)] +mod test { + use super::{glob, MatchOptions, Pattern}; + use std::path::Path; + + #[test] + fn test_pattern_from_str() { + assert!("a*b".parse::().unwrap().matches("a_b")); + assert!("a/**b".parse::().unwrap_err().pos == 4); + } + + #[test] + fn test_wildcard_errors() { + assert!(Pattern::new("a/**b").unwrap_err().pos == 4); + assert!(Pattern::new("a/bc**").unwrap_err().pos == 3); + assert!(Pattern::new("a/*****").unwrap_err().pos == 4); + assert!(Pattern::new("a/b**c**d").unwrap_err().pos == 2); + assert!(Pattern::new("a**b").unwrap_err().pos == 0); + } + + #[test] + fn test_glob_errors() { + assert!(glob("a/**b").err().unwrap().pos == 4); + } + + // this test assumes that there is a /root directory and that + // the user running this test is not root or otherwise doesn't + // have permission to read its contents + #[cfg(all(unix, not(target_os = "macos")))] + #[test] + fn test_iteration_errors() { + use std::io; + let mut iter = glob("/root/*").unwrap(); + + // GlobErrors shouldn't halt iteration + let next = iter.next(); + assert!(next.is_some()); + + let err = next.unwrap(); + assert!(err.is_err()); + + let err = err.err().unwrap(); + assert!(err.path() == Path::new("/root")); + assert!(err.error().kind() == io::ErrorKind::PermissionDenied); + } + + #[test] + fn test_absolute_pattern() { + assert!(glob("/").unwrap().next().is_some()); + assert!(glob("//").unwrap().next().is_some()); + + // assume that the filesystem is not empty! + assert!(glob("/*").unwrap().next().is_some()); + + #[cfg(not(windows))] + fn win() {} + + #[cfg(windows)] + fn win() { + use std::env::current_dir; + use std::path::Component; + + // check windows absolute paths with host/device components + let root_with_device = current_dir() + .ok() + .and_then(|p| match p.components().next().unwrap() { + Component::Prefix(prefix_component) => { + let path = Path::new(prefix_component.as_os_str()); + path.join("*"); + Some(path.to_path_buf()) + } + _ => panic!("no prefix in this path"), + }) + .unwrap(); + // FIXME (#9639): This needs to handle non-utf8 paths + assert!(glob(root_with_device.as_os_str().to_str().unwrap()) + .unwrap() + .next() + .is_some()); + } + win() + } + + #[test] + fn test_wildcards() { + assert!(Pattern::new("a*b").unwrap().matches("a_b")); + assert!(Pattern::new("a*b*c").unwrap().matches("abc")); + assert!(!Pattern::new("a*b*c").unwrap().matches("abcd")); + assert!(Pattern::new("a*b*c").unwrap().matches("a_b_c")); + assert!(Pattern::new("a*b*c").unwrap().matches("a___b___c")); + assert!(Pattern::new("abc*abc*abc") + .unwrap() + .matches("abcabcabcabcabcabcabc")); + assert!(!Pattern::new("abc*abc*abc") + .unwrap() + .matches("abcabcabcabcabcabcabca")); + assert!(Pattern::new("a*a*a*a*a*a*a*a*a") + .unwrap() + .matches("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")); + } + + #[test] + fn test_recursive_wildcards() { + let pat = Pattern::new("some/**/needle.txt").unwrap(); + assert!(pat.matches("some/needle.txt")); + assert!(pat.matches("some/one/needle.txt")); + assert!(pat.matches("some/one/two/needle.txt")); + assert!(pat.matches("some/other/needle.txt")); + assert!(!pat.matches("some/other/notthis.txt")); + + // a single ** should be valid, for globs + // Should accept anything + let pat = Pattern::new("**").unwrap(); + assert!(pat.is_recursive); + assert!(pat.matches("abcde")); + assert!(pat.matches("")); + assert!(pat.matches(".asdf")); + assert!(pat.matches("/x/.asdf")); + + // collapse consecutive wildcards + let pat = Pattern::new("some/**/**/needle.txt").unwrap(); + assert!(pat.matches("some/needle.txt")); + assert!(pat.matches("some/one/needle.txt")); + assert!(pat.matches("some/one/two/needle.txt")); + assert!(pat.matches("some/other/needle.txt")); + assert!(!pat.matches("some/other/notthis.txt")); + + // ** can begin the pattern + let pat = Pattern::new("**/test").unwrap(); + assert!(pat.matches("one/two/test")); + assert!(pat.matches("one/test")); + assert!(pat.matches("test")); + + // /** can begin the pattern + let pat = Pattern::new("/**/test").unwrap(); + assert!(pat.matches("/one/two/test")); + assert!(pat.matches("/one/test")); + assert!(pat.matches("/test")); + assert!(!pat.matches("/one/notthis")); + assert!(!pat.matches("/notthis")); + + // Only start sub-patterns on start of path segment. + let pat = Pattern::new("**/.*").unwrap(); + assert!(pat.matches(".abc")); + assert!(pat.matches("abc/.abc")); + assert!(!pat.matches("ab.c")); + assert!(!pat.matches("abc/ab.c")); + } + + #[test] + fn test_lots_of_files() { + // this is a good test because it touches lots of differently named files + glob("/*/*/*/*").unwrap().nth(10000); + } + + #[test] + fn test_pattern_matches() { + let txt_pat = Pattern::new("*hello.txt").unwrap(); + assert!(txt_pat.matches("hello.txt")); + assert!(txt_pat.matches("gareth_says_hello.txt")); + assert!(txt_pat.matches("some/path/to/hello.txt")); + assert!(txt_pat.matches("some\\path\\to\\hello.txt")); + assert!(txt_pat.matches("/an/absolute/path/to/hello.txt")); + assert!(!txt_pat.matches("hello.txt-and-then-some")); + assert!(!txt_pat.matches("goodbye.txt")); + + let dir_pat = Pattern::new("*some/path/to/hello.txt").unwrap(); + assert!(dir_pat.matches("some/path/to/hello.txt")); + assert!(dir_pat.matches("a/bigger/some/path/to/hello.txt")); + assert!(!dir_pat.matches("some/path/to/hello.txt-and-then-some")); + assert!(!dir_pat.matches("some/other/path/to/hello.txt")); + } + + #[test] + fn test_pattern_matches_case_insensitive() { + let pat = Pattern::new("aBcDeFg").unwrap(); + let options = MatchOptions { + case_sensitive: false, + require_literal_separator: false, + require_literal_leading_dot: false, + }; + + assert!(pat.matches_with("aBcDeFg", options)); + assert!(pat.matches_with("abcdefg", options)); + assert!(pat.matches_with("ABCDEFG", options)); + assert!(pat.matches_with("AbCdEfG", options)); + } + + #[test] + fn test_pattern_matches_require_literal_separator() { + let options_require_literal = MatchOptions { + case_sensitive: true, + require_literal_separator: true, + require_literal_leading_dot: false, + }; + let options_not_require_literal = MatchOptions { + case_sensitive: true, + require_literal_separator: false, + require_literal_leading_dot: false, + }; + + assert!(Pattern::new("abc/def") + .unwrap() + .matches_with("abc/def", options_require_literal)); + assert!(!Pattern::new("abc?def") + .unwrap() + .matches_with("abc/def", options_require_literal)); + assert!(!Pattern::new("abc*def") + .unwrap() + .matches_with("abc/def", options_require_literal)); + + assert!(Pattern::new("abc/def") + .unwrap() + .matches_with("abc/def", options_not_require_literal)); + assert!(Pattern::new("abc?def") + .unwrap() + .matches_with("abc/def", options_not_require_literal)); + assert!(Pattern::new("abc*def") + .unwrap() + .matches_with("abc/def", options_not_require_literal)); + } + + #[test] + fn test_pattern_matches_require_literal_leading_dot() { + let options_require_literal_leading_dot = MatchOptions { + case_sensitive: true, + require_literal_separator: false, + require_literal_leading_dot: true, + }; + let options_not_require_literal_leading_dot = MatchOptions { + case_sensitive: true, + require_literal_separator: false, + require_literal_leading_dot: false, + }; + + let f = |options| { + Pattern::new("*.txt") + .unwrap() + .matches_with(".hello.txt", options) + }; + assert!(f(options_not_require_literal_leading_dot)); + assert!(!f(options_require_literal_leading_dot)); + + let f = |options| { + Pattern::new(".*.*") + .unwrap() + .matches_with(".hello.txt", options) + }; + assert!(f(options_not_require_literal_leading_dot)); + assert!(f(options_require_literal_leading_dot)); + + let f = |options| { + Pattern::new("aaa/bbb/*") + .unwrap() + .matches_with("aaa/bbb/.ccc", options) + }; + assert!(f(options_not_require_literal_leading_dot)); + assert!(!f(options_require_literal_leading_dot)); + + let f = |options| { + Pattern::new("aaa/bbb/*") + .unwrap() + .matches_with("aaa/bbb/c.c.c.", options) + }; + assert!(f(options_not_require_literal_leading_dot)); + assert!(f(options_require_literal_leading_dot)); + + let f = |options| { + Pattern::new("aaa/bbb/.*") + .unwrap() + .matches_with("aaa/bbb/.ccc", options) + }; + assert!(f(options_not_require_literal_leading_dot)); + assert!(f(options_require_literal_leading_dot)); + + let f = |options| { + Pattern::new("aaa/?bbb") + .unwrap() + .matches_with("aaa/.bbb", options) + }; + assert!(f(options_not_require_literal_leading_dot)); + assert!(!f(options_require_literal_leading_dot)); + + let f = |options| Pattern::new("**/*").unwrap().matches_with(".bbb", options); + assert!(f(options_not_require_literal_leading_dot)); + assert!(!f(options_require_literal_leading_dot)); + } + + #[test] + fn test_matches_path() { + // on windows, (Path::new("a/b").as_str().unwrap() == "a\\b"), so this + // tests that / and \ are considered equivalent on windows + assert!(Pattern::new("a/b").unwrap().matches_path(Path::new("a/b"))); + } + + #[test] + fn test_path_join() { + let pattern = Path::new("one").join(&Path::new("**/*.rs")); + assert!(Pattern::new(pattern.to_str().unwrap()).is_ok()); + } +} diff --git a/crates/nu-json/Cargo.toml b/crates/nu-json/Cargo.toml index 48fdcd3418..103048dd05 100644 --- a/crates/nu-json/Cargo.toml +++ b/crates/nu-json/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors", "Christian Zangl "] +authors = ["The Nushell Project Developers", "Christian Zangl "] description = "Fork of serde-hjson" edition = "2021" license = "MIT" diff --git a/crates/nu-json/LICENSE b/crates/nu-json/LICENSE index e6fee54fa8..95fb62d47e 100644 --- a/crates/nu-json/LICENSE +++ b/crates/nu-json/LICENSE @@ -2,7 +2,7 @@ The MIT License (MIT) Copyright (c) 2014 The Rust Project Developers Copyright (c) 2016 Christian Zangl -Copyright (c) 2020 The Nu Project Contributors +Copyright (c) 2020-2022 The Nushell Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/crates/nu-path/Cargo.toml b/crates/nu-path/Cargo.toml index 74974032a5..7a45b406c4 100644 --- a/crates/nu-path/Cargo.toml +++ b/crates/nu-path/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "Path handling library for Nushell" edition = "2021" license = "MIT" diff --git a/crates/nu-pretty-hex/Cargo.toml b/crates/nu-pretty-hex/Cargo.toml index 8cbeeff71a..db1fb028d0 100644 --- a/crates/nu-pretty-hex/Cargo.toml +++ b/crates/nu-pretty-hex/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["Andrei Volnin ", "The Nu Project Contributors"] +authors = ["Andrei Volnin ", "The Nushell Project Developers"] description = "Pretty hex dump of bytes slice in the common style." edition = "2021" license = "MIT" diff --git a/crates/nu-pretty-hex/LICENSE b/crates/nu-pretty-hex/LICENSE index f70169fa2f..dc3fc3f1fa 100644 --- a/crates/nu-pretty-hex/LICENSE +++ b/crates/nu-pretty-hex/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2018 Andrei Volnin +Copyright (c) 2021-2022 The Nushell Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/nu-system/Cargo.toml b/crates/nu-system/Cargo.toml index 1fbdee5d68..cffef96b9e 100644 --- a/crates/nu-system/Cargo.toml +++ b/crates/nu-system/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors", "procs creators"] +authors = ["The Nushell Project Developers", "procs creators"] description = "Nushell system querying" name = "nu-system" version = "0.59.1" diff --git a/crates/nu-system/LICENSE b/crates/nu-system/LICENSE index e0e33baa83..e51a65a340 100644 --- a/crates/nu-system/LICENSE +++ b/crates/nu-system/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 procs developers and Nushell developers +Copyright (c) 2022 procs developers and The Nushell Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index 9cc83bc2da..b5b3e8597f 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "Nushell table printing" edition = "2021" license = "MIT" diff --git a/crates/nu-term-grid/Cargo.toml b/crates/nu-term-grid/Cargo.toml index 96e6be5ede..1102b65751 100644 --- a/crates/nu-term-grid/Cargo.toml +++ b/crates/nu-term-grid/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "Nushell grid printing" edition = "2021" license = "MIT" diff --git a/crates/nu-test-support/Cargo.toml b/crates/nu-test-support/Cargo.toml index e9aa1585e7..469b8a84e4 100644 --- a/crates/nu-test-support/Cargo.toml +++ b/crates/nu-test-support/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "Support for writing Nushell tests" edition = "2018" license = "MIT" @@ -12,11 +12,11 @@ doctest = false [dependencies] nu-path = { path="../nu-path", version = "0.59.1" } nu-protocol = { path="../nu-protocol", version = "0.59.1" } +nu-glob = { path = "../nu-glob", version = "0.59.1" } bigdecimal = { package = "bigdecimal", version = "0.3.0", features = ["serde"] } chrono = "0.4.19" getset = "0.1.1" -glob = "0.3.0" indexmap = { version="1.6.1", features=["serde-1"] } num-bigint = { version="0.4.3", features=["serde"] } tempfile = "3.2.0" diff --git a/crates/nu-test-support/src/playground/play.rs b/crates/nu-test-support/src/playground/play.rs index 2129352913..29765ccfb1 100644 --- a/crates/nu-test-support/src/playground/play.rs +++ b/crates/nu-test-support/src/playground/play.rs @@ -2,7 +2,7 @@ use super::Director; use crate::fs; use crate::fs::Stub; use getset::Getters; -use glob::glob; +use nu_glob::glob; use std::path::{Path, PathBuf}; use std::str; use tempfile::{tempdir, TempDir}; diff --git a/crates/nu_plugin_example/Cargo.toml b/crates/nu_plugin_example/Cargo.toml index 72c8545804..3cda62a85a 100644 --- a/crates/nu_plugin_example/Cargo.toml +++ b/crates/nu_plugin_example/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "A version incrementer plugin for Nushell" edition = "2021" license = "MIT" diff --git a/crates/nu_plugin_gstat/Cargo.toml b/crates/nu_plugin_gstat/Cargo.toml index ed66e083e8..c8e139fe7a 100644 --- a/crates/nu_plugin_gstat/Cargo.toml +++ b/crates/nu_plugin_gstat/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "A git status plugin for Nushell" edition = "2021" license = "MIT" diff --git a/crates/nu_plugin_inc/Cargo.toml b/crates/nu_plugin_inc/Cargo.toml index ba3bd3e084..5ec9066e5a 100644 --- a/crates/nu_plugin_inc/Cargo.toml +++ b/crates/nu_plugin_inc/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "A version incrementer plugin for Nushell" edition = "2021" license = "MIT" diff --git a/crates/nu_plugin_query/Cargo.toml b/crates/nu_plugin_query/Cargo.toml index e7cc7823a4..9adfda6311 100644 --- a/crates/nu_plugin_query/Cargo.toml +++ b/crates/nu_plugin_query/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "A set of query commands for Nushell" edition = "2021" license = "MIT" diff --git a/crates/old/nu_plugin_chart/Cargo.toml b/crates/old/nu_plugin_chart/Cargo.toml index 7721e6970e..3b2b055853 100644 --- a/crates/old/nu_plugin_chart/Cargo.toml +++ b/crates/old/nu_plugin_chart/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "A plugin to display charts" edition = "2018" license = "MIT" diff --git a/crates/old/nu_plugin_from_bson/Cargo.toml b/crates/old/nu_plugin_from_bson/Cargo.toml index 3684e19e55..18623796f6 100644 --- a/crates/old/nu_plugin_from_bson/Cargo.toml +++ b/crates/old/nu_plugin_from_bson/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "A converter plugin to the bson format for Nushell" edition = "2018" license = "MIT" diff --git a/crates/old/nu_plugin_from_mp4/Cargo.toml b/crates/old/nu_plugin_from_mp4/Cargo.toml index 0a702b3b7c..d2f902dbf2 100644 --- a/crates/old/nu_plugin_from_mp4/Cargo.toml +++ b/crates/old/nu_plugin_from_mp4/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "A converter plugin to the mp4 format for Nushell" edition = "2018" license = "MIT" diff --git a/crates/old/nu_plugin_from_sqlite/Cargo.toml b/crates/old/nu_plugin_from_sqlite/Cargo.toml index b4f80565f4..9e7c387433 100644 --- a/crates/old/nu_plugin_from_sqlite/Cargo.toml +++ b/crates/old/nu_plugin_from_sqlite/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "A converter plugin to the bson format for Nushell" edition = "2018" license = "MIT" diff --git a/crates/old/nu_plugin_s3/Cargo.toml b/crates/old/nu_plugin_s3/Cargo.toml index 37bf15e00d..f3cce52c9d 100644 --- a/crates/old/nu_plugin_s3/Cargo.toml +++ b/crates/old/nu_plugin_s3/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "An S3 plugin for Nushell" edition = "2018" license = "MIT" diff --git a/crates/old/nu_plugin_start/Cargo.toml b/crates/old/nu_plugin_start/Cargo.toml index ce8a7cfda1..8871a4b0a1 100644 --- a/crates/old/nu_plugin_start/Cargo.toml +++ b/crates/old/nu_plugin_start/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "A plugin to open files/URLs directly from Nushell" edition = "2018" license = "MIT" diff --git a/crates/old/nu_plugin_start/src/start.rs b/crates/old/nu_plugin_start/src/start.rs index 068c2abb33..cae6bebe03 100644 --- a/crates/old/nu_plugin_start/src/start.rs +++ b/crates/old/nu_plugin_start/src/start.rs @@ -43,7 +43,7 @@ impl Start { fn glob_to_values(&self, value: &Value) -> Result>, ShellError> { let mut result = vec![]; - match glob::glob(&value.as_string()?) { + match nu_glob::glob(&value.as_string()?) { Ok(paths) => { for path_result in paths { match path_result { diff --git a/crates/old/nu_plugin_to_bson/Cargo.toml b/crates/old/nu_plugin_to_bson/Cargo.toml index a9a75652c4..45abe79d9a 100644 --- a/crates/old/nu_plugin_to_bson/Cargo.toml +++ b/crates/old/nu_plugin_to_bson/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "A converter plugin to the bson format for Nushell" edition = "2018" license = "MIT" diff --git a/crates/old/nu_plugin_to_sqlite/Cargo.toml b/crates/old/nu_plugin_to_sqlite/Cargo.toml index ca8432532a..b67389fa4d 100644 --- a/crates/old/nu_plugin_to_sqlite/Cargo.toml +++ b/crates/old/nu_plugin_to_sqlite/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "A converter plugin to the SQLite format for Nushell" edition = "2018" license = "MIT" diff --git a/crates/old/nu_plugin_tree/Cargo.toml b/crates/old/nu_plugin_tree/Cargo.toml index f5b845e83f..c3c0958273 100644 --- a/crates/old/nu_plugin_tree/Cargo.toml +++ b/crates/old/nu_plugin_tree/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "Tree viewer plugin for Nushell" edition = "2018" license = "MIT" diff --git a/samples/wasm/LICENSE_MIT b/samples/wasm/LICENSE_MIT index 6fbb158478..c442137c92 100644 --- a/samples/wasm/LICENSE_MIT +++ b/samples/wasm/LICENSE_MIT @@ -1,4 +1,4 @@ -Copyright (c) 2018 Jonathan turner +Copyright (c) 2019-2022 The Nushell Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/tests/fixtures/formats/cargo_sample.toml b/tests/fixtures/formats/cargo_sample.toml index c36a40e0ad..a1bb219c67 100644 --- a/tests/fixtures/formats/cargo_sample.toml +++ b/tests/fixtures/formats/cargo_sample.toml @@ -1,7 +1,7 @@ [package] name = "nu" version = "0.1.1" -authors = ["The Nu Project Contributors"] +authors = ["The Nushell Project Developers"] description = "a new type of shell" license = "ISC" edition = "2018" diff --git a/wix/main.wxs b/wix/main.wxs index bc2030ec4d..a5013c8054 100644 --- a/wix/main.wxs +++ b/wix/main.wxs @@ -35,7 +35,7 @@ Id='*' Name='nu' UpgradeCode='82D756D2-19FA-4F09-B10F-64942E89F364' - Manufacturer='The Nu Project Contributors' + Manufacturer='The Nushell Project Developers' Language='1033' Codepage='1252' Version='$(var.Version)'> @@ -43,7 +43,7 @@