Merge remote-tracking branch 'origin/upstream' am: a81a9f4598 am: 6ee9851196 am: 924274b74d

Original change: undetermined

Change-Id: I216aa4dbbc79ea87f05a29ed2e273cf5d4d9b2b0
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..d9469a7
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "b333eaccb707d862feb45d5202de63ff26b77839"
+  },
+  "path_in_vcs": ""
+}
\ No newline at end of file
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..2df15a2
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,33 @@
+// This file is generated by cargo_embargo.
+// Do not modify this file as changes will be overridden on upgrade.
+
+rust_library {
+    name: "libtermtree",
+    host_supported: true,
+    crate_name: "termtree",
+    cargo_env_compat: true,
+    cargo_pkg_version: "0.4.1",
+    srcs: ["src/lib.rs"],
+    edition: "2018",
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
+    product_available: true,
+    vendor_available: true,
+}
+
+rust_test {
+    name: "termtree_test_src_lib",
+    host_supported: true,
+    crate_name: "termtree",
+    cargo_env_compat: true,
+    cargo_pkg_version: "0.4.1",
+    srcs: ["src/lib.rs"],
+    test_suites: ["general-tests"],
+    auto_gen_config: true,
+    test_options: {
+        unit_test: true,
+    },
+    edition: "2018",
+}
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..69736c1
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "termtree"
+version = "0.4.1"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..f0e5f2b
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,72 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+name = "termtree"
+version = "0.4.1"
+include = [
+    "src/**/*",
+    "Cargo.toml",
+    "LICENSE*",
+    "README.md",
+    "examples/**/*",
+]
+description = "Visualize tree-like data on the command-line"
+homepage = "https://github.com/rust-cli/termtree"
+documentation = "https://docs.rs/termtree"
+readme = "README.md"
+keywords = [
+    "cli",
+    "tree",
+    "dag",
+]
+categories = [
+    "command-line-interface",
+    "visualization",
+]
+license = "MIT"
+repository = "https://github.com/rust-cli/termtree"
+
+[[package.metadata.release.pre-release-replacements]]
+file = "CHANGELOG.md"
+search = "Unreleased"
+replace = "{{version}}"
+min = 1
+
+[[package.metadata.release.pre-release-replacements]]
+file = "CHANGELOG.md"
+search = '\.\.\.HEAD'
+replace = "...{{tag_name}}"
+exactly = 1
+
+[[package.metadata.release.pre-release-replacements]]
+file = "CHANGELOG.md"
+search = "ReleaseDate"
+replace = "{{date}}"
+min = 1
+
+[[package.metadata.release.pre-release-replacements]]
+file = "CHANGELOG.md"
+search = "<!-- next-header -->"
+replace = """
+<!-- next-header -->
+## [Unreleased] - ReleaseDate
+"""
+exactly = 1
+
+[[package.metadata.release.pre-release-replacements]]
+file = "CHANGELOG.md"
+search = "<!-- next-url -->"
+replace = """
+<!-- next-url -->
+[Unreleased]: https://github.com/rust-cli/termtree/compare/{{tag_name}}...HEAD"""
+exactly = 1
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d5fcce5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2017 Doug Tangren
+
+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.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..8bc6904
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,19 @@
+name: "termtree"
+description: "Visualize tree-like data on the command-line"
+third_party {
+  identifier {
+    type: "crates.io"
+    value: "https://crates.io/crates/termtree"
+  }
+  identifier {
+    type: "Archive"
+    value: "https://static.crates.io/crates/termtree/termtree-0.4.1.crate"
+  }
+  version: "0.4.1"
+  license_type: NOTICE
+  last_upgrade_date {
+    year: 2023
+    month: 11
+    day: 6
+  }
+}
diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_MIT
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..48bea6e
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 688011
+include platform/prebuilts/rust:main:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..71d4594
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+# termtree [![Main](https://github.com/rust-cli/termtree/actions/workflows/main.yml/badge.svg)](https://github.com/rust-cli/termtree/actions/workflows/main.yml)
+
+> Visualize tree-like data on the command-line
+
+[API documentation](https://docs.rs/termtree)
+
+## Example
+
+An example program is provided under the "examples" directory to mimic the `tree(1)`
+linux program
+
+```bash
+$ cargo run --example tree target
+    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
+     Running `target/debug/examples/tree target`
+target
+└── debug
+    ├── .cargo-lock
+    ├── .fingerprint
+    |   └── termtree-21a5bdbd42e0b6da
+    |       ├── dep-example-tree
+    |       ├── dep-lib-termtree
+    |       ├── example-tree
+    |       ├── example-tree.json
+    |       ├── lib-termtree
+    |       └── lib-termtree.json
+    ├── build
+    ├── deps
+    |   └── libtermtree.rlib
+    ├── examples
+    |   ├── tree
+    |   └── tree.dSYM
+    |       └── Contents
+    |           ├── Info.plist
+    |           └── Resources
+    |               └── DWARF
+    |                   └── tree
+    ├── libtermtree.rlib
+    └── native
+```
+
+## Related Crates
+
+- [`treeline`](https://crates.io/crates/treeline): termtree was forked from this.
+- [`tree_decorator`](https://crates.io/crates/tree_decorator)
+- [`xtree`](https://crates.io/crates/xtree)
+- [`ptree`](https://crates.io/crates/ptree)
+
+## License
+
+Licensed under MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
diff --git a/cargo_embargo.json b/cargo_embargo.json
new file mode 100644
index 0000000..d40889a
--- /dev/null
+++ b/cargo_embargo.json
@@ -0,0 +1,4 @@
+{
+    "run_cargo": false,
+    "tests": true
+}
diff --git a/examples/tree.rs b/examples/tree.rs
new file mode 100644
index 0000000..0a9bca3
--- /dev/null
+++ b/examples/tree.rs
@@ -0,0 +1,32 @@
+use termtree::Tree;
+
+use std::path::Path;
+use std::{env, fs, io};
+
+fn label<P: AsRef<Path>>(p: P) -> String {
+    p.as_ref().file_name().unwrap().to_str().unwrap().to_owned()
+}
+
+fn tree<P: AsRef<Path>>(p: P) -> io::Result<Tree<String>> {
+    let result = fs::read_dir(&p)?.filter_map(|e| e.ok()).fold(
+        Tree::new(label(p.as_ref().canonicalize()?)),
+        |mut root, entry| {
+            let dir = entry.metadata().unwrap();
+            if dir.is_dir() {
+                root.push(tree(entry.path()).unwrap());
+            } else {
+                root.push(Tree::new(label(entry.path())));
+            }
+            root
+        },
+    );
+    Ok(result)
+}
+
+fn main() {
+    let dir = env::args().nth(1).unwrap_or_else(|| String::from("."));
+    match tree(dir) {
+        Ok(tree) => println!("{}", tree),
+        Err(err) => println!("error: {}", err),
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..7893d6d
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,210 @@
+#![allow(clippy::branches_sharing_code)]
+
+#[cfg(test)]
+mod tests;
+
+use std::collections::VecDeque;
+use std::fmt::{self, Display};
+use std::rc::Rc;
+
+/// a simple recursive type which is able to render its
+/// components in a tree-like format
+#[derive(Debug, Clone)]
+pub struct Tree<D: Display> {
+    pub root: D,
+    pub leaves: Vec<Tree<D>>,
+    multiline: bool,
+    glyphs: GlyphPalette,
+}
+
+impl<D: Display> Tree<D> {
+    pub fn new(root: D) -> Self {
+        Tree {
+            root,
+            leaves: Vec::new(),
+            multiline: false,
+            glyphs: GlyphPalette::new(),
+        }
+    }
+
+    pub fn with_leaves(mut self, leaves: impl IntoIterator<Item = impl Into<Tree<D>>>) -> Self {
+        self.leaves = leaves.into_iter().map(Into::into).collect();
+        self
+    }
+
+    /// Ensure all lines for `root` are indented
+    pub fn with_multiline(mut self, yes: bool) -> Self {
+        self.multiline = yes;
+        self
+    }
+
+    /// Customize the rendering of this node
+    pub fn with_glyphs(mut self, glyphs: GlyphPalette) -> Self {
+        self.glyphs = glyphs;
+        self
+    }
+}
+
+impl<D: Display> Tree<D> {
+    /// Ensure all lines for `root` are indented
+    pub fn set_multiline(&mut self, yes: bool) -> &mut Self {
+        self.multiline = yes;
+        self
+    }
+
+    /// Customize the rendering of this node
+    pub fn set_glyphs(&mut self, glyphs: GlyphPalette) -> &mut Self {
+        self.glyphs = glyphs;
+        self
+    }
+}
+
+impl<D: Display> Tree<D> {
+    pub fn push(&mut self, leaf: impl Into<Tree<D>>) -> &mut Self {
+        self.leaves.push(leaf.into());
+        self
+    }
+}
+
+impl<D: Display> From<D> for Tree<D> {
+    fn from(inner: D) -> Self {
+        Self::new(inner)
+    }
+}
+
+impl<D: Display> Extend<D> for Tree<D> {
+    fn extend<T: IntoIterator<Item = D>>(&mut self, iter: T) {
+        self.leaves.extend(iter.into_iter().map(Into::into))
+    }
+}
+
+impl<D: Display> Extend<Tree<D>> for Tree<D> {
+    fn extend<T: IntoIterator<Item = Tree<D>>>(&mut self, iter: T) {
+        self.leaves.extend(iter)
+    }
+}
+
+impl<D: Display> Display for Tree<D> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.root.fmt(f)?; // Pass along `f.alternate()`
+        writeln!(f)?;
+        let mut queue = DisplauQueue::new();
+        let no_space = Rc::new(Vec::new());
+        enqueue_leaves(&mut queue, self, no_space);
+        while let Some((last, leaf, spaces)) = queue.pop_front() {
+            let mut prefix = (
+                if last {
+                    leaf.glyphs.last_item
+                } else {
+                    leaf.glyphs.middle_item
+                },
+                leaf.glyphs.item_indent,
+            );
+
+            if leaf.multiline {
+                let rest_prefix = (
+                    if last {
+                        leaf.glyphs.last_skip
+                    } else {
+                        leaf.glyphs.middle_skip
+                    },
+                    leaf.glyphs.skip_indent,
+                );
+                debug_assert_eq!(prefix.0.chars().count(), rest_prefix.0.chars().count());
+                debug_assert_eq!(prefix.1.chars().count(), rest_prefix.1.chars().count());
+
+                let root = if f.alternate() {
+                    format!("{:#}", leaf.root)
+                } else {
+                    format!("{:}", leaf.root)
+                };
+                for line in root.lines() {
+                    // print single line
+                    for s in spaces.as_slice() {
+                        if *s {
+                            self.glyphs.last_skip.fmt(f)?;
+                            self.glyphs.skip_indent.fmt(f)?;
+                        } else {
+                            self.glyphs.middle_skip.fmt(f)?;
+                            self.glyphs.skip_indent.fmt(f)?;
+                        }
+                    }
+                    prefix.0.fmt(f)?;
+                    prefix.1.fmt(f)?;
+                    line.fmt(f)?;
+                    writeln!(f)?;
+                    prefix = rest_prefix;
+                }
+            } else {
+                // print single line
+                for s in spaces.as_slice() {
+                    if *s {
+                        self.glyphs.last_skip.fmt(f)?;
+                        self.glyphs.skip_indent.fmt(f)?;
+                    } else {
+                        self.glyphs.middle_skip.fmt(f)?;
+                        self.glyphs.skip_indent.fmt(f)?;
+                    }
+                }
+                prefix.0.fmt(f)?;
+                prefix.1.fmt(f)?;
+                leaf.root.fmt(f)?; // Pass along `f.alternate()`
+                writeln!(f)?;
+            }
+
+            // recurse
+            if !leaf.leaves.is_empty() {
+                let s: &Vec<bool> = &spaces;
+                let mut child_spaces = s.clone();
+                child_spaces.push(last);
+                let child_spaces = Rc::new(child_spaces);
+                enqueue_leaves(&mut queue, leaf, child_spaces);
+            }
+        }
+        Ok(())
+    }
+}
+
+type DisplauQueue<'t, D> = VecDeque<(bool, &'t Tree<D>, Rc<Vec<bool>>)>;
+
+fn enqueue_leaves<'t, D: Display>(
+    queue: &mut DisplauQueue<'t, D>,
+    parent: &'t Tree<D>,
+    spaces: Rc<Vec<bool>>,
+) {
+    for (i, leaf) in parent.leaves.iter().rev().enumerate() {
+        let last = i == 0;
+        queue.push_front((last, leaf, spaces.clone()));
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub struct GlyphPalette {
+    pub middle_item: &'static str,
+    pub last_item: &'static str,
+    pub item_indent: &'static str,
+
+    pub middle_skip: &'static str,
+    pub last_skip: &'static str,
+    pub skip_indent: &'static str,
+}
+
+impl GlyphPalette {
+    pub const fn new() -> Self {
+        Self {
+            middle_item: "├",
+            last_item: "└",
+            item_indent: "── ",
+
+            middle_skip: "│",
+            last_skip: " ",
+            skip_indent: "   ",
+        }
+    }
+}
+
+impl Default for GlyphPalette {
+    fn default() -> Self {
+        Self::new()
+    }
+}
diff --git a/src/tests.rs b/src/tests.rs
new file mode 100644
index 0000000..4d60cf1
--- /dev/null
+++ b/src/tests.rs
@@ -0,0 +1,48 @@
+use super::*;
+
+#[test]
+fn render_tree_root() {
+    let tree = Tree::new("foo");
+    assert_eq!(format!("{}", tree), "foo\n")
+}
+
+#[test]
+fn render_tree_with_leaves() {
+    let tree = Tree::new("foo").with_leaves([Tree::new("bar").with_leaves(["baz"])]);
+    assert_eq!(
+        format!("{}", tree),
+        r#"foo
+└── bar
+    └── baz
+"#
+    )
+}
+
+#[test]
+fn render_tree_with_multiple_leaves() {
+    let tree = Tree::new("foo").with_leaves(["bar", "baz"]);
+    assert_eq!(
+        format!("{}", tree),
+        r#"foo
+├── bar
+└── baz
+"#
+    )
+}
+
+#[test]
+fn render_tree_with_multiline_leaf() {
+    let tree = Tree::new("foo").with_leaves([
+        Tree::new("hello\nworld").with_multiline(true),
+        Tree::new("goodbye\nworld").with_multiline(true),
+    ]);
+    assert_eq!(
+        format!("{}", tree),
+        r#"foo
+├── hello
+│   world
+└── goodbye
+    world
+"#
+    )
+}