h3o_cli/commands/
index_decode.rs

1//! Expose index's components
2
3use crate::index::Index;
4use anyhow::{Context, Result as AnyResult};
5use clap::{Parser, ValueEnum};
6use either::Either;
7use h3o::{BaseCell, Direction, Edge, IndexMode, Resolution, Vertex};
8use serde::Serialize;
9
10/// Decode h3o indexes into components
11#[derive(Parser, Debug)]
12pub struct Args {
13    /// h3o index.
14    #[arg(short, long)]
15    index: Option<Index>,
16
17    /// Output format.
18    #[arg(short, long, value_enum, default_value_t = Format::Text)]
19    format: Format,
20
21    /// Prettify the output.
22    #[arg(short, long, default_value_t = false)]
23    pretty: bool,
24}
25
26#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]
27enum Format {
28    Text,
29    Json,
30}
31
32/// Run the `indexDecode` command.
33pub fn run(args: &Args) -> AnyResult<()> {
34    let components = args
35        .index
36        .map_or_else(
37            || Either::Right(crate::io::read_indexes()),
38            |index| Either::Left(std::iter::once(Ok(index))),
39        )
40        .map(|input| input.map(Components::from));
41
42    match args.format {
43        Format::Text => {
44            if args.pretty {
45                components_to_pretty(components)
46            } else {
47                components_to_compact(components)
48            }
49        }
50        Format::Json => components_to_json(components, args.pretty),
51    }
52    .context("indexDecode")?;
53
54    Ok(())
55}
56
57fn components_to_compact(
58    components: impl IntoIterator<Item = AnyResult<Components>>,
59) -> AnyResult<()> {
60    for component in components {
61        let component = component?;
62        let mode = u8::from(component.mode);
63        let resolution = u8::from(component.resolution);
64        let base_cell = u8::from(component.base_cell);
65        let directions = component
66            .directions
67            .iter()
68            .map(ToString::to_string)
69            .collect::<String>();
70
71        component.custom.as_ref().map_or_else(
72            || {
73                println!("{mode}:{resolution}:{base_cell}:{directions}");
74            },
75            |field| {
76                println!(
77                    "{mode}:{}:{resolution}:{base_cell}:{directions}",
78                    u8::from(*field),
79                );
80            },
81        );
82    }
83
84    Ok(())
85}
86
87fn components_to_pretty(
88    components: impl IntoIterator<Item = AnyResult<Components>>,
89) -> AnyResult<()> {
90    for component in components {
91        let component = component?;
92
93        println!("╔════════════╗");
94        println!("║ h3o Index  ║ {}", component.index);
95        println!("╠════════════╣");
96        println!(
97            "║ Mode       ║ {} ({})",
98            component.mode,
99            u8::from(component.mode)
100        );
101        println!("║ Resolution ║ {}", component.resolution);
102        match component.custom {
103            Some(CustomField::Edge(edge)) => println!("║ Edge       ║ {edge}"),
104            Some(CustomField::Vertex(vertex)) => {
105                println!("║ Vertex     ║ {vertex}");
106            }
107            _ => (),
108        }
109        println!("║ Base Cell  ║ {}", component.base_cell);
110        for (i, direction) in component.directions.iter().enumerate() {
111            println!("║ Child {:>2}   ║ {direction} ({direction:?})", i + 1);
112        }
113        println!("╚════════════╝");
114    }
115
116    Ok(())
117}
118
119fn components_to_json(
120    components: impl IntoIterator<Item = AnyResult<Components>>,
121    pretty: bool,
122) -> AnyResult<()> {
123    let components = components.into_iter().collect::<AnyResult<Vec<_>>>()?;
124
125    crate::json::print(&components, pretty)
126}
127
128// -----------------------------------------------------------------------------
129
130#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
131#[serde(rename_all = "camelCase")]
132struct Components {
133    index: String,
134    mode: IndexMode,
135    #[serde(flatten)]
136    custom: Option<CustomField>,
137    resolution: Resolution,
138    base_cell: BaseCell,
139    directions: Vec<Direction>,
140}
141
142impl From<Index> for Components {
143    fn from(value: Index) -> Self {
144        let (index, mode, custom, cell) = match value {
145            Index::Cell(index) => {
146                (index.to_string(), IndexMode::Cell, None, index)
147            }
148            Index::DirectedEdge(index) => (
149                index.to_string(),
150                IndexMode::DirectedEdge,
151                Some(CustomField::Edge(index.edge())),
152                index.origin(),
153            ),
154            Index::Vertex(index) => (
155                index.to_string(),
156                IndexMode::Vertex,
157                Some(CustomField::Vertex(index.vertex())),
158                index.owner(),
159            ),
160        };
161
162        Self {
163            index,
164            mode,
165            custom,
166            resolution: cell.resolution(),
167            base_cell: cell.base_cell(),
168            directions: Resolution::range(Resolution::One, cell.resolution())
169                .map(|resolution| {
170                    cell.direction_at(resolution).expect("direction")
171                })
172                .collect(),
173        }
174    }
175}
176
177#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize)]
178#[serde(rename_all = "lowercase")]
179enum CustomField {
180    Edge(Edge),
181    Vertex(Vertex),
182}
183
184impl From<CustomField> for u8 {
185    fn from(value: CustomField) -> Self {
186        match value {
187            CustomField::Edge(edge) => edge.into(),
188            CustomField::Vertex(vertex) => vertex.into(),
189        }
190    }
191}