From fe48f5aab6364be704c34fbbde1d34fb3ff477c7 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 15 Feb 2022 21:28:54 -0800 Subject: [PATCH 01/18] Copy generate_container_preview as generate_container_count This just prints the container's number of children, while keeping the opening/closing characters. So: foo: [ 2 ] bar: { 3 } --- src/lineprinter.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/lineprinter.rs b/src/lineprinter.rs index d2df366..41068d4 100644 --- a/src/lineprinter.rs +++ b/src/lineprinter.rs @@ -796,6 +796,78 @@ impl<'a, 'b, 'c> LinePrinter<'a, 'b, 'c> { Ok(num_printed) } + // similar to generate_container_preview, except it only prints the count + // of the number of children: + // - number of key-val pairs in a hash, ie "{ 3 }" + // - number of items in an array, ie "[ 2 ]" + fn generate_container_count( + &mut self, + flatjson: &FlatJson, + row: &Row, + mut available_space: isize, + quoted_object_keys: bool, + ) -> Result { + debug_assert!(row.is_opening_of_container()); + + // Minimum amount of space required == 3: […] + if available_space < 3 { + return Ok(0); + } + + let container_type = row.value.container_type().unwrap(); + let mut num_printed = 0; + + // Print opening character, either "[" or "{" + self.highlight_str( + container_type.open_str(), + Some(self.value_range.start), + highlighting::PREVIEW_STYLES, + )?; + + num_printed += 1; + + let mut next_sibling = row.first_child(); + let mut count:usize = 0; + while let OptionIndex::Index(child) = next_sibling { + next_sibling = flatjson[child].next_sibling; + count += 1; + } + + // Print count with spaces around it: " N " + self.highlight_str( + " ", + Some(self.value_range.start+num_printed), + highlighting::PREVIEW_STYLES + )?; + num_printed += 1; + + let count_as_str = count.to_string(); + let count_length = count_as_str.len(); + self.highlight_str( + &count_as_str, + Some(self.value_range.start+num_printed), + highlighting::PREVIEW_STYLES + )?; + num_printed += count_length; + + self.highlight_str( + " ", + Some(self.value_range.start+num_printed), + highlighting::PREVIEW_STYLES + )?; + num_printed += 1; + + // Print closing char, "]" or "}" + self.highlight_str( + container_type.close_str(), + Some(self.value_range.end - 1), + highlighting::PREVIEW_STYLES, + )?; + num_printed += 1; + + Ok(num_printed) + } + // {a…: …, …} // // [a, …] From 6eedd029af27358fe67c22d6ed29ec167183c404 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 15 Feb 2022 21:09:20 -0800 Subject: [PATCH 02/18] Fix `cargo build` error ``` $ cargo build Compiling jless v0.7.1 (/...) error[E0308]: mismatched types --> src/lineprinter.rs:765:12 | 765 | Ok(num_printed) | ^^^^^^^^^^^ expected `isize`, found `usize` | help: you can convert a `usize` to an `isize` and panic if the converted value doesn't fit | 765 | Ok(num_printed.try_into().unwrap()) | ++++++++++++++++++++ ``` I'm not sure why this error only showed up after my changes. I haven't changed the type of `num_printed`, and I've only changed it by calling `+=` on it a few extra times. --- src/lineprinter.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lineprinter.rs b/src/lineprinter.rs index 41068d4..8e4bb73 100644 --- a/src/lineprinter.rs +++ b/src/lineprinter.rs @@ -2,6 +2,7 @@ use std::collections::hash_map::Entry; use std::fmt; use std::iter::Peekable; use std::ops::Range; +use std::convert::TryInto; use regex::Regex; @@ -804,8 +805,7 @@ impl<'a, 'b, 'c> LinePrinter<'a, 'b, 'c> { &mut self, flatjson: &FlatJson, row: &Row, - mut available_space: isize, - quoted_object_keys: bool, + mut available_space: isize ) -> Result { debug_assert!(row.is_opening_of_container()); @@ -865,7 +865,7 @@ impl<'a, 'b, 'c> LinePrinter<'a, 'b, 'c> { )?; num_printed += 1; - Ok(num_printed) + Ok(num_printed.try_into().unwrap()) } // {a…: …, …} From 8c82925088de6c14c1512fd599599f662db1c85d Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 15 Feb 2022 21:52:51 -0800 Subject: [PATCH 03/18] Add `JsonViewer.preview`, and have `fill_in_container_preview` read it This is modeled exactly after the existing `JsonViewer.mode`, down to its arg handling and toggling. --- src/app.rs | 3 ++- src/lineprinter.rs | 36 +++++++++++++++++++++++------------- src/options.rs | 9 +++++++++ src/screenwriter.rs | 1 + src/viewer.rs | 21 ++++++++++++++++++++- 5 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/app.rs b/src/app.rs index 2ad2f01..ef36acc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -49,7 +49,7 @@ impl App { Err(err) => return Err(format!("Unable to parse input: {:?}", err)), }; - let mut viewer = JsonViewer::new(flatjson, opt.mode); + let mut viewer = JsonViewer::new(flatjson, opt.mode, opt.preview); viewer.scrolloff_setting = opt.scrolloff; let screen_writer = @@ -233,6 +233,7 @@ impl App { Key::Char('G') | Key::End => Some(Action::FocusBottom), Key::Char('%') => Some(Action::FocusMatchingPair), Key::Char('m') => Some(Action::ToggleMode), + Key::Char('p') => Some(Action::TogglePreview), Key::Char('<') => { self.screen_writer .decrease_indentation_level(self.viewer.flatjson.2 as u16); diff --git a/src/lineprinter.rs b/src/lineprinter.rs index 8e4bb73..5fcda59 100644 --- a/src/lineprinter.rs +++ b/src/lineprinter.rs @@ -13,6 +13,7 @@ use crate::terminal; use crate::terminal::{Color, Style, Terminal}; use crate::truncatedstrview::TruncatedStrView; use crate::viewer::Mode; +use crate::viewer::Preview; // # Printing out individual lines // @@ -195,6 +196,7 @@ pub enum LineValue<'a> { pub struct LinePrinter<'a, 'b, 'c> { pub mode: Mode, + pub preview: Preview, pub terminal: &'c mut dyn Terminal, pub node_depth: usize, @@ -685,23 +687,31 @@ impl<'a, 'b, 'c> LinePrinter<'a, 'b, 'c> { available_space -= 1; } - let quoted_object_keys = self.mode == Mode::Line; - let mut used_space = - self.generate_container_preview(flatjson, row, available_space, quoted_object_keys)?; + let mut used_space = 0; + + if self.preview == Preview::Full { + let quoted_object_keys = self.mode == Mode::Line; + used_space = + self.generate_container_preview(flatjson, row, available_space, quoted_object_keys)?; - if self.trailing_comma { - used_space += 1; if self.trailing_comma { - self.highlight_str( - ",", - Some(self.value_range.end), - ( - &highlighting::DEFAULT_STYLE, - &highlighting::SEARCH_MATCH_HIGHLIGHTED, - ), - )?; + used_space += 1; + if self.trailing_comma { + self.highlight_str( + ",", + Some(self.value_range.end), + ( + &highlighting::DEFAULT_STYLE, + &highlighting::SEARCH_MATCH_HIGHLIGHTED, + ), + )?; + } } } + else { + used_space = + self.generate_container_count(flatjson, row, available_space)?; + } Ok(used_space) } diff --git a/src/options.rs b/src/options.rs index eba1dbb..2cee8c0 100644 --- a/src/options.rs +++ b/src/options.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use clap::Parser; use crate::viewer::Mode; +use crate::viewer::Preview; /// A pager for JSON data #[derive(Debug, Parser)] @@ -21,6 +22,14 @@ pub struct Opt { #[clap(short, long, arg_enum, hide_possible_values = true, default_value_t = Mode::Data)] pub mode: Mode, + /// Initial preview of container nodes. In full mode (--preview full; + /// the default), containers will be rendered as much as they can be in + /// the width of the terminal. In count mode (--preview count), only + /// the child node count will be rendered. This can be toggled by + /// pressing 'p'. + #[clap(short, long, arg_enum, hide_possible_values = true, default_value_t = Preview::Full)] + pub preview: Preview, + /// Number of lines to maintain as padding between the currently /// focused row and the top or bottom of the screen. Setting this to /// a large value will keep the focused in the middle of the screen diff --git a/src/screenwriter.rs b/src/screenwriter.rs index 7a5e0dd..a040a26 100644 --- a/src/screenwriter.rs +++ b/src/screenwriter.rs @@ -275,6 +275,7 @@ impl ScreenWriter { let mut line = lp::LinePrinter { mode: viewer.mode, + preview: viewer.preview, terminal: &mut self.terminal, node_depth: row.depth, diff --git a/src/viewer.rs b/src/viewer.rs index 2fed3b8..b046b70 100644 --- a/src/viewer.rs +++ b/src/viewer.rs @@ -9,6 +9,12 @@ pub enum Mode { Data, } +#[derive(PartialEq, Eq, Copy, Clone, Debug, ArgEnum)] +pub enum Preview { + Full, + Count, +} + const DEFAULT_SCROLLOFF: u16 = 3; pub struct JsonViewer { @@ -26,10 +32,11 @@ pub struct JsonViewer { // Access the functional value via .scrolloff(). pub scrolloff_setting: u16, pub mode: Mode, + pub preview: Preview, } impl JsonViewer { - pub fn new(flatjson: FlatJson, mode: Mode) -> JsonViewer { + pub fn new(flatjson: FlatJson, mode: Mode, preview: Preview) -> JsonViewer { JsonViewer { flatjson, top_row: 0, @@ -38,6 +45,7 @@ impl JsonViewer { dimensions: TTYDimensions::default(), scrolloff_setting: DEFAULT_SCROLLOFF, mode, + preview, } } } @@ -96,6 +104,7 @@ pub enum Action { ExpandNodeAndSiblings, ToggleMode, + TogglePreview, ResizeViewerDimensions(TTYDimensions), } @@ -137,6 +146,7 @@ impl JsonViewer { // TODO: custom window management here self.toggle_mode(); } + Action::TogglePreview => self.toggle_preview(), Action::ResizeViewerDimensions(dims) => self.dimensions = dims, } @@ -178,6 +188,7 @@ impl JsonViewer { Action::CollapseNodeAndSiblings => true, Action::ExpandNodeAndSiblings => true, Action::ToggleMode => false, + Action::TogglePreview => false, Action::ResizeViewerDimensions(_) => true, _ => false, } @@ -197,6 +208,7 @@ impl JsonViewer { | Action::MoveFocusedLineToCenter | Action::MoveFocusedLineToBottom | Action::ToggleMode + | Action::TogglePreview | Action::ResizeViewerDimensions(_) ) } @@ -586,6 +598,13 @@ impl JsonViewer { } } + fn toggle_preview(&mut self) { + self.preview = match self.preview { + Preview::Full => Preview::Count, + Preview::Count => Preview::Full, + } + } + fn scrolloff(&self) -> u16 { self.scrolloff_setting.min((self.dimensions.height - 1) / 2) } From 1e6dbc1574dba4f348fcc8fb897a5a1f183fdf2c Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 16 Feb 2022 09:04:30 -0800 Subject: [PATCH 04/18] Reduce complexity of `generate_container_count` Rather than calling `highlight_str` a bunch, use `format!` to assemble the string. This also removes the need to `use std::convert::TryInto`, since I'm `as isize`ing everywhere. It also respects and updates `available_space`, though I'm not quite sure if a string as short as this should do so. --- src/lineprinter.rs | 61 +++++++++++++++------------------------------- 1 file changed, 19 insertions(+), 42 deletions(-) diff --git a/src/lineprinter.rs b/src/lineprinter.rs index 5fcda59..1fb531e 100644 --- a/src/lineprinter.rs +++ b/src/lineprinter.rs @@ -2,7 +2,6 @@ use std::collections::hash_map::Entry; use std::fmt; use std::iter::Peekable; use std::ops::Range; -use std::convert::TryInto; use regex::Regex; @@ -824,18 +823,6 @@ impl<'a, 'b, 'c> LinePrinter<'a, 'b, 'c> { return Ok(0); } - let container_type = row.value.container_type().unwrap(); - let mut num_printed = 0; - - // Print opening character, either "[" or "{" - self.highlight_str( - container_type.open_str(), - Some(self.value_range.start), - highlighting::PREVIEW_STYLES, - )?; - - num_printed += 1; - let mut next_sibling = row.first_child(); let mut count:usize = 0; while let OptionIndex::Index(child) = next_sibling { @@ -843,39 +830,29 @@ impl<'a, 'b, 'c> LinePrinter<'a, 'b, 'c> { count += 1; } - // Print count with spaces around it: " N " - self.highlight_str( - " ", - Some(self.value_range.start+num_printed), - highlighting::PREVIEW_STYLES - )?; - num_printed += 1; - - let count_as_str = count.to_string(); - let count_length = count_as_str.len(); - self.highlight_str( - &count_as_str, - Some(self.value_range.start+num_printed), - highlighting::PREVIEW_STYLES - )?; - num_printed += count_length; + let container_type = row.value.container_type().unwrap(); + let mut count_str = format!( + "{} {} {}", + container_type.open_str(), + count.to_string(), + container_type.close_str() + ); + if count_str.len() as isize > available_space { + count_str = format!( + "{}…{}", + container_type.open_str(), + container_type.close_str() + ); + } self.highlight_str( - " ", - Some(self.value_range.start+num_printed), + &count_str, + Some(self.value_range.start), highlighting::PREVIEW_STYLES )?; - num_printed += 1; - - // Print closing char, "]" or "}" - self.highlight_str( - container_type.close_str(), - Some(self.value_range.end - 1), - highlighting::PREVIEW_STYLES, - )?; - num_printed += 1; - - Ok(num_printed.try_into().unwrap()) + let len = count_str.len() as isize; + available_space -= len; + Ok(len) } // {a…: …, …} From f7e2c0b4adba9a0180ff9e1b8aac229664997b89 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 16 Feb 2022 10:14:15 -0800 Subject: [PATCH 05/18] Remove `preview` from `JsonViewer::new`, set it elsewhere This is used in lots of places, so it's not worth changing every single call to `::new()` just to add `preview`. Just set it right after `::new` in app.rs and let it default everywhere else. --- src/app.rs | 3 ++- src/viewer.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/app.rs b/src/app.rs index ef36acc..d853ac7 100644 --- a/src/app.rs +++ b/src/app.rs @@ -49,7 +49,8 @@ impl App { Err(err) => return Err(format!("Unable to parse input: {:?}", err)), }; - let mut viewer = JsonViewer::new(flatjson, opt.mode, opt.preview); + let mut viewer = JsonViewer::new(flatjson, opt.mode); + viewer.preview = opt.preview; viewer.scrolloff_setting = opt.scrolloff; let screen_writer = diff --git a/src/viewer.rs b/src/viewer.rs index b046b70..37f2043 100644 --- a/src/viewer.rs +++ b/src/viewer.rs @@ -36,7 +36,7 @@ pub struct JsonViewer { } impl JsonViewer { - pub fn new(flatjson: FlatJson, mode: Mode, preview: Preview) -> JsonViewer { + pub fn new(flatjson: FlatJson, mode: Mode) -> JsonViewer { JsonViewer { flatjson, top_row: 0, @@ -45,7 +45,7 @@ impl JsonViewer { dimensions: TTYDimensions::default(), scrolloff_setting: DEFAULT_SCROLLOFF, mode, - preview, + preview: Preview::Full, } } } From a9a42258b512112e864656329720cffad5fe8ae0 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 16 Feb 2022 10:16:50 -0800 Subject: [PATCH 06/18] Add test for `generate_container_count` This involved adding an extra string to `generate_container_count`, because Rust has weird memory rules around str::len() that was causing the returned value to get stuck based on the initial assigned value of `count_str`, rather than its final value. Or something, I don't know. --- src/lineprinter.rs | 100 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 85 insertions(+), 15 deletions(-) diff --git a/src/lineprinter.rs b/src/lineprinter.rs index 1fb531e..3062267 100644 --- a/src/lineprinter.rs +++ b/src/lineprinter.rs @@ -831,28 +831,37 @@ impl<'a, 'b, 'c> LinePrinter<'a, 'b, 'c> { } let container_type = row.value.container_type().unwrap(); - let mut count_str = format!( + let count_str = format!( "{} {} {}", container_type.open_str(), count.to_string(), container_type.close_str() ); + let short_str = format!( + "{}…{}", + container_type.open_str(), + container_type.close_str() + ); - if count_str.len() as isize > available_space { - count_str = format!( - "{}…{}", - container_type.open_str(), - container_type.close_str() - ); + if count_str.len() as isize <= available_space { + self.highlight_str( + &count_str, + Some(self.value_range.start), + highlighting::PREVIEW_STYLES + )?; + let len = count_str.len() as isize; + available_space -= len; + return Ok(len) } - self.highlight_str( - &count_str, - Some(self.value_range.start), - highlighting::PREVIEW_STYLES - )?; - let len = count_str.len() as isize; - available_space -= len; - Ok(len) + else { + self.highlight_str( + &short_str, + Some(self.value_range.start), + highlighting::PREVIEW_STYLES + )?; + available_space -= 3; + return Ok(3) + } } // {a…: …, …} @@ -1113,6 +1122,7 @@ mod tests { fn default_line_printer(terminal: &mut dyn Terminal) -> LinePrinter { LinePrinter { mode: Mode::Data, + preview: Preview::Full, terminal, node_depth: 0, depth: 0, @@ -1564,6 +1574,66 @@ mod tests { Ok(()) } + #[test] + fn test_generate_countainer_count() -> std::fmt::Result { + let json_arr = r#"[1,2,3] "#; + let json_obj = r#"{"a":1, "b":2}"#; + // 012345678901234 (14 chars) + let fj_arr = parse_top_level_json(json_arr.to_owned()).unwrap(); + let fj_obj = parse_top_level_json(json_obj.to_owned()).unwrap(); + + let mut term = TextOnlyTerminal::new(); + let mut line: LinePrinter = LinePrinter { + value_range: &(0..json_obj.len()), + preview: Preview::Count, + ..default_line_printer(&mut term) + }; + + for (available_space, used_space, expected) in vec![ + (14, 5, r#"[ 3 ]"#), + (4, 3, r#"[…]"#), + (2, 0, r#""#), + ] + .into_iter() + { + let used = + line.generate_container_count(&fj_arr, &fj_arr[0], available_space)?; + assert_eq!( + expected, + line.terminal.output(), + "expected preview with {} available columns (used up {} columns)", + available_space, + UnicodeWidthStr::width(line.terminal.output()), + ); + assert_eq!(used_space, used); + + line.terminal.clear_output(); + } + + for (available_space, used_space, expected) in vec![ + (14, 5, r#"{ 2 }"#), + (4, 3, r#"{…}"#), + (2, 0, r#""#), + ] + .into_iter() + { + let used = + line.generate_container_count(&fj_obj, &fj_obj[0], available_space)?; + assert_eq!( + expected, + line.terminal.output(), + "expected preview with {} available columns (used up {} columns)", + available_space, + UnicodeWidthStr::width(line.terminal.output()), + ); + assert_eq!(used_space, used); + + line.terminal.clear_output(); + } + + Ok(()) + } + #[test] fn test_generate_array_preview() -> fmt::Result { let json = r#"[1, {"x": true}, null, "hello", true]"#; From 8ab2644631f3c5a02aa79fb6d8d80911c71f1958 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 16 Feb 2022 13:45:55 -0800 Subject: [PATCH 07/18] Add "none" as an option to `preview` This renders nothing, as it's been requested in a couple other Github issues. Toggling now goes `full (default)` --> `count` --> `none`, then repeats. --- src/lineprinter.rs | 3 +++ src/options.rs | 5 +++-- src/viewer.rs | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/lineprinter.rs b/src/lineprinter.rs index 3062267..4777a8d 100644 --- a/src/lineprinter.rs +++ b/src/lineprinter.rs @@ -707,6 +707,9 @@ impl<'a, 'b, 'c> LinePrinter<'a, 'b, 'c> { } } } + else if self.preview == Preview::None { + used_space = 0; + } else { used_space = self.generate_container_count(flatjson, row, available_space)?; diff --git a/src/options.rs b/src/options.rs index 2cee8c0..54f5eb8 100644 --- a/src/options.rs +++ b/src/options.rs @@ -25,8 +25,9 @@ pub struct Opt { /// Initial preview of container nodes. In full mode (--preview full; /// the default), containers will be rendered as much as they can be in /// the width of the terminal. In count mode (--preview count), only - /// the child node count will be rendered. This can be toggled by - /// pressing 'p'. + /// the child node count will be rendered. In none mode (--preview none), + /// no preview will be rendered at all. This can be toggled by pressing + /// 'p'. #[clap(short, long, arg_enum, hide_possible_values = true, default_value_t = Preview::Full)] pub preview: Preview, diff --git a/src/viewer.rs b/src/viewer.rs index 37f2043..422f8b8 100644 --- a/src/viewer.rs +++ b/src/viewer.rs @@ -13,6 +13,7 @@ pub enum Mode { pub enum Preview { Full, Count, + None, } const DEFAULT_SCROLLOFF: u16 = 3; @@ -601,7 +602,8 @@ impl JsonViewer { fn toggle_preview(&mut self) { self.preview = match self.preview { Preview::Full => Preview::Count, - Preview::Count => Preview::Full, + Preview::Count => Preview::None, + Preview::None => Preview::Full, } } From 8ad349d0b720e6cfef9927f6037a3579bfd11a15 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 17 Feb 2022 10:18:39 -0800 Subject: [PATCH 08/18] Update jless.help and usage.html --- docs/usage.html | 40 ++++++++++++++++++++++++++++++++++++++++ src/jless.help | 6 ++++++ 2 files changed, 46 insertions(+) diff --git a/docs/usage.html b/docs/usage.html index 65da7fc..18bb178 100644 --- a/docs/usage.html +++ b/docs/usage.html @@ -344,6 +344,46 @@

Data Mode vs. Line Mode

In line mode you can press % when focused on an open or close delimiter of an object or array to jump to its matching pair.

+

Changing preview of child nodes

+

+

+ The way JLess displays previews can be changed, either by passing --preview or -p with one of: full (the default), count, or none. By pressing p, you can cycle between these three. +

+
+# full (default) +▽ [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, …] + ▽ [0]: {sha: "cdcb02038730f0cd3f5ccea594511e3cfb37887a", node_id: "C_kwDOFYWjXtoAK…", …} + sha: "cdcb02038730f0cd3f5ccea594511e3cfb37887a" + node_id: "C_kwDOFYWjXtoAKGNkY2IwMjAzODczMGYwY2QzZjVjY2VhNTk0NTExZTNjZmIzNzg4N2E" + ▽ commit: {author: {…}, committer: {…}, message: "Upgrade arg parsing to clap v3…", …} + ▽ author: {name: "Marcin Puc", email: "5671049+tranzystorek-io@users.noreply.g…", …} + name: "Marcin Puc" + email: "5671049+tranzystorek-io@users.noreply.github.com" + date: "2022-02-13T20:23:20Z" + +# count +▽ [ 30 ] + ▽ [0]: { 9 } + sha: "cdcb02038730f0cd3f5ccea594511e3cfb37887a" + node_id: "C_kwDOFYWjXtoAKGNkY2IwMjAzODczMGYwY2QzZjVjY2VhNTk0NTExZTNjZmIzNzg4N2E" + ▽ commit: { 7 } + ▽ author: { 3 } + name: "Marcin Puc" + email: "5671049+tranzystorek-io@users.noreply.github.com" + date: "2022-02-13T20:23:20Z" + +# none +▽ + ▽ [0]: + sha: "cdcb02038730f0cd3f5ccea594511e3cfb37887a" + node_id: "C_kwDOFYWjXtoAKGNkY2IwMjAzODczMGYwY2QzZjVjY2VhNTk0NTExZTNjZmIzNzg4N2E" + ▽ commit: + ▽ author: + name: "Marcin Puc" + email: "5671049+tranzystorek-io@users.noreply.github.com" + date: "2022-02-13T20:23:20Z" +
+