1use 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#[derive(Parser, Debug)]
12pub struct Args {
13 #[arg(short, long)]
15 index: Option<Index>,
16
17 #[arg(short, long, value_enum, default_value_t = Format::Text)]
19 format: Format,
20
21 #[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
32pub 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#[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}