Compare commits
31 Commits
v0.1.0
...
61090ba5c2
Author | SHA1 | Date | |
---|---|---|---|
|
61090ba5c2 | ||
a70f1cd4b4 | |||
0927d36505 | |||
bc2b47a6c5 | |||
44d9ed2790 | |||
118aeb3898 | |||
65c2466f97
|
|||
832587b51d
|
|||
4ad8120cb5
|
|||
1f0f526e38
|
|||
91ad8eda01
|
|||
7496d0e964
|
|||
208b14583e
|
|||
4a91a564bf
|
|||
364f3992bb | |||
837caee5db
|
|||
816869e6f9
|
|||
7bdf8393b1
|
|||
699bac7159
|
|||
76f1c87663
|
|||
64d59e069f
|
|||
9bb5bc9e87
|
|||
2d63d3ad4c
|
|||
9cb3296cec
|
|||
874045dca8
|
|||
5548d8e36e
|
|||
cf26422673
|
|||
4a0fcd1bbb
|
|||
86cba91b16
|
|||
113c646334
|
|||
6a147ba0d2
|
38
CHANGELOG.md
38
CHANGELOG.md
@@ -6,6 +6,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.2.0] - 2024-05-25
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- enable creating items on the same level
|
||||||
|
- add command for quickly creating an item
|
||||||
|
- remove removal of spaces in title
|
||||||
|
- with toggle item
|
||||||
|
- with backend
|
||||||
|
- can get actual available roots
|
||||||
|
- can add items
|
||||||
|
- server can actually create root and sections
|
||||||
|
- abstract commander
|
||||||
|
- with async commands instead of inline mutations phew.
|
||||||
|
- add command pattern
|
||||||
|
- allow async function in command
|
||||||
|
- move core to tui and begin grpc work
|
||||||
|
- add protos
|
||||||
|
- update deps
|
||||||
|
- with basic server
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- *(deps)* update rust crate serde to v1.0.203
|
||||||
|
- *(deps)* update rust crate prost to 0.12.6
|
||||||
|
- *(deps)* update rust crate prost to 0.12.5
|
||||||
|
- *(deps)* update rust crate serde to 1.0.202
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- *(deps)* update all dependencies
|
||||||
|
- *(deps)* update rust crate itertools to 0.13.0
|
||||||
|
- move unused imports into cfg
|
||||||
|
- remove unused functions and fix warnings
|
||||||
|
- remove unused variables
|
||||||
|
- fix formatting
|
||||||
|
- refactor out graph created event
|
||||||
|
- let state use either local or backend
|
||||||
|
- remove warnings
|
||||||
|
- remove extra logs
|
||||||
|
|
||||||
## [0.1.0] - 2024-05-11
|
## [0.1.0] - 2024-05-11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
1177
Cargo.lock
generated
1177
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@@ -5,6 +5,8 @@ resolver = "2"
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
hyperlog-core = { path = "crates/hyperlog-core" }
|
hyperlog-core = { path = "crates/hyperlog-core" }
|
||||||
hyperlog-tui = { path = "crates/hyperlog-tui" }
|
hyperlog-tui = { path = "crates/hyperlog-tui" }
|
||||||
|
hyperlog-server = { path = "crates/hyperlog-server" }
|
||||||
|
hyperlog-protos = { path = "crates/hyperlog-protos" }
|
||||||
|
|
||||||
anyhow = { version = "1" }
|
anyhow = { version = "1" }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
@@ -13,8 +15,12 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
|||||||
clap = { version = "4", features = ["derive", "env"] }
|
clap = { version = "4", features = ["derive", "env"] }
|
||||||
dotenv = { version = "0.15" }
|
dotenv = { version = "0.15" }
|
||||||
axum = { version = "0.7" }
|
axum = { version = "0.7" }
|
||||||
|
serde = { version = "1.0.202", features = ["derive"] }
|
||||||
serde_json = "1.0.117"
|
serde_json = "1.0.117"
|
||||||
itertools = "0.12.1"
|
itertools = "0.13.0"
|
||||||
|
uuid = { version = "1.8.0", features = ["v4"] }
|
||||||
|
tonic = "0.11.0"
|
||||||
|
futures = { version = "0.3.30" }
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
|
@@ -12,19 +12,9 @@ clap.workspace = true
|
|||||||
dotenv.workspace = true
|
dotenv.workspace = true
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
|
|
||||||
serde = { version = "1.0.201", features = ["derive"] }
|
serde = { version = "1.0.202", features = ["derive"] }
|
||||||
sqlx = { version = "0.7.4", features = [
|
|
||||||
"runtime-tokio",
|
|
||||||
"tls-rustls",
|
|
||||||
"postgres",
|
|
||||||
"uuid",
|
|
||||||
"time",
|
|
||||||
] }
|
|
||||||
uuid = { version = "1.8.0", features = ["v4"] }
|
uuid = { version = "1.8.0", features = ["v4"] }
|
||||||
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
|
|
||||||
serde_json = "1.0.117"
|
serde_json = "1.0.117"
|
||||||
bus = "2.4.1"
|
|
||||||
dirs = "5.0.1"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
similar-asserts = "1.5.0"
|
similar-asserts = "1.5.0"
|
||||||
|
@@ -1,11 +1 @@
|
|||||||
#![feature(map_try_insert)]
|
|
||||||
|
|
||||||
pub mod commander;
|
|
||||||
pub mod querier;
|
|
||||||
|
|
||||||
pub mod engine;
|
|
||||||
pub mod events;
|
|
||||||
pub mod log;
|
pub mod log;
|
||||||
pub mod shared_engine;
|
|
||||||
pub mod state;
|
|
||||||
pub mod storage;
|
|
||||||
|
@@ -1,32 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
commander::Commander, events::Events, querier::Querier, shared_engine::SharedEngine,
|
|
||||||
storage::Storage,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct State {
|
|
||||||
engine: SharedEngine,
|
|
||||||
pub storage: Storage,
|
|
||||||
events: Events,
|
|
||||||
|
|
||||||
pub commander: Commander,
|
|
||||||
pub querier: Querier,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
pub fn new() -> anyhow::Result<Self> {
|
|
||||||
let storage = Storage::new();
|
|
||||||
let engine = storage.load()?;
|
|
||||||
let events = Events::default();
|
|
||||||
let engine = SharedEngine::from(engine);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
engine: engine.clone(),
|
|
||||||
storage: storage.clone(),
|
|
||||||
events: events.clone(),
|
|
||||||
|
|
||||||
commander: Commander::new(engine.clone(), storage, events)?,
|
|
||||||
querier: Querier::new(engine),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
12
crates/hyperlog-protos/Cargo.toml
Normal file
12
crates/hyperlog-protos/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "hyperlog-protos"
|
||||||
|
edition = "2021"
|
||||||
|
version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tonic.workspace = true
|
||||||
|
|
||||||
|
prost = "0.12.6"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tonic-build = "0.11.0"
|
3
crates/hyperlog-protos/build.rs
Normal file
3
crates/hyperlog-protos/build.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tonic_build::compile_protos("proto/hyperlog.proto").unwrap();
|
||||||
|
}
|
92
crates/hyperlog-protos/proto/hyperlog.proto
Normal file
92
crates/hyperlog-protos/proto/hyperlog.proto
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package hyperlog;
|
||||||
|
|
||||||
|
message UserGraphItem {
|
||||||
|
map<string, GraphItem> items = 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
message SectionGraphItem {
|
||||||
|
map<string, GraphItem> items = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ItemStateNotDone {}
|
||||||
|
message ItemStateDone {}
|
||||||
|
|
||||||
|
message ItemGraphItem {
|
||||||
|
string title = 1;
|
||||||
|
string description = 2;
|
||||||
|
oneof item_state {
|
||||||
|
ItemStateNotDone not_done = 3;
|
||||||
|
ItemStateDone done = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message GraphItem {
|
||||||
|
oneof contents {
|
||||||
|
UserGraphItem user = 1;
|
||||||
|
SectionGraphItem section = 2;
|
||||||
|
ItemGraphItem item = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
service Graph {
|
||||||
|
// Commands
|
||||||
|
rpc CreateSection(CreateSectionRequest) returns (CreateSectionResponse);
|
||||||
|
rpc CreateRoot(CreateRootRequest) returns (CreateRootResponse);
|
||||||
|
rpc CreateItem(CreateItemRequest) returns (CreateItemResponse);
|
||||||
|
rpc UpdateItem(UpdateItemRequest) returns (UpdateItemResponse);
|
||||||
|
rpc ToggleItem(ToggleItemRequest) returns (ToggleItemResponse);
|
||||||
|
|
||||||
|
// Queriers
|
||||||
|
rpc GetAvailableRoots(GetAvailableRootsRequest) returns (GetAvailableRootsResponse);
|
||||||
|
rpc Get(GetRequest) returns (GetReply);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
message CreateSectionRequest {
|
||||||
|
string root = 1;
|
||||||
|
repeated string path = 2;
|
||||||
|
}
|
||||||
|
message CreateSectionResponse {}
|
||||||
|
|
||||||
|
message CreateRootRequest {
|
||||||
|
string root = 1;
|
||||||
|
}
|
||||||
|
message CreateRootResponse {}
|
||||||
|
|
||||||
|
message CreateItemRequest {
|
||||||
|
string root = 1;
|
||||||
|
repeated string path = 2;
|
||||||
|
ItemGraphItem item = 3;
|
||||||
|
}
|
||||||
|
message CreateItemResponse {}
|
||||||
|
|
||||||
|
message UpdateItemRequest {
|
||||||
|
string root = 1;
|
||||||
|
repeated string path = 2;
|
||||||
|
ItemGraphItem item = 3;
|
||||||
|
}
|
||||||
|
message UpdateItemResponse {}
|
||||||
|
|
||||||
|
message ToggleItemRequest {
|
||||||
|
string root = 1;
|
||||||
|
repeated string path = 2;
|
||||||
|
}
|
||||||
|
message ToggleItemResponse {}
|
||||||
|
|
||||||
|
// Queries
|
||||||
|
message GetAvailableRootsRequest {}
|
||||||
|
message GetAvailableRootsResponse {
|
||||||
|
repeated string roots = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetRequest {
|
||||||
|
string root = 1;
|
||||||
|
repeated string paths = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetReply {
|
||||||
|
GraphItem item = 1;
|
||||||
|
}
|
3
crates/hyperlog-protos/src/lib.rs
Normal file
3
crates/hyperlog-protos/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod hyperlog {
|
||||||
|
tonic::include_proto!("hyperlog"); // Specify the same package name as in your .proto file
|
||||||
|
}
|
30
crates/hyperlog-server/Cargo.toml
Normal file
30
crates/hyperlog-server/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
[package]
|
||||||
|
name = "hyperlog-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
hyperlog-core.workspace = true
|
||||||
|
hyperlog-protos.workspace = true
|
||||||
|
|
||||||
|
anyhow.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
axum.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
uuid.workspace = true
|
||||||
|
tonic.workspace = true
|
||||||
|
|
||||||
|
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
|
||||||
|
sqlx = { version = "0.7.4", features = [
|
||||||
|
"runtime-tokio",
|
||||||
|
"tls-rustls",
|
||||||
|
"postgres",
|
||||||
|
"uuid",
|
||||||
|
"time",
|
||||||
|
] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
similar-asserts = "1.5.0"
|
||||||
|
tempfile = "3.10.1"
|
@@ -0,0 +1,17 @@
|
|||||||
|
-- Add migration script here
|
||||||
|
|
||||||
|
CREATE TABLE roots (
|
||||||
|
id UUID NOT NULL PRIMARY KEY,
|
||||||
|
root_name VARCHAR(255) UNIQUE NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE nodes (
|
||||||
|
id UUID NOT NULL PRIMARY KEY,
|
||||||
|
root_id UUID NOT NULL,
|
||||||
|
path VARCHAR NOT NULL,
|
||||||
|
item_type VARCHAR NOT NULL,
|
||||||
|
item_content JSONB
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_unique_root_path ON nodes(root_id, path);
|
||||||
|
|
154
crates/hyperlog-server/src/commands.rs
Normal file
154
crates/hyperlog-server/src/commands.rs
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
use hyperlog_core::log::ItemState;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
services::{
|
||||||
|
create_item::{self, CreateItem, CreateItemExt},
|
||||||
|
create_root::{self, CreateRoot, CreateRootExt},
|
||||||
|
create_section::{self, CreateSection, CreateSectionExt},
|
||||||
|
toggle_item::{self, ToggleItem, ToggleItemExt},
|
||||||
|
update_item::{self, UpdateItem, UpdateItemExt},
|
||||||
|
},
|
||||||
|
state::SharedState,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub enum Command {
|
||||||
|
CreateRoot {
|
||||||
|
root: String,
|
||||||
|
},
|
||||||
|
CreateSection {
|
||||||
|
root: String,
|
||||||
|
path: Vec<String>,
|
||||||
|
},
|
||||||
|
CreateItem {
|
||||||
|
root: String,
|
||||||
|
path: Vec<String>,
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
state: ItemState,
|
||||||
|
},
|
||||||
|
UpdateItem {
|
||||||
|
root: String,
|
||||||
|
path: Vec<String>,
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
state: ItemState,
|
||||||
|
},
|
||||||
|
ToggleItem {
|
||||||
|
root: String,
|
||||||
|
path: Vec<String>,
|
||||||
|
},
|
||||||
|
Move {
|
||||||
|
root: String,
|
||||||
|
src: Vec<String>,
|
||||||
|
dest: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct Commander {
|
||||||
|
create_root: CreateRoot,
|
||||||
|
create_section: CreateSection,
|
||||||
|
create_item: CreateItem,
|
||||||
|
update_item: UpdateItem,
|
||||||
|
toggle_item: ToggleItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Commander {
|
||||||
|
pub fn new(
|
||||||
|
create_root: CreateRoot,
|
||||||
|
create_section: CreateSection,
|
||||||
|
create_item: CreateItem,
|
||||||
|
update_item: UpdateItem,
|
||||||
|
toggle_item: ToggleItem,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
create_root,
|
||||||
|
create_section,
|
||||||
|
create_item,
|
||||||
|
update_item,
|
||||||
|
toggle_item,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(&self, cmd: Command) -> anyhow::Result<()> {
|
||||||
|
match cmd {
|
||||||
|
Command::CreateRoot { root } => {
|
||||||
|
self.create_root
|
||||||
|
.execute(create_root::Request { root })
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Command::CreateSection { root, path } => {
|
||||||
|
self.create_section
|
||||||
|
.execute(create_section::Request { root, path })
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Command::CreateItem {
|
||||||
|
root,
|
||||||
|
path,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
state,
|
||||||
|
} => {
|
||||||
|
self.create_item
|
||||||
|
.execute(create_item::Request {
|
||||||
|
root,
|
||||||
|
path,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
state,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Command::UpdateItem {
|
||||||
|
root,
|
||||||
|
path,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
state,
|
||||||
|
} => {
|
||||||
|
self.update_item
|
||||||
|
.execute(update_item::Request {
|
||||||
|
root,
|
||||||
|
path,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
state,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Command::ToggleItem { root, path } => {
|
||||||
|
self.toggle_item
|
||||||
|
.execute(toggle_item::Request { root, path })
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Command::Move { .. } => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CommanderExt {
|
||||||
|
fn commander(&self) -> Commander;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommanderExt for SharedState {
|
||||||
|
fn commander(&self) -> Commander {
|
||||||
|
Commander::new(
|
||||||
|
self.create_root_service(),
|
||||||
|
self.create_section_service(),
|
||||||
|
self.create_item_service(),
|
||||||
|
self.update_item_service(),
|
||||||
|
self.toggle_item_service(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
454
crates/hyperlog-server/src/external_grpc.rs
Normal file
454
crates/hyperlog-server/src/external_grpc.rs
Normal file
@@ -0,0 +1,454 @@
|
|||||||
|
use hyperlog_protos::hyperlog::{
|
||||||
|
graph_server::{Graph, GraphServer},
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
use std::{collections::HashMap, net::SocketAddr};
|
||||||
|
use tonic::{transport, Response};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
commands::{Command, Commander, CommanderExt},
|
||||||
|
querier::{Querier, QuerierExt},
|
||||||
|
state::SharedState,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct Server {
|
||||||
|
querier: Querier,
|
||||||
|
commander: Commander,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
pub fn new(querier: Querier, commander: Commander) -> Self {
|
||||||
|
Self { querier, commander }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tonic::async_trait]
|
||||||
|
impl Graph for Server {
|
||||||
|
async fn create_item(
|
||||||
|
&self,
|
||||||
|
request: tonic::Request<CreateItemRequest>,
|
||||||
|
) -> std::result::Result<tonic::Response<CreateItemResponse>, tonic::Status> {
|
||||||
|
let req = request.into_inner();
|
||||||
|
tracing::trace!("create item: req({:?})", req);
|
||||||
|
|
||||||
|
if req.root.is_empty() {
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"root cannot be empty".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.path.is_empty() {
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"path cannot be empty".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if req
|
||||||
|
.path
|
||||||
|
.iter()
|
||||||
|
.filter(|item| item.is_empty())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.first()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"path cannot contain empty paths".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if req
|
||||||
|
.path
|
||||||
|
.iter()
|
||||||
|
.filter(|item| item.contains("."))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.first()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"path cannot contain `.`".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = match req.item {
|
||||||
|
Some(i) => i,
|
||||||
|
None => {
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"item cannot contain empty or null".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.commander
|
||||||
|
.execute(Command::CreateItem {
|
||||||
|
root: req.root,
|
||||||
|
path: req.path,
|
||||||
|
title: item.title,
|
||||||
|
description: item.description,
|
||||||
|
state: match item.item_state {
|
||||||
|
Some(item_graph_item::ItemState::Done(_)) => {
|
||||||
|
hyperlog_core::log::ItemState::Done
|
||||||
|
}
|
||||||
|
Some(item_graph_item::ItemState::NotDone(_)) => {
|
||||||
|
hyperlog_core::log::ItemState::NotDone
|
||||||
|
}
|
||||||
|
None => hyperlog_core::log::ItemState::default(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(to_tonic_err)?;
|
||||||
|
|
||||||
|
Ok(Response::new(CreateItemResponse {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_root(
|
||||||
|
&self,
|
||||||
|
request: tonic::Request<CreateRootRequest>,
|
||||||
|
) -> std::result::Result<tonic::Response<CreateRootResponse>, tonic::Status> {
|
||||||
|
let req = request.into_inner();
|
||||||
|
tracing::trace!("create root: req({:?})", req);
|
||||||
|
|
||||||
|
if req.root.is_empty() {
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"root cannot be empty".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.commander
|
||||||
|
.execute(Command::CreateRoot { root: req.root })
|
||||||
|
.await
|
||||||
|
.map_err(to_tonic_err)?;
|
||||||
|
|
||||||
|
Ok(Response::new(CreateRootResponse {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_section(
|
||||||
|
&self,
|
||||||
|
request: tonic::Request<CreateSectionRequest>,
|
||||||
|
) -> std::result::Result<tonic::Response<CreateSectionResponse>, tonic::Status> {
|
||||||
|
let req = request.into_inner();
|
||||||
|
tracing::trace!("create section: req({:?})", req);
|
||||||
|
|
||||||
|
if req.root.is_empty() {
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"root cannot be empty".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.path.is_empty() {
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"path cannot be empty".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if req
|
||||||
|
.path
|
||||||
|
.iter()
|
||||||
|
.filter(|item| item.is_empty())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.first()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"path cannot contain empty paths".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if req
|
||||||
|
.path
|
||||||
|
.iter()
|
||||||
|
.filter(|item| item.contains("."))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.first()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"path cannot contain `.`".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.commander
|
||||||
|
.execute(Command::CreateSection {
|
||||||
|
root: req.root,
|
||||||
|
path: req.path,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(to_tonic_err)?;
|
||||||
|
|
||||||
|
Ok(Response::new(CreateSectionResponse {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get(
|
||||||
|
&self,
|
||||||
|
request: tonic::Request<GetRequest>,
|
||||||
|
) -> std::result::Result<tonic::Response<GetReply>, tonic::Status> {
|
||||||
|
let msg = request.get_ref();
|
||||||
|
|
||||||
|
tracing::trace!("get: req({:?})", msg);
|
||||||
|
|
||||||
|
let res = self
|
||||||
|
.querier
|
||||||
|
.get(&msg.root, msg.paths.clone())
|
||||||
|
.await
|
||||||
|
.map_err(to_tonic_err)?;
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Some(item) => Ok(Response::new(GetReply {
|
||||||
|
item: Some(to_native(&item).map_err(to_tonic_err)?),
|
||||||
|
})),
|
||||||
|
None => {
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::NotFound,
|
||||||
|
"failed to find any valid roots",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_available_roots(
|
||||||
|
&self,
|
||||||
|
request: tonic::Request<GetAvailableRootsRequest>,
|
||||||
|
) -> std::result::Result<tonic::Response<GetAvailableRootsResponse>, tonic::Status> {
|
||||||
|
let req = request.into_inner();
|
||||||
|
tracing::trace!("get available roots: req({:?})", req);
|
||||||
|
|
||||||
|
let roots = match self
|
||||||
|
.querier
|
||||||
|
.get_available_roots()
|
||||||
|
.await
|
||||||
|
.map_err(to_tonic_err)?
|
||||||
|
{
|
||||||
|
Some(roots) => roots,
|
||||||
|
None => {
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::NotFound,
|
||||||
|
"failed to find any valid roots",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Response::new(GetAvailableRootsResponse { roots }))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_item(
|
||||||
|
&self,
|
||||||
|
request: tonic::Request<UpdateItemRequest>,
|
||||||
|
) -> std::result::Result<tonic::Response<UpdateItemResponse>, tonic::Status> {
|
||||||
|
let req = request.into_inner();
|
||||||
|
tracing::trace!("update item: req({:?})", req);
|
||||||
|
|
||||||
|
if req.root.is_empty() {
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"root cannot be empty".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.path.is_empty() {
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"path cannot be empty".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if req
|
||||||
|
.path
|
||||||
|
.iter()
|
||||||
|
.filter(|item| item.is_empty())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.first()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"path cannot contain empty paths".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if req
|
||||||
|
.path
|
||||||
|
.iter()
|
||||||
|
.filter(|item| item.contains("."))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.first()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"path cannot contain `.`".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = match req.item {
|
||||||
|
Some(i) => i,
|
||||||
|
None => {
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"item cannot contain empty or null".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.commander
|
||||||
|
.execute(Command::UpdateItem {
|
||||||
|
root: req.root,
|
||||||
|
path: req.path,
|
||||||
|
title: item.title,
|
||||||
|
description: item.description,
|
||||||
|
state: match item.item_state {
|
||||||
|
Some(item_graph_item::ItemState::Done(_)) => {
|
||||||
|
hyperlog_core::log::ItemState::Done
|
||||||
|
}
|
||||||
|
Some(item_graph_item::ItemState::NotDone(_)) => {
|
||||||
|
hyperlog_core::log::ItemState::NotDone
|
||||||
|
}
|
||||||
|
None => hyperlog_core::log::ItemState::default(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(to_tonic_err)?;
|
||||||
|
|
||||||
|
Ok(Response::new(UpdateItemResponse {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn toggle_item(
|
||||||
|
&self,
|
||||||
|
request: tonic::Request<ToggleItemRequest>,
|
||||||
|
) -> std::result::Result<tonic::Response<ToggleItemResponse>, tonic::Status> {
|
||||||
|
let req = request.into_inner();
|
||||||
|
tracing::trace!("update item: req({:?})", req);
|
||||||
|
|
||||||
|
if req.root.is_empty() {
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"root cannot be empty".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.path.is_empty() {
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"path cannot be empty".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if req
|
||||||
|
.path
|
||||||
|
.iter()
|
||||||
|
.filter(|item| item.is_empty())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.first()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"path cannot contain empty paths".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if req
|
||||||
|
.path
|
||||||
|
.iter()
|
||||||
|
.filter(|item| item.contains("."))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.first()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return Err(tonic::Status::new(
|
||||||
|
tonic::Code::InvalidArgument,
|
||||||
|
"path cannot contain `.`".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.commander
|
||||||
|
.execute(Command::ToggleItem {
|
||||||
|
root: req.root,
|
||||||
|
path: req.path,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.map_err(to_tonic_err)?;
|
||||||
|
|
||||||
|
Ok(Response::new(ToggleItemResponse {}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_native(from: &hyperlog_core::log::GraphItem) -> anyhow::Result<GraphItem> {
|
||||||
|
match from {
|
||||||
|
hyperlog_core::log::GraphItem::User(section)
|
||||||
|
| hyperlog_core::log::GraphItem::Section(section) => {
|
||||||
|
let mut root = HashMap::new();
|
||||||
|
for (key, value) in section.iter() {
|
||||||
|
root.insert(key.to_string(), to_native(value)?);
|
||||||
|
}
|
||||||
|
match from {
|
||||||
|
hyperlog_core::log::GraphItem::User(_) => Ok(GraphItem {
|
||||||
|
contents: Some(graph_item::Contents::User(UserGraphItem { items: root })),
|
||||||
|
}),
|
||||||
|
hyperlog_core::log::GraphItem::Section(_) => Ok(GraphItem {
|
||||||
|
contents: Some(graph_item::Contents::Section(SectionGraphItem {
|
||||||
|
items: root,
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
_ => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hyperlog_core::log::GraphItem::Item {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
state,
|
||||||
|
} => Ok(GraphItem {
|
||||||
|
contents: Some(graph_item::Contents::Item(ItemGraphItem {
|
||||||
|
title: title.to_owned(),
|
||||||
|
description: description.to_owned(),
|
||||||
|
item_state: Some(match state {
|
||||||
|
hyperlog_core::log::ItemState::NotDone => {
|
||||||
|
item_graph_item::ItemState::NotDone(ItemStateNotDone {})
|
||||||
|
}
|
||||||
|
hyperlog_core::log::ItemState::Done => {
|
||||||
|
item_graph_item::ItemState::Done(ItemStateDone {})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: create more defined protobuf categories for errors
|
||||||
|
fn to_tonic_err(err: anyhow::Error) -> tonic::Status {
|
||||||
|
tonic::Status::new(tonic::Code::Unknown, err.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ServerExt {
|
||||||
|
fn grpc_server(&self) -> Server;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerExt for SharedState {
|
||||||
|
fn grpc_server(&self) -> Server {
|
||||||
|
Server::new(self.querier(), self.commander())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn serve(state: &SharedState, host: SocketAddr) -> anyhow::Result<()> {
|
||||||
|
tracing::info!("listening on {}", host);
|
||||||
|
|
||||||
|
let graph_server = state.grpc_server();
|
||||||
|
|
||||||
|
transport::Server::builder()
|
||||||
|
.add_service(GraphServer::new(graph_server))
|
||||||
|
.serve(host)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
40
crates/hyperlog-server/src/external_http.rs
Normal file
40
crates/hyperlog-server/src/external_http.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use axum::{extract::MatchedPath, http::Request, routing::get, Router};
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
|
|
||||||
|
use crate::state::SharedState;
|
||||||
|
|
||||||
|
async fn root() -> &'static str {
|
||||||
|
"Hello, hyperlog!"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn serve(state: &SharedState, host: &SocketAddr) -> anyhow::Result<()> {
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(root))
|
||||||
|
.with_state(state.clone())
|
||||||
|
.layer(
|
||||||
|
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
|
||||||
|
// Log the matched route's path (with placeholders not filled in).
|
||||||
|
// Use request.uri() or OriginalUri if you want the real path.
|
||||||
|
let matched_path = request
|
||||||
|
.extensions()
|
||||||
|
.get::<MatchedPath>()
|
||||||
|
.map(MatchedPath::as_str);
|
||||||
|
|
||||||
|
tracing::info_span!(
|
||||||
|
"http_request",
|
||||||
|
method = ?request.method(),
|
||||||
|
matched_path,
|
||||||
|
some_other_field = tracing::field::Empty,
|
||||||
|
)
|
||||||
|
}), // ...
|
||||||
|
);
|
||||||
|
|
||||||
|
tracing::info!("listening on {}", host);
|
||||||
|
let listener = tokio::net::TcpListener::bind(host).await.unwrap();
|
||||||
|
axum::serve(listener, app.into_make_service())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
40
crates/hyperlog-server/src/internal_http.rs
Normal file
40
crates/hyperlog-server/src/internal_http.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use axum::{extract::MatchedPath, http::Request, routing::get, Router};
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
|
|
||||||
|
use crate::state::SharedState;
|
||||||
|
|
||||||
|
async fn root() -> &'static str {
|
||||||
|
"Hello, hyperlog!"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn serve(state: &SharedState, host: &SocketAddr) -> anyhow::Result<()> {
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", get(root))
|
||||||
|
.with_state(state.clone())
|
||||||
|
.layer(
|
||||||
|
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
|
||||||
|
// Log the matched route's path (with placeholders not filled in).
|
||||||
|
// Use request.uri() or OriginalUri if you want the real path.
|
||||||
|
let matched_path = request
|
||||||
|
.extensions()
|
||||||
|
.get::<MatchedPath>()
|
||||||
|
.map(MatchedPath::as_str);
|
||||||
|
|
||||||
|
tracing::info_span!(
|
||||||
|
"http_request",
|
||||||
|
method = ?request.method(),
|
||||||
|
matched_path,
|
||||||
|
some_other_field = tracing::field::Empty,
|
||||||
|
)
|
||||||
|
}), // ...
|
||||||
|
);
|
||||||
|
|
||||||
|
tracing::info!("listening on {}", host);
|
||||||
|
let listener = tokio::net::TcpListener::bind(host).await.unwrap();
|
||||||
|
axum::serve(listener, app.into_make_service())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
50
crates/hyperlog-server/src/lib.rs
Normal file
50
crates/hyperlog-server/src/lib.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#![feature(map_try_insert)]
|
||||||
|
|
||||||
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
|
use crate::state::{SharedState, State};
|
||||||
|
|
||||||
|
mod external_grpc;
|
||||||
|
mod external_http;
|
||||||
|
mod internal_http;
|
||||||
|
|
||||||
|
mod commands;
|
||||||
|
mod querier;
|
||||||
|
|
||||||
|
mod state;
|
||||||
|
|
||||||
|
mod services;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ServeOptions {
|
||||||
|
pub external_http: SocketAddr,
|
||||||
|
pub internal_http: SocketAddr,
|
||||||
|
|
||||||
|
pub external_grpc: SocketAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn serve(opts: ServeOptions) -> anyhow::Result<()> {
|
||||||
|
let ctrl_c = async {
|
||||||
|
tokio::signal::ctrl_c().await.unwrap();
|
||||||
|
tracing::info!("kill signal received, shutting down");
|
||||||
|
};
|
||||||
|
tracing::debug!("setting up dependencies");
|
||||||
|
let state = SharedState(Arc::new(State::new().await?));
|
||||||
|
|
||||||
|
tracing::debug!("serve starting");
|
||||||
|
tokio::select!(
|
||||||
|
res = external_http::serve(&state, &opts.external_http) => {
|
||||||
|
res?
|
||||||
|
},
|
||||||
|
res = internal_http::serve(&state, &opts.internal_http) => {
|
||||||
|
res?
|
||||||
|
},
|
||||||
|
res = external_grpc::serve(&state, opts.external_grpc) => {
|
||||||
|
res?
|
||||||
|
}
|
||||||
|
() = ctrl_c => {}
|
||||||
|
);
|
||||||
|
tracing::debug!("serve finalized");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
62
crates/hyperlog-server/src/querier.rs
Normal file
62
crates/hyperlog-server/src/querier.rs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
use hyperlog_core::log::GraphItem;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
services::{
|
||||||
|
get_available_roots::{self, GetAvailableRoots, GetAvailableRootsExt},
|
||||||
|
get_graph::{GetGraph, GetGraphExt},
|
||||||
|
},
|
||||||
|
state::SharedState,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Querier {
|
||||||
|
get_available_roots: GetAvailableRoots,
|
||||||
|
get_graph: GetGraph,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Querier {
|
||||||
|
pub fn new(get_available_roots: GetAvailableRoots, get_graph: GetGraph) -> Self {
|
||||||
|
Self {
|
||||||
|
get_available_roots,
|
||||||
|
get_graph,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_available_roots(&self) -> anyhow::Result<Option<Vec<String>>> {
|
||||||
|
let res = self
|
||||||
|
.get_available_roots
|
||||||
|
.execute(get_available_roots::Request {})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if res.roots.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(res.roots))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(
|
||||||
|
&self,
|
||||||
|
root: &str,
|
||||||
|
path: impl IntoIterator<Item = impl Into<String>>,
|
||||||
|
) -> anyhow::Result<Option<GraphItem>> {
|
||||||
|
let graph = self
|
||||||
|
.get_graph
|
||||||
|
.execute(crate::services::get_graph::Request {
|
||||||
|
root: root.into(),
|
||||||
|
path: path.into_iter().map(|s| s.into()).collect(),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Some(graph.item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait QuerierExt {
|
||||||
|
fn querier(&self) -> Querier;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QuerierExt for SharedState {
|
||||||
|
fn querier(&self) -> Querier {
|
||||||
|
Querier::new(self.get_available_roots_service(), self.get_graph_service())
|
||||||
|
}
|
||||||
|
}
|
8
crates/hyperlog-server/src/services.rs
Normal file
8
crates/hyperlog-server/src/services.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
pub mod create_item;
|
||||||
|
pub mod create_root;
|
||||||
|
pub mod create_section;
|
||||||
|
pub mod toggle_item;
|
||||||
|
pub mod update_item;
|
||||||
|
|
||||||
|
pub mod get_available_roots;
|
||||||
|
pub mod get_graph;
|
104
crates/hyperlog-server/src/services/create_item.rs
Normal file
104
crates/hyperlog-server/src/services/create_item.rs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
use hyperlog_core::log::ItemState;
|
||||||
|
use sqlx::types::Json;
|
||||||
|
|
||||||
|
use crate::state::SharedState;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CreateItem {
|
||||||
|
db: sqlx::PgPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Request {
|
||||||
|
pub root: String,
|
||||||
|
pub path: Vec<String>,
|
||||||
|
|
||||||
|
pub title: String,
|
||||||
|
pub description: String,
|
||||||
|
pub state: ItemState,
|
||||||
|
}
|
||||||
|
pub struct Response {}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
struct ItemContent {
|
||||||
|
pub title: String,
|
||||||
|
pub description: String,
|
||||||
|
pub state: ItemState,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
|
struct Root {
|
||||||
|
id: uuid::Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
|
struct Section {}
|
||||||
|
|
||||||
|
impl CreateItem {
|
||||||
|
pub fn new(db: sqlx::PgPool) -> Self {
|
||||||
|
Self { db }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {
|
||||||
|
let Root { id: root_id, .. } =
|
||||||
|
sqlx::query_as(r#"SELECT * FROM roots WHERE root_name = $1"#)
|
||||||
|
.bind(req.root)
|
||||||
|
.fetch_one(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
match req.path.split_last() {
|
||||||
|
Some((_, section_path)) => {
|
||||||
|
if !section_path.is_empty() {
|
||||||
|
let Section { .. } = sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
nodes
|
||||||
|
WHERE
|
||||||
|
root_id = $1 AND
|
||||||
|
path = $2 AND
|
||||||
|
item_type = 'SECTION'
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(root_id)
|
||||||
|
.bind(section_path.join("."))
|
||||||
|
.fetch_one(&self.db)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let node_id = uuid::Uuid::new_v4();
|
||||||
|
sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO nodes
|
||||||
|
(id, root_id, path, item_type, item_content)
|
||||||
|
VALUES
|
||||||
|
($1, $2, $3, $4, $5)"#,
|
||||||
|
)
|
||||||
|
.bind(node_id)
|
||||||
|
.bind(root_id)
|
||||||
|
.bind(req.path.join("."))
|
||||||
|
.bind("ITEM".to_string())
|
||||||
|
.bind(Json(ItemContent {
|
||||||
|
title: req.title,
|
||||||
|
description: req.description,
|
||||||
|
state: req.state,
|
||||||
|
}))
|
||||||
|
.execute(&self.db)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
None => anyhow::bail!("path most contain at least one item"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Response {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CreateItemExt {
|
||||||
|
fn create_item_service(&self) -> CreateItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateItemExt for SharedState {
|
||||||
|
fn create_item_service(&self) -> CreateItem {
|
||||||
|
CreateItem::new(self.db.clone())
|
||||||
|
}
|
||||||
|
}
|
38
crates/hyperlog-server/src/services/create_root.rs
Normal file
38
crates/hyperlog-server/src/services/create_root.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
use crate::state::SharedState;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CreateRoot {
|
||||||
|
db: sqlx::PgPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Request {
|
||||||
|
pub root: String,
|
||||||
|
}
|
||||||
|
pub struct Response {}
|
||||||
|
|
||||||
|
impl CreateRoot {
|
||||||
|
pub fn new(db: sqlx::PgPool) -> Self {
|
||||||
|
Self { db }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {
|
||||||
|
let root_id = uuid::Uuid::new_v4();
|
||||||
|
sqlx::query(r#"INSERT INTO roots (id, root_name) VALUES ($1, $2)"#)
|
||||||
|
.bind(root_id)
|
||||||
|
.bind(req.root)
|
||||||
|
.execute(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Response {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CreateRootExt {
|
||||||
|
fn create_root_service(&self) -> CreateRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateRootExt for SharedState {
|
||||||
|
fn create_root_service(&self) -> CreateRoot {
|
||||||
|
CreateRoot::new(self.db.clone())
|
||||||
|
}
|
||||||
|
}
|
61
crates/hyperlog-server/src/services/create_section.rs
Normal file
61
crates/hyperlog-server/src/services/create_section.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use crate::state::SharedState;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CreateSection {
|
||||||
|
db: sqlx::PgPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Request {
|
||||||
|
pub root: String,
|
||||||
|
pub path: Vec<String>,
|
||||||
|
}
|
||||||
|
pub struct Response {}
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
|
struct Root {
|
||||||
|
id: uuid::Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateSection {
|
||||||
|
pub fn new(db: sqlx::PgPool) -> Self {
|
||||||
|
Self { db }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {
|
||||||
|
let Root { id: root_id, .. } =
|
||||||
|
sqlx::query_as(r#"SELECT * FROM roots WHERE root_name = $1"#)
|
||||||
|
.bind(req.root)
|
||||||
|
.fetch_one(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// FIXME: implement consistency check on path
|
||||||
|
|
||||||
|
let node_id = uuid::Uuid::new_v4();
|
||||||
|
sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO nodes
|
||||||
|
(id, root_id, path, item_type, item_content)
|
||||||
|
VALUES
|
||||||
|
($1, $2, $3, $4, $5)"#,
|
||||||
|
)
|
||||||
|
.bind(node_id)
|
||||||
|
.bind(root_id)
|
||||||
|
.bind(req.path.join("."))
|
||||||
|
.bind("SECTION".to_string())
|
||||||
|
.bind(None::<serde_json::Value>)
|
||||||
|
.execute(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Response {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CreateSectionExt {
|
||||||
|
fn create_section_service(&self) -> CreateSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateSectionExt for SharedState {
|
||||||
|
fn create_section_service(&self) -> CreateSection {
|
||||||
|
CreateSection::new(self.db.clone())
|
||||||
|
}
|
||||||
|
}
|
51
crates/hyperlog-server/src/services/get_available_roots.rs
Normal file
51
crates/hyperlog-server/src/services/get_available_roots.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
use crate::state::SharedState;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GetAvailableRoots {
|
||||||
|
db: sqlx::PgPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Request {}
|
||||||
|
pub struct Response {
|
||||||
|
pub roots: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
|
pub struct Root {
|
||||||
|
root_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetAvailableRoots {
|
||||||
|
pub fn new(db: sqlx::PgPool) -> Self {
|
||||||
|
Self { db }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(&self, _req: Request) -> anyhow::Result<Response> {
|
||||||
|
let roots: Vec<Root> = sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
roots
|
||||||
|
LIMIT
|
||||||
|
100
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Response {
|
||||||
|
roots: roots.into_iter().map(|i| i.root_name).collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait GetAvailableRootsExt {
|
||||||
|
fn get_available_roots_service(&self) -> GetAvailableRoots;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetAvailableRootsExt for SharedState {
|
||||||
|
fn get_available_roots_service(&self) -> GetAvailableRoots {
|
||||||
|
GetAvailableRoots::new(self.db.clone())
|
||||||
|
}
|
||||||
|
}
|
368
crates/hyperlog-server/src/services/get_graph.rs
Normal file
368
crates/hyperlog-server/src/services/get_graph.rs
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use hyperlog_core::log::{GraphItem, ItemState};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use sqlx::types::Json;
|
||||||
|
|
||||||
|
use crate::state::SharedState;
|
||||||
|
|
||||||
|
use self::engine::Engine;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GetGraph {
|
||||||
|
db: sqlx::PgPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Request {
|
||||||
|
pub root: String,
|
||||||
|
pub path: Vec<String>,
|
||||||
|
}
|
||||||
|
pub struct Response {
|
||||||
|
pub item: GraphItem,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
|
struct Root {
|
||||||
|
id: uuid::Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Item {
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
state: ItemState,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow, Debug)]
|
||||||
|
struct Node {
|
||||||
|
path: String,
|
||||||
|
item_type: String,
|
||||||
|
item_content: Option<Json<serde_json::Value>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetGraph {
|
||||||
|
pub fn new(db: sqlx::PgPool) -> Self {
|
||||||
|
Self { db }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {
|
||||||
|
let Root { id: root_id, .. } =
|
||||||
|
sqlx::query_as(r#"SELECT * FROM roots WHERE root_name = $1"#)
|
||||||
|
.bind(&req.root)
|
||||||
|
.fetch_one(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let nodes: Vec<Node> = sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
nodes
|
||||||
|
WHERE
|
||||||
|
root_id = $1
|
||||||
|
LIMIT
|
||||||
|
1000
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(root_id)
|
||||||
|
.fetch_all(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let item = self.build_graph(req.root, req.path, nodes)?;
|
||||||
|
|
||||||
|
Ok(Response { item })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_graph(
|
||||||
|
&self,
|
||||||
|
root: String,
|
||||||
|
path: Vec<String>,
|
||||||
|
mut nodes: Vec<Node>,
|
||||||
|
) -> anyhow::Result<GraphItem> {
|
||||||
|
nodes.sort_by(|a, b| a.path.cmp(&b.path));
|
||||||
|
let mut engine = Engine::default();
|
||||||
|
engine.create_root(&root)?;
|
||||||
|
|
||||||
|
self.get_graph_items(&root, &mut engine, &nodes)?;
|
||||||
|
|
||||||
|
engine
|
||||||
|
.get(&root, &path.iter().map(|s| s.as_str()).collect::<Vec<_>>())
|
||||||
|
.ok_or(anyhow::anyhow!("failed to find a valid graph"))
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_graph_items(
|
||||||
|
&self,
|
||||||
|
root: &str,
|
||||||
|
engine: &mut Engine,
|
||||||
|
nodes: &Vec<Node>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
for node in nodes {
|
||||||
|
if let Some(item) = self.get_graph_item(node) {
|
||||||
|
let path = node.path.split('.').collect::<Vec<_>>();
|
||||||
|
engine.create(root, &path, item)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_graph_item(&self, node: &Node) -> Option<GraphItem> {
|
||||||
|
match node.item_type.as_str() {
|
||||||
|
"SECTION" => Some(GraphItem::Section(BTreeMap::default())),
|
||||||
|
"ITEM" => {
|
||||||
|
if let Some(content) = &node.item_content {
|
||||||
|
let item: Item = serde_json::from_value(content.0.clone()).ok()?;
|
||||||
|
|
||||||
|
Some(GraphItem::Item {
|
||||||
|
title: item.title,
|
||||||
|
description: item.description,
|
||||||
|
state: item.state,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait GetGraphExt {
|
||||||
|
fn get_graph_service(&self) -> GetGraph;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetGraphExt for SharedState {
|
||||||
|
fn get_graph_service(&self) -> GetGraph {
|
||||||
|
GetGraph::new(self.db.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod engine {
|
||||||
|
|
||||||
|
use std::{collections::BTreeMap, fmt::Display};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Context};
|
||||||
|
use hyperlog_core::log::{Graph, GraphItem, ItemState};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Engine {
|
||||||
|
graph: Graph,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Engine {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn engine_from_str(input: &str) -> anyhow::Result<Self> {
|
||||||
|
let graph: Graph = serde_json::from_str(input)?;
|
||||||
|
|
||||||
|
Ok(Self { graph })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn to_str(&self) -> anyhow::Result<String> {
|
||||||
|
serde_json::to_string_pretty(&self.graph).context("failed to serialize graph")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_root(&mut self, root: &str) -> anyhow::Result<()> {
|
||||||
|
self.graph
|
||||||
|
.try_insert(root.to_string(), GraphItem::User(BTreeMap::default()))
|
||||||
|
.map_err(|_| anyhow!("entry was already found, aborting"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(&mut self, root: &str, path: &[&str], item: GraphItem) -> anyhow::Result<()> {
|
||||||
|
let graph = &mut self.graph;
|
||||||
|
|
||||||
|
let (last, items) = path.split_last().ok_or(anyhow!(
|
||||||
|
"path cannot be empty, must contain at least one item"
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let root = graph
|
||||||
|
.get_mut(root)
|
||||||
|
.ok_or(anyhow!("root was missing a user, aborting"))?;
|
||||||
|
|
||||||
|
let mut current_item = root;
|
||||||
|
for section in items {
|
||||||
|
match current_item {
|
||||||
|
GraphItem::User(u) => match u.get_mut(section.to_owned()) {
|
||||||
|
Some(graph_item) => {
|
||||||
|
current_item = graph_item;
|
||||||
|
}
|
||||||
|
None => anyhow::bail!("path: {} section was not found", section),
|
||||||
|
},
|
||||||
|
GraphItem::Item { .. } => anyhow::bail!("path: {} was already found", section),
|
||||||
|
GraphItem::Section(s) => match s.get_mut(section.to_owned()) {
|
||||||
|
Some(graph_item) => {
|
||||||
|
current_item = graph_item;
|
||||||
|
}
|
||||||
|
None => anyhow::bail!("path: {} section was not found", section),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match current_item {
|
||||||
|
GraphItem::User(u) => {
|
||||||
|
u.insert(last.to_string(), item);
|
||||||
|
}
|
||||||
|
GraphItem::Section(s) => {
|
||||||
|
s.insert(last.to_string(), item);
|
||||||
|
}
|
||||||
|
GraphItem::Item { .. } => anyhow::bail!("cannot insert an item into an item"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, root: &str, path: &[&str]) -> Option<&GraphItem> {
|
||||||
|
let root = self.graph.get(root)?;
|
||||||
|
|
||||||
|
root.get(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn get_mut(&mut self, root: &str, path: &[&str]) -> Option<&mut GraphItem> {
|
||||||
|
let root = self.graph.get_mut(root)?;
|
||||||
|
|
||||||
|
root.get_mut(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn take(&mut self, root: &str, path: &[&str]) -> Option<GraphItem> {
|
||||||
|
let root = self.graph.get_mut(root)?;
|
||||||
|
|
||||||
|
root.take(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn section_move(
|
||||||
|
&mut self,
|
||||||
|
root: &str,
|
||||||
|
src_path: &[&str],
|
||||||
|
dest_path: &[&str],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let src = self
|
||||||
|
.take(root, src_path)
|
||||||
|
.ok_or(anyhow!("failed to find source path"))?;
|
||||||
|
|
||||||
|
let dest = self
|
||||||
|
.get_mut(root, dest_path)
|
||||||
|
.ok_or(anyhow!("failed to find destination"))?;
|
||||||
|
|
||||||
|
let src_item = src_path
|
||||||
|
.last()
|
||||||
|
.ok_or(anyhow!("src path must have at least one item"))?;
|
||||||
|
|
||||||
|
match dest {
|
||||||
|
GraphItem::User(u) => {
|
||||||
|
u.try_insert(src_item.to_string(), src)
|
||||||
|
.map_err(|_e| anyhow!("key was already found, aborting: {}", src_item))?;
|
||||||
|
}
|
||||||
|
GraphItem::Section(s) => {
|
||||||
|
s.try_insert(src_item.to_string(), src)
|
||||||
|
.map_err(|_e| anyhow!("key was already found, aborting: {}", src_item))?;
|
||||||
|
}
|
||||||
|
GraphItem::Item { .. } => {
|
||||||
|
anyhow::bail!(
|
||||||
|
"failed to insert src at item, item doesn't support arbitrary items"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn delete(&mut self, root: &str, path: &[&str]) -> anyhow::Result<()> {
|
||||||
|
self.take(root, path)
|
||||||
|
.map(|_| ())
|
||||||
|
.ok_or(anyhow!("item was not found"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn toggle_item(&mut self, root: &str, path: &[&str]) -> anyhow::Result<()> {
|
||||||
|
if let Some(item) = self.get_mut(root, path) {
|
||||||
|
match item {
|
||||||
|
GraphItem::Item { state, .. } => match state {
|
||||||
|
ItemState::NotDone => *state = ItemState::Done,
|
||||||
|
ItemState::Done => *state = ItemState::NotDone,
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
anyhow::bail!("{}.{:?} is not an item", root, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn update_item(
|
||||||
|
&mut self,
|
||||||
|
root: &str,
|
||||||
|
path: &[&str],
|
||||||
|
item: &GraphItem,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
if let Some((name, dest_last)) = path.split_last() {
|
||||||
|
if let Some(parent) = self.get_mut(root, dest_last) {
|
||||||
|
match parent {
|
||||||
|
GraphItem::User(s) | GraphItem::Section(s) => {
|
||||||
|
if let Some(mut existing) = s.remove(*name) {
|
||||||
|
match (&mut existing, item) {
|
||||||
|
(
|
||||||
|
GraphItem::Item {
|
||||||
|
title: ex_title,
|
||||||
|
description: ex_desc,
|
||||||
|
state: ex_state,
|
||||||
|
},
|
||||||
|
GraphItem::Item {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
state,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
ex_title.clone_from(title);
|
||||||
|
ex_desc.clone_from(description);
|
||||||
|
ex_state.clone_from(state);
|
||||||
|
|
||||||
|
let title = title.replace(".", "-");
|
||||||
|
s.insert(title, existing.clone());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
anyhow::bail!(
|
||||||
|
"path: {}.{} found is not an item",
|
||||||
|
root,
|
||||||
|
path.join(".")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GraphItem::Item { .. } => {
|
||||||
|
anyhow::bail!("cannot rename when item is placed in an item")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn get_roots(&self) -> Option<Vec<String>> {
|
||||||
|
let items = self.graph.keys().cloned().collect::<Vec<_>>();
|
||||||
|
if items.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Engine {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let output = serde_json::to_string_pretty(&self.graph).unwrap();
|
||||||
|
f.write_str(&output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
105
crates/hyperlog-server/src/services/toggle_item.rs
Normal file
105
crates/hyperlog-server/src/services/toggle_item.rs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
use hyperlog_core::log::ItemState;
|
||||||
|
use sqlx::types::Json;
|
||||||
|
|
||||||
|
use crate::state::SharedState;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ToggleItem {
|
||||||
|
db: sqlx::PgPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Request {
|
||||||
|
pub root: String,
|
||||||
|
pub path: Vec<String>,
|
||||||
|
}
|
||||||
|
pub struct Response {}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
struct ItemContent {
|
||||||
|
pub title: String,
|
||||||
|
pub description: String,
|
||||||
|
pub state: ItemState,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
|
struct Root {
|
||||||
|
id: uuid::Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
|
struct Node {
|
||||||
|
id: uuid::Uuid,
|
||||||
|
item_content: Option<Json<ItemContent>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToggleItem {
|
||||||
|
pub fn new(db: sqlx::PgPool) -> Self {
|
||||||
|
Self { db }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {
|
||||||
|
let Root { id: root_id, .. } =
|
||||||
|
sqlx::query_as(r#"SELECT * FROM roots WHERE root_name = $1"#)
|
||||||
|
.bind(req.root)
|
||||||
|
.fetch_one(&self.db)
|
||||||
|
.await?;
|
||||||
|
let Node {
|
||||||
|
id: node_id,
|
||||||
|
mut item_content,
|
||||||
|
} = sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
nodes
|
||||||
|
WHERE
|
||||||
|
root_id = $1
|
||||||
|
AND path = $2
|
||||||
|
AND item_type = $3
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(root_id)
|
||||||
|
.bind(req.path.join("."))
|
||||||
|
.bind("ITEM")
|
||||||
|
.fetch_one(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(ref mut content) = item_content {
|
||||||
|
content.state = match content.state {
|
||||||
|
ItemState::NotDone => ItemState::Done,
|
||||||
|
ItemState::Done => ItemState::NotDone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = sqlx::query(
|
||||||
|
r#"
|
||||||
|
UPDATE
|
||||||
|
nodes
|
||||||
|
SET
|
||||||
|
item_content = $1
|
||||||
|
WHERE
|
||||||
|
id = $2
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(item_content)
|
||||||
|
.bind(node_id)
|
||||||
|
.execute(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if res.rows_affected() != 1 {
|
||||||
|
anyhow::bail!("failed to update item");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Response {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ToggleItemExt {
|
||||||
|
fn toggle_item_service(&self) -> ToggleItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToggleItemExt for SharedState {
|
||||||
|
fn toggle_item_service(&self) -> ToggleItem {
|
||||||
|
ToggleItem::new(self.db.clone())
|
||||||
|
}
|
||||||
|
}
|
107
crates/hyperlog-server/src/services/update_item.rs
Normal file
107
crates/hyperlog-server/src/services/update_item.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use hyperlog_core::log::ItemState;
|
||||||
|
use sqlx::types::Json;
|
||||||
|
|
||||||
|
use crate::state::SharedState;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct UpdateItem {
|
||||||
|
db: sqlx::PgPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Request {
|
||||||
|
pub root: String,
|
||||||
|
pub path: Vec<String>,
|
||||||
|
|
||||||
|
pub title: String,
|
||||||
|
pub description: String,
|
||||||
|
pub state: ItemState,
|
||||||
|
}
|
||||||
|
pub struct Response {}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
struct ItemContent {
|
||||||
|
pub title: String,
|
||||||
|
pub description: String,
|
||||||
|
pub state: ItemState,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
|
struct Root {
|
||||||
|
id: uuid::Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateItem {
|
||||||
|
pub fn new(db: sqlx::PgPool) -> Self {
|
||||||
|
Self { db }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {
|
||||||
|
let Root { id: root_id, .. } =
|
||||||
|
sqlx::query_as(r#"SELECT * FROM roots WHERE root_name = $1"#)
|
||||||
|
.bind(req.root)
|
||||||
|
.fetch_one(&self.db)
|
||||||
|
.await?;
|
||||||
|
let Root { id: node_id } = sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id
|
||||||
|
FROM
|
||||||
|
nodes
|
||||||
|
WHERE
|
||||||
|
root_id = $1
|
||||||
|
AND path = $2
|
||||||
|
AND item_type = $3
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(root_id)
|
||||||
|
.bind(req.path.join("."))
|
||||||
|
.bind("ITEM")
|
||||||
|
.fetch_one(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let (_, rest) = req
|
||||||
|
.path
|
||||||
|
.split_last()
|
||||||
|
.ok_or(anyhow::anyhow!("expected path to have at least one item"))?;
|
||||||
|
|
||||||
|
let mut rest = rest.to_vec();
|
||||||
|
rest.push(req.title.replace(".", "-"));
|
||||||
|
|
||||||
|
let res = sqlx::query(
|
||||||
|
r#"
|
||||||
|
UPDATE
|
||||||
|
nodes
|
||||||
|
SET
|
||||||
|
item_content = $1,
|
||||||
|
path = $2
|
||||||
|
WHERE
|
||||||
|
id = $3
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(Json(ItemContent {
|
||||||
|
title: req.title,
|
||||||
|
description: req.description,
|
||||||
|
state: req.state,
|
||||||
|
}))
|
||||||
|
.bind(rest.join("."))
|
||||||
|
.bind(node_id)
|
||||||
|
.execute(&self.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if res.rows_affected() != 1 {
|
||||||
|
anyhow::bail!("failed to update item");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Response {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait UpdateItemExt {
|
||||||
|
fn update_item_service(&self) -> UpdateItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateItemExt for SharedState {
|
||||||
|
fn update_item_service(&self) -> UpdateItem {
|
||||||
|
UpdateItem::new(self.db.clone())
|
||||||
|
}
|
||||||
|
}
|
37
crates/hyperlog-server/src/state.rs
Normal file
37
crates/hyperlog-server/src/state.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SharedState(pub Arc<State>);
|
||||||
|
|
||||||
|
impl Deref for SharedState {
|
||||||
|
type Target = Arc<State>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct State {
|
||||||
|
pub db: Pool<Postgres>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub async fn new() -> anyhow::Result<Self> {
|
||||||
|
let db = sqlx::PgPool::connect(
|
||||||
|
&std::env::var("DATABASE_URL").context("DATABASE_URL is not set")?,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
sqlx::migrate!("migrations/crdb")
|
||||||
|
.set_locking(false)
|
||||||
|
.run(&db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let _ = sqlx::query("SELECT 1;").fetch_one(&db).await?;
|
||||||
|
|
||||||
|
Ok(Self { db })
|
||||||
|
}
|
||||||
|
}
|
@@ -6,19 +6,26 @@ repository = "https://git.front.kjuulh.io/kjuulh/hyperlog"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hyperlog-core.workspace = true
|
hyperlog-core.workspace = true
|
||||||
|
hyperlog-protos.workspace = true
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
tracing-subscriber.workspace = true
|
tracing-subscriber.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
|
tonic.workspace = true
|
||||||
|
futures.workspace = true
|
||||||
|
|
||||||
ratatui = "0.26.2"
|
ratatui = "0.26.2"
|
||||||
crossterm = { version = "0.27.0", features = ["event-stream"] }
|
crossterm = { version = "0.27.0", features = ["event-stream"] }
|
||||||
directories = "5.0.1"
|
directories = "5.0.1"
|
||||||
human-panic = "2.0.0"
|
human-panic = "2.0.0"
|
||||||
ropey = "1.6.1"
|
ropey = "1.6.1"
|
||||||
|
bus = "2.4.1"
|
||||||
|
dirs = "5.0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
similar-asserts = "1.5.0"
|
similar-asserts = "1.5.0"
|
||||||
|
tempfile = "3.10.1"
|
||||||
|
@@ -5,8 +5,12 @@ use ratatui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command_parser::CommandParser, commands::IntoCommand,
|
command_parser::CommandParser,
|
||||||
components::graph_explorer::GraphExplorer, state::SharedState, Msg,
|
commands::{batch::BatchCommand, IntoCommand},
|
||||||
|
components::graph_explorer::GraphExplorer,
|
||||||
|
models::IOEvent,
|
||||||
|
state::SharedState,
|
||||||
|
Msg,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
@@ -26,10 +30,10 @@ pub enum Dialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Dialog {
|
impl Dialog {
|
||||||
pub fn get_command(&self) -> Option<hyperlog_core::commander::Command> {
|
pub fn get_command(&self) -> Option<impl IntoCommand> {
|
||||||
match self {
|
match self {
|
||||||
Dialog::CreateItem { state } => state.get_command(),
|
Dialog::CreateItem { state } => state.get_command().map(|c| c.into_command()),
|
||||||
Dialog::EditItem { state } => state.get_command(),
|
Dialog::EditItem { state } => state.get_command().map(|c| c.into_command()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,12 +86,21 @@ impl<'a> App<'a> {
|
|||||||
pub fn update(&mut self, msg: Msg) -> anyhow::Result<impl IntoCommand> {
|
pub fn update(&mut self, msg: Msg) -> anyhow::Result<impl IntoCommand> {
|
||||||
tracing::trace!("handling msg: {:?}", msg);
|
tracing::trace!("handling msg: {:?}", msg);
|
||||||
|
|
||||||
|
let mut batch = BatchCommand::default();
|
||||||
|
|
||||||
match &msg {
|
match &msg {
|
||||||
|
Msg::ItemCreated(IOEvent::Success(()))
|
||||||
|
| Msg::ItemUpdated(IOEvent::Success(()))
|
||||||
|
| Msg::SectionCreated(IOEvent::Success(()))
|
||||||
|
| Msg::ItemToggled(IOEvent::Success(())) => {
|
||||||
|
batch.with(self.graph_explorer.new_update_graph());
|
||||||
|
}
|
||||||
Msg::MoveRight => self.graph_explorer.move_right()?,
|
Msg::MoveRight => self.graph_explorer.move_right()?,
|
||||||
Msg::MoveLeft => self.graph_explorer.move_left()?,
|
Msg::MoveLeft => self.graph_explorer.move_left()?,
|
||||||
Msg::MoveDown => self.graph_explorer.move_down()?,
|
Msg::MoveDown => self.graph_explorer.move_down()?,
|
||||||
Msg::MoveUp => self.graph_explorer.move_up()?,
|
Msg::MoveUp => self.graph_explorer.move_up()?,
|
||||||
Msg::OpenCreateItemDialog => self.open_dialog(),
|
Msg::OpenCreateItemDialog => self.open_dialog(),
|
||||||
|
Msg::OpenCreateItemDialogBelow => self.open_dialog_below(),
|
||||||
Msg::OpenEditItemDialog { item } => self.open_edit_item_dialog(item),
|
Msg::OpenEditItemDialog { item } => self.open_edit_item_dialog(item),
|
||||||
Msg::EnterInsertMode => self.mode = Mode::Insert,
|
Msg::EnterInsertMode => self.mode = Mode::Insert,
|
||||||
Msg::EnterViewMode => self.mode = Mode::View,
|
Msg::EnterViewMode => self.mode = Mode::View,
|
||||||
@@ -97,7 +110,10 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
Msg::Interact => match self.focus {
|
Msg::Interact => match self.focus {
|
||||||
AppFocus::Dialog => {}
|
AppFocus::Dialog => {}
|
||||||
AppFocus::Graph => self.graph_explorer.interact()?,
|
AppFocus::Graph => {
|
||||||
|
let cmd = self.graph_explorer.interact()?;
|
||||||
|
batch.with(cmd);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Msg::SubmitCommand { command } => {
|
Msg::SubmitCommand { command } => {
|
||||||
tracing::info!("submitting command");
|
tracing::info!("submitting command");
|
||||||
@@ -108,11 +124,9 @@ impl<'a> App<'a> {
|
|||||||
if command.is_write() {
|
if command.is_write() {
|
||||||
if let Some(dialog) = &self.dialog {
|
if let Some(dialog) = &self.dialog {
|
||||||
if let Some(output) = dialog.get_command() {
|
if let Some(output) = dialog.get_command() {
|
||||||
self.state.commander.execute(output)?;
|
batch.with(output.into_command());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.graph_explorer.update_graph()?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if command.is_quit() {
|
if command.is_quit() {
|
||||||
@@ -121,26 +135,31 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
AppFocus::Graph => {
|
AppFocus::Graph => {
|
||||||
if let Some(msg) = self.graph_explorer.execute_command(&command)? {
|
if let Some(cmd) = self.graph_explorer.execute_command(&command)? {
|
||||||
self.command = None;
|
self.command = None;
|
||||||
return Ok(msg.into_command());
|
batch.with(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
if command.is_quit() {
|
if command.is_quit() {
|
||||||
return Ok(Msg::QuitApp.into_command());
|
batch.with(Msg::QuitApp.into_command());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.command = None;
|
self.command = None;
|
||||||
return Ok(Msg::EnterViewMode.into_command());
|
batch.with(Msg::EnterViewMode.into_command());
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cmd = self.graph_explorer.inner.update(&msg);
|
||||||
|
if let Some(cmd) = cmd {
|
||||||
|
batch.with(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(command) = &mut self.command {
|
if let Some(command) = &mut self.command {
|
||||||
let cmd = command.update(&msg)?;
|
let cmd = command.update(&msg)?;
|
||||||
return Ok(cmd.into_command());
|
batch.with(cmd);
|
||||||
} else if let Some(dialog) = &mut self.dialog {
|
} else if let Some(dialog) = &mut self.dialog {
|
||||||
match dialog {
|
match dialog {
|
||||||
Dialog::CreateItem { state } => state.update(&msg)?,
|
Dialog::CreateItem { state } => state.update(&msg)?,
|
||||||
@@ -148,7 +167,7 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(().into_command())
|
Ok(batch.into_command())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_dialog(&mut self) {
|
fn open_dialog(&mut self) {
|
||||||
@@ -158,18 +177,34 @@ impl<'a> App<'a> {
|
|||||||
|
|
||||||
self.focus = AppFocus::Dialog;
|
self.focus = AppFocus::Dialog;
|
||||||
self.dialog = Some(Dialog::CreateItem {
|
self.dialog = Some(Dialog::CreateItem {
|
||||||
state: CreateItemState::new(root, path),
|
state: CreateItemState::new(&self.state, root, path),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn open_dialog_below(&mut self) {
|
||||||
|
if self.dialog.is_none() {
|
||||||
|
let root = self.root.clone();
|
||||||
|
let path = self.graph_explorer.get_current_path();
|
||||||
|
|
||||||
|
if let Some((_, rest)) = path.split_last() {
|
||||||
|
let path = rest.to_vec();
|
||||||
|
|
||||||
|
self.focus = AppFocus::Dialog;
|
||||||
|
self.dialog = Some(Dialog::CreateItem {
|
||||||
|
state: CreateItemState::new(&self.state, root, path),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn open_edit_item_dialog(&mut self, item: &GraphItem) {
|
fn open_edit_item_dialog(&mut self, item: &GraphItem) {
|
||||||
if self.dialog.is_none() {
|
if self.dialog.is_none() {
|
||||||
let root = self.root.clone();
|
let root = self.root.clone();
|
||||||
let path = self.graph_explorer.get_current_path();
|
let path = self.graph_explorer.get_current_path();
|
||||||
|
|
||||||
self.dialog = Some(Dialog::EditItem {
|
self.dialog = Some(Dialog::EditItem {
|
||||||
state: EditItemState::new(root, path, item),
|
state: EditItemState::new(&self.state, root, path, item),
|
||||||
});
|
});
|
||||||
self.command = None;
|
self.command = None;
|
||||||
self.focus = AppFocus::Dialog;
|
self.focus = AppFocus::Dialog;
|
||||||
|
@@ -1,7 +1,12 @@
|
|||||||
|
use hyperlog_core::log::ItemState;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{prelude::*, widgets::*};
|
||||||
|
|
||||||
use crate::models::Msg;
|
use crate::{
|
||||||
|
commands::{create_item::CreateItemCommandExt, IntoCommand},
|
||||||
|
models::Msg,
|
||||||
|
state::SharedState,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{InputBuffer, InputField};
|
use super::{InputBuffer, InputField};
|
||||||
|
|
||||||
@@ -23,10 +28,16 @@ pub struct CreateItemState {
|
|||||||
description: InputBuffer,
|
description: InputBuffer,
|
||||||
|
|
||||||
focused: CreateItemFocused,
|
focused: CreateItemFocused,
|
||||||
|
|
||||||
|
state: SharedState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateItemState {
|
impl CreateItemState {
|
||||||
pub fn new(root: impl Into<String>, path: impl IntoIterator<Item = impl Into<String>>) -> Self {
|
pub fn new(
|
||||||
|
state: &SharedState,
|
||||||
|
root: impl Into<String>,
|
||||||
|
path: impl IntoIterator<Item = impl Into<String>>,
|
||||||
|
) -> Self {
|
||||||
let root = root.into();
|
let root = root.into();
|
||||||
let path = path.into_iter().map(|p| p.into()).collect_vec();
|
let path = path.into_iter().map(|p| p.into()).collect_vec();
|
||||||
|
|
||||||
@@ -37,6 +48,8 @@ impl CreateItemState {
|
|||||||
title: Default::default(),
|
title: Default::default(),
|
||||||
description: Default::default(),
|
description: Default::default(),
|
||||||
focused: Default::default(),
|
focused: Default::default(),
|
||||||
|
|
||||||
|
state: state.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,21 +74,29 @@ impl CreateItemState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_command(&self) -> Option<hyperlog_core::commander::Command> {
|
pub fn get_command(&self) -> Option<impl IntoCommand> {
|
||||||
let title = self.title.string();
|
let title = self.title.string();
|
||||||
let description = self.description.string();
|
let description = self.description.string();
|
||||||
|
|
||||||
if !title.is_empty() {
|
if !title.is_empty() {
|
||||||
let mut path = self.path.clone();
|
let mut path = self.path.clone();
|
||||||
path.push(title.replace([' ', '.'], "-"));
|
path.push(title.replace(['.'], "-"));
|
||||||
|
|
||||||
Some(hyperlog_core::commander::Command::CreateItem {
|
Some(self.state.create_item_command().command(
|
||||||
root: self.root.clone(),
|
&self.root,
|
||||||
path,
|
&path.iter().map(|i| i.as_str()).collect_vec(),
|
||||||
title: title.trim().into(),
|
title.trim(),
|
||||||
description: description.trim().into(),
|
description.trim(),
|
||||||
state: hyperlog_core::log::ItemState::NotDone,
|
&ItemState::NotDone,
|
||||||
})
|
))
|
||||||
|
|
||||||
|
// Some(commander::Command::CreateItem {
|
||||||
|
// root: self.root.clone(),
|
||||||
|
// path,
|
||||||
|
// title: title.trim().into(),
|
||||||
|
// description: description.trim().into(),
|
||||||
|
// state: hyperlog_core::log::ItemState::NotDone,
|
||||||
|
// })
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,11 @@ use hyperlog_core::log::GraphItem;
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{prelude::*, widgets::*};
|
||||||
|
|
||||||
use crate::models::Msg;
|
use crate::{
|
||||||
|
commands::{update_item::UpdateItemCommandExt, IntoCommand},
|
||||||
|
models::Msg,
|
||||||
|
state::SharedState,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{InputBuffer, InputField};
|
use super::{InputBuffer, InputField};
|
||||||
|
|
||||||
@@ -26,10 +30,13 @@ pub struct EditItemState {
|
|||||||
item: GraphItem,
|
item: GraphItem,
|
||||||
|
|
||||||
focused: EditItemFocused,
|
focused: EditItemFocused,
|
||||||
|
|
||||||
|
state: SharedState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditItemState {
|
impl EditItemState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
state: &SharedState,
|
||||||
root: impl Into<String>,
|
root: impl Into<String>,
|
||||||
path: impl IntoIterator<Item = impl Into<String>>,
|
path: impl IntoIterator<Item = impl Into<String>>,
|
||||||
item: &GraphItem,
|
item: &GraphItem,
|
||||||
@@ -47,6 +54,8 @@ impl EditItemState {
|
|||||||
title.set_position(title_len);
|
title.set_position(title_len);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
state: state.clone(),
|
||||||
|
|
||||||
root,
|
root,
|
||||||
path,
|
path,
|
||||||
|
|
||||||
@@ -82,24 +91,36 @@ impl EditItemState {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_command(&self) -> Option<hyperlog_core::commander::Command> {
|
pub fn get_command(&self) -> Option<impl IntoCommand> {
|
||||||
let title = self.title.string();
|
let title = self.title.string();
|
||||||
let description = self.description.string();
|
let description = self.description.string();
|
||||||
|
|
||||||
if !title.is_empty() {
|
if !title.is_empty() {
|
||||||
let path = self.path.clone();
|
let path = self.path.clone();
|
||||||
|
|
||||||
Some(hyperlog_core::commander::Command::UpdateItem {
|
Some(self.state.update_item_command().command(
|
||||||
root: self.root.clone(),
|
&self.root,
|
||||||
path,
|
&path.iter().map(|s| s.as_str()).collect_vec(),
|
||||||
title: title.trim().into(),
|
title.trim(),
|
||||||
description: description.trim().into(),
|
description.trim(),
|
||||||
state: match &self.item {
|
match &self.item {
|
||||||
GraphItem::User(_) => Default::default(),
|
GraphItem::User(_) => Default::default(),
|
||||||
GraphItem::Section(_) => Default::default(),
|
GraphItem::Section(_) => Default::default(),
|
||||||
GraphItem::Item { state, .. } => state.clone(),
|
GraphItem::Item { state, .. } => state.clone(),
|
||||||
},
|
},
|
||||||
})
|
))
|
||||||
|
|
||||||
|
// Some(commander::Command::UpdateItem {
|
||||||
|
// root: self.root.clone(),
|
||||||
|
// path,
|
||||||
|
// title: title.trim().into(),
|
||||||
|
// description: description.trim().into(),
|
||||||
|
// state: match &self.item {
|
||||||
|
// GraphItem::User(_) => Default::default(),
|
||||||
|
// GraphItem::Section(_) => Default::default(),
|
||||||
|
// GraphItem::Item { state, .. } => state.clone(),
|
||||||
|
// },
|
||||||
|
// })
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@@ -6,10 +6,13 @@ pub enum Commands {
|
|||||||
WriteQuit,
|
WriteQuit,
|
||||||
Archive,
|
Archive,
|
||||||
CreateSection { name: String },
|
CreateSection { name: String },
|
||||||
|
CreateItem { name: String },
|
||||||
|
CreateBelow { name: String },
|
||||||
Edit,
|
Edit,
|
||||||
|
|
||||||
ShowAll,
|
ShowAll,
|
||||||
HideDone,
|
HideDone,
|
||||||
|
Test,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Commands {
|
impl Commands {
|
||||||
@@ -39,9 +42,16 @@ impl CommandParser {
|
|||||||
"cs" | "create-section" => rest.first().map(|name| Commands::CreateSection {
|
"cs" | "create-section" => rest.first().map(|name| Commands::CreateSection {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
}),
|
}),
|
||||||
|
"ci" | "create-item" => Some(Commands::CreateItem {
|
||||||
|
name: rest.join(" ").to_string(),
|
||||||
|
}),
|
||||||
|
"cb" | "create-below" => Some(Commands::CreateBelow {
|
||||||
|
name: rest.join(" ").to_string(),
|
||||||
|
}),
|
||||||
"e" | "edit" => Some(Commands::Edit),
|
"e" | "edit" => Some(Commands::Edit),
|
||||||
"show-all" => Some(Commands::ShowAll),
|
"show-all" => Some(Commands::ShowAll),
|
||||||
"hide-done" => Some(Commands::HideDone),
|
"hide-done" => Some(Commands::HideDone),
|
||||||
|
"test" => Some(Commands::Test),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
None => None,
|
None => None,
|
||||||
|
74
crates/hyperlog-tui/src/commander.rs
Normal file
74
crates/hyperlog-tui/src/commander.rs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
use hyperlog_core::log::ItemState;
|
||||||
|
use serde::Serialize;
|
||||||
|
use tonic::transport::Channel;
|
||||||
|
|
||||||
|
use crate::{events::Events, shared_engine::SharedEngine, storage::Storage};
|
||||||
|
|
||||||
|
mod local;
|
||||||
|
mod remote;
|
||||||
|
|
||||||
|
#[derive(Serialize, PartialEq, Eq, Debug, Clone)]
|
||||||
|
pub enum Command {
|
||||||
|
CreateRoot {
|
||||||
|
root: String,
|
||||||
|
},
|
||||||
|
CreateSection {
|
||||||
|
root: String,
|
||||||
|
path: Vec<String>,
|
||||||
|
},
|
||||||
|
CreateItem {
|
||||||
|
root: String,
|
||||||
|
path: Vec<String>,
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
state: ItemState,
|
||||||
|
},
|
||||||
|
UpdateItem {
|
||||||
|
root: String,
|
||||||
|
path: Vec<String>,
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
state: ItemState,
|
||||||
|
},
|
||||||
|
ToggleItem {
|
||||||
|
root: String,
|
||||||
|
path: Vec<String>,
|
||||||
|
},
|
||||||
|
Move {
|
||||||
|
root: String,
|
||||||
|
src: Vec<String>,
|
||||||
|
dest: Vec<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum CommanderVariant {
|
||||||
|
Local(local::Commander),
|
||||||
|
Remote(remote::Commander),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Commander {
|
||||||
|
variant: CommanderVariant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Commander {
|
||||||
|
pub fn local(engine: SharedEngine, storage: Storage, events: Events) -> anyhow::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
variant: CommanderVariant::Local(local::Commander::new(engine, storage, events)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remote(channel: Channel) -> anyhow::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
variant: CommanderVariant::Remote(remote::Commander::new(channel)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(&self, cmd: Command) -> anyhow::Result<()> {
|
||||||
|
match &self.variant {
|
||||||
|
CommanderVariant::Local(commander) => commander.execute(cmd),
|
||||||
|
CommanderVariant::Remote(commander) => commander.execute(cmd).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,48 +1,12 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use serde::Serialize;
|
use hyperlog_core::log::GraphItem;
|
||||||
|
|
||||||
use crate::{
|
use crate::{events::Events, shared_engine::SharedEngine, storage::Storage};
|
||||||
events::Events,
|
|
||||||
log::{GraphItem, ItemState},
|
|
||||||
shared_engine::SharedEngine,
|
|
||||||
storage::Storage,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Serialize, PartialEq, Eq, Debug, Clone)]
|
use super::Command;
|
||||||
pub enum Command {
|
|
||||||
CreateRoot {
|
|
||||||
root: String,
|
|
||||||
},
|
|
||||||
CreateSection {
|
|
||||||
root: String,
|
|
||||||
path: Vec<String>,
|
|
||||||
},
|
|
||||||
CreateItem {
|
|
||||||
root: String,
|
|
||||||
path: Vec<String>,
|
|
||||||
title: String,
|
|
||||||
description: String,
|
|
||||||
state: ItemState,
|
|
||||||
},
|
|
||||||
UpdateItem {
|
|
||||||
root: String,
|
|
||||||
path: Vec<String>,
|
|
||||||
title: String,
|
|
||||||
description: String,
|
|
||||||
state: ItemState,
|
|
||||||
},
|
|
||||||
ToggleItem {
|
|
||||||
root: String,
|
|
||||||
path: Vec<String>,
|
|
||||||
},
|
|
||||||
Move {
|
|
||||||
root: String,
|
|
||||||
src: Vec<String>,
|
|
||||||
dest: Vec<String>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Commander {
|
pub struct Commander {
|
||||||
engine: SharedEngine,
|
engine: SharedEngine,
|
||||||
storage: Storage,
|
storage: Storage,
|
149
crates/hyperlog-tui/src/commander/remote.rs
Normal file
149
crates/hyperlog-tui/src/commander/remote.rs
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
use hyperlog_protos::hyperlog::{graph_client::GraphClient, *};
|
||||||
|
use tonic::transport::Channel;
|
||||||
|
|
||||||
|
use super::Command;
|
||||||
|
|
||||||
|
#[allow(dead_code, unused_variables)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Commander {
|
||||||
|
channel: Channel,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code, unused_variables)]
|
||||||
|
impl Commander {
|
||||||
|
pub fn new(channel: Channel) -> anyhow::Result<Self> {
|
||||||
|
Ok(Self { channel })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute(&self, cmd: Command) -> anyhow::Result<()> {
|
||||||
|
tracing::debug!("executing event: {}", serde_json::to_string(&cmd)?);
|
||||||
|
|
||||||
|
match cmd.clone() {
|
||||||
|
Command::CreateRoot { root } => {
|
||||||
|
let channel = self.channel.clone();
|
||||||
|
|
||||||
|
let mut client = GraphClient::new(channel);
|
||||||
|
|
||||||
|
let request = tonic::Request::new(CreateRootRequest { root });
|
||||||
|
let response = client.create_root(request).await?;
|
||||||
|
let res = response.into_inner();
|
||||||
|
//self.engine.create_root(&root)?;
|
||||||
|
}
|
||||||
|
Command::CreateSection { root, path } => {
|
||||||
|
let channel = self.channel.clone();
|
||||||
|
|
||||||
|
let mut client = GraphClient::new(channel);
|
||||||
|
|
||||||
|
let request = tonic::Request::new(CreateSectionRequest { root, path });
|
||||||
|
let response = client.create_section(request).await?;
|
||||||
|
let res = response.into_inner();
|
||||||
|
|
||||||
|
// self.engine.create(
|
||||||
|
// &root,
|
||||||
|
// &path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||||
|
// GraphItem::Section(BTreeMap::default()),
|
||||||
|
// )?;
|
||||||
|
}
|
||||||
|
Command::CreateItem {
|
||||||
|
root,
|
||||||
|
path,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
state,
|
||||||
|
} => {
|
||||||
|
let channel = self.channel.clone();
|
||||||
|
|
||||||
|
let mut client = GraphClient::new(channel);
|
||||||
|
|
||||||
|
let request = tonic::Request::new(CreateItemRequest {
|
||||||
|
root,
|
||||||
|
path,
|
||||||
|
item: Some(ItemGraphItem {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
item_state: Some(match state {
|
||||||
|
hyperlog_core::log::ItemState::NotDone => {
|
||||||
|
item_graph_item::ItemState::NotDone(ItemStateNotDone {})
|
||||||
|
}
|
||||||
|
hyperlog_core::log::ItemState::Done => {
|
||||||
|
item_graph_item::ItemState::Done(ItemStateDone {})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
let response = client.create_item(request).await?;
|
||||||
|
let res = response.into_inner();
|
||||||
|
// self.engine.create(
|
||||||
|
// &root,
|
||||||
|
// &path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||||
|
// GraphItem::Item {
|
||||||
|
// title,
|
||||||
|
// description,
|
||||||
|
// state,
|
||||||
|
// },
|
||||||
|
// )?
|
||||||
|
}
|
||||||
|
Command::Move { root, src, dest } => {
|
||||||
|
todo!()
|
||||||
|
// self.engine.section_move(
|
||||||
|
// &root,
|
||||||
|
// &src.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||||
|
// &dest.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||||
|
// )?
|
||||||
|
}
|
||||||
|
Command::ToggleItem { root, path } => {
|
||||||
|
let channel = self.channel.clone();
|
||||||
|
|
||||||
|
let mut client = GraphClient::new(channel);
|
||||||
|
|
||||||
|
let request = tonic::Request::new(ToggleItemRequest { root, path });
|
||||||
|
let response = client.toggle_item(request).await?;
|
||||||
|
let res = response.into_inner();
|
||||||
|
}
|
||||||
|
Command::UpdateItem {
|
||||||
|
root,
|
||||||
|
path,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
state,
|
||||||
|
} => {
|
||||||
|
let channel = self.channel.clone();
|
||||||
|
|
||||||
|
let mut client = GraphClient::new(channel);
|
||||||
|
|
||||||
|
let request = tonic::Request::new(UpdateItemRequest {
|
||||||
|
root,
|
||||||
|
path,
|
||||||
|
item: Some(ItemGraphItem {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
item_state: Some(match state {
|
||||||
|
hyperlog_core::log::ItemState::NotDone => {
|
||||||
|
item_graph_item::ItemState::NotDone(ItemStateNotDone {})
|
||||||
|
}
|
||||||
|
hyperlog_core::log::ItemState::Done => {
|
||||||
|
item_graph_item::ItemState::Done(ItemStateDone {})
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
let response = client.update_item(request).await?;
|
||||||
|
let res = response.into_inner();
|
||||||
|
// self.engine.update_item(
|
||||||
|
// &root,
|
||||||
|
// &path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
|
||||||
|
// GraphItem::Item {
|
||||||
|
// title,
|
||||||
|
// description,
|
||||||
|
// state,
|
||||||
|
// },
|
||||||
|
// )?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// self.storage.store(&self.engine)?;
|
||||||
|
// self.events.enque_command(cmd)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,14 @@
|
|||||||
|
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||||
|
|
||||||
|
pub mod batch;
|
||||||
|
|
||||||
|
pub mod create_item;
|
||||||
|
pub mod create_section;
|
||||||
|
pub mod open_update_item_dialog;
|
||||||
|
pub mod toggle_item;
|
||||||
|
pub mod update_graph;
|
||||||
|
pub mod update_item;
|
||||||
|
|
||||||
use crate::models::Msg;
|
use crate::models::Msg;
|
||||||
|
|
||||||
pub trait IntoCommand {
|
pub trait IntoCommand {
|
||||||
@@ -6,7 +17,7 @@ pub trait IntoCommand {
|
|||||||
|
|
||||||
impl IntoCommand for () {
|
impl IntoCommand for () {
|
||||||
fn into_command(self) -> Command {
|
fn into_command(self) -> Command {
|
||||||
Command::new(|| None)
|
Command::new(|_| None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,16 +27,47 @@ impl IntoCommand for Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommandFunc = dyn FnOnce(Dispatch) -> Option<Msg>;
|
||||||
|
|
||||||
pub struct Command {
|
pub struct Command {
|
||||||
func: Box<dyn FnOnce() -> Option<Msg>>,
|
func: Box<CommandFunc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
pub fn new<T: FnOnce() -> Option<Msg> + 'static>(f: T) -> Self {
|
pub fn new<T: FnOnce(Dispatch) -> Option<Msg> + 'static>(f: T) -> Self {
|
||||||
Self { func: Box::new(f) }
|
Self { func: Box::new(f) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute(self) -> Option<Msg> {
|
pub fn execute(self, dispatch: Dispatch) -> Option<Msg> {
|
||||||
self.func.call_once(())
|
self.func.call_once((dispatch,))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_dispatch() -> (Dispatch, Receiver) {
|
||||||
|
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
(Dispatch { sender: tx }, Receiver { receiver: rx })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Dispatch {
|
||||||
|
sender: UnboundedSender<Msg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatch {
|
||||||
|
pub fn send(&self, msg: Msg) {
|
||||||
|
if let Err(e) = self.sender.send(msg) {
|
||||||
|
tracing::warn!("failed to send event: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Receiver {
|
||||||
|
receiver: UnboundedReceiver<Msg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Receiver {
|
||||||
|
pub async fn next(&mut self) -> Option<Msg> {
|
||||||
|
self.receiver.recv().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
crates/hyperlog-tui/src/commands/batch.rs
Normal file
41
crates/hyperlog-tui/src/commands/batch.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
use super::IntoCommand;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct BatchCommand {
|
||||||
|
commands: Vec<super::Command>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BatchCommand {
|
||||||
|
pub fn with(&mut self, cmd: impl IntoCommand) -> &mut Self {
|
||||||
|
self.commands.push(cmd.into_command());
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoCommand for Vec<super::Command> {
|
||||||
|
fn into_command(self) -> super::Command {
|
||||||
|
BatchCommand::from(self).into_command()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<super::Command>> for BatchCommand {
|
||||||
|
fn from(value: Vec<super::Command>) -> Self {
|
||||||
|
BatchCommand { commands: value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoCommand for BatchCommand {
|
||||||
|
fn into_command(self) -> super::Command {
|
||||||
|
super::Command::new(|dispatch| {
|
||||||
|
for command in self.commands {
|
||||||
|
let msg = command.execute(dispatch.clone());
|
||||||
|
if let Some(msg) = msg {
|
||||||
|
dispatch.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
73
crates/hyperlog-tui/src/commands/create_item.rs
Normal file
73
crates/hyperlog-tui/src/commands/create_item.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
use hyperlog_core::log::ItemState;
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
commander::{self, Commander},
|
||||||
|
models::{IOEvent, Msg},
|
||||||
|
state::SharedState,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct CreateItemCommand {
|
||||||
|
commander: Commander,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateItemCommand {
|
||||||
|
pub fn new(commander: Commander) -> Self {
|
||||||
|
Self { commander }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(
|
||||||
|
self,
|
||||||
|
root: &str,
|
||||||
|
path: &[&str],
|
||||||
|
title: &str,
|
||||||
|
description: &str,
|
||||||
|
state: &ItemState,
|
||||||
|
) -> super::Command {
|
||||||
|
let root = root.to_owned();
|
||||||
|
let path = path.iter().map(|s| s.to_string()).collect_vec();
|
||||||
|
let title = title.to_string();
|
||||||
|
let description = description.to_string();
|
||||||
|
let state = state.clone();
|
||||||
|
|
||||||
|
super::Command::new(|dispatch| {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
dispatch.send(Msg::ItemCreated(IOEvent::Initialized));
|
||||||
|
|
||||||
|
match self
|
||||||
|
.commander
|
||||||
|
.execute(commander::Command::CreateItem {
|
||||||
|
root,
|
||||||
|
path,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
state,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
dispatch.send(Msg::ItemCreated(IOEvent::Success(())));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
dispatch.send(Msg::ItemCreated(IOEvent::Failure(e.to_string())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CreateItemCommandExt {
|
||||||
|
fn create_item_command(&self) -> CreateItemCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateItemCommandExt for SharedState {
|
||||||
|
fn create_item_command(&self) -> CreateItemCommand {
|
||||||
|
CreateItemCommand::new(self.commander.clone())
|
||||||
|
}
|
||||||
|
}
|
59
crates/hyperlog-tui/src/commands/create_section.rs
Normal file
59
crates/hyperlog-tui/src/commands/create_section.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
commander::{self, Commander},
|
||||||
|
models::IOEvent,
|
||||||
|
state::SharedState,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct CreateSectionCommand {
|
||||||
|
commander: Commander,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateSectionCommand {
|
||||||
|
pub fn new(commander: Commander) -> Self {
|
||||||
|
Self { commander }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(self, root: &str, path: &[&str]) -> super::Command {
|
||||||
|
let root = root.to_owned();
|
||||||
|
let path = path.iter().map(|s| s.to_string()).collect_vec();
|
||||||
|
|
||||||
|
super::Command::new(|dispatch| {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
dispatch.send(crate::models::Msg::SectionCreated(IOEvent::Initialized));
|
||||||
|
|
||||||
|
match self
|
||||||
|
.commander
|
||||||
|
.execute(commander::Command::CreateSection { root, path })
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch.send(crate::models::Msg::SectionCreated(IOEvent::Success(())));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
dispatch.send(crate::models::Msg::SectionCreated(IOEvent::Failure(
|
||||||
|
e.to_string(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CreateSectionCommandExt {
|
||||||
|
fn create_section_command(&self) -> CreateSectionCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateSectionCommandExt for SharedState {
|
||||||
|
fn create_section_command(&self) -> CreateSectionCommand {
|
||||||
|
CreateSectionCommand::new(self.commander.clone())
|
||||||
|
}
|
||||||
|
}
|
59
crates/hyperlog-tui/src/commands/open_update_item_dialog.rs
Normal file
59
crates/hyperlog-tui/src/commands/open_update_item_dialog.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use crate::{
|
||||||
|
models::{IOEvent, Msg},
|
||||||
|
querier::Querier,
|
||||||
|
state::SharedState,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct OpenUpdateItemDialogCommand {
|
||||||
|
querier: Querier,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenUpdateItemDialogCommand {
|
||||||
|
pub fn new(querier: Querier) -> Self {
|
||||||
|
Self { querier }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(self, root: &str, path: Vec<String>) -> super::Command {
|
||||||
|
let root = root.to_string();
|
||||||
|
|
||||||
|
super::Command::new(|dispatch| {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
dispatch.send(Msg::OpenUpdateItemDialog(IOEvent::Initialized));
|
||||||
|
|
||||||
|
let item = match self.querier.get_async(&root, path).await {
|
||||||
|
Ok(item) => match item {
|
||||||
|
Some(item) => {
|
||||||
|
dispatch.send(Msg::OpenUpdateItemDialog(IOEvent::Success(())));
|
||||||
|
item
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
dispatch.send(Msg::OpenUpdateItemDialog(IOEvent::Failure(
|
||||||
|
"failed to find a valid item for path".into(),
|
||||||
|
)));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
dispatch.send(Msg::OpenUpdateItemDialog(IOEvent::Failure(e.to_string())));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatch.send(Msg::OpenEditItemDialog { item });
|
||||||
|
});
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait OpenUpdateItemDialogCommandExt {
|
||||||
|
fn open_update_item_dialog_command(&self) -> OpenUpdateItemDialogCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenUpdateItemDialogCommandExt for SharedState {
|
||||||
|
fn open_update_item_dialog_command(&self) -> OpenUpdateItemDialogCommand {
|
||||||
|
OpenUpdateItemDialogCommand::new(self.querier.clone())
|
||||||
|
}
|
||||||
|
}
|
59
crates/hyperlog-tui/src/commands/toggle_item.rs
Normal file
59
crates/hyperlog-tui/src/commands/toggle_item.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
commander::{self, Commander},
|
||||||
|
models::IOEvent,
|
||||||
|
state::SharedState,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ToggleItemCommand {
|
||||||
|
commander: Commander,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToggleItemCommand {
|
||||||
|
pub fn new(commander: Commander) -> Self {
|
||||||
|
Self { commander }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(self, root: &str, path: &[&str]) -> super::Command {
|
||||||
|
let root = root.to_owned();
|
||||||
|
let path = path.iter().map(|s| s.to_string()).collect_vec();
|
||||||
|
|
||||||
|
super::Command::new(|dispatch| {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
dispatch.send(crate::models::Msg::ItemToggled(IOEvent::Initialized));
|
||||||
|
|
||||||
|
match self
|
||||||
|
.commander
|
||||||
|
.execute(commander::Command::ToggleItem { root, path })
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch.send(crate::models::Msg::ItemToggled(IOEvent::Success(())));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
dispatch.send(crate::models::Msg::ItemToggled(IOEvent::Failure(
|
||||||
|
e.to_string(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ToggleItemCommandExt {
|
||||||
|
fn toggle_item_command(&self) -> ToggleItemCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToggleItemCommandExt for SharedState {
|
||||||
|
fn toggle_item_command(&self) -> ToggleItemCommand {
|
||||||
|
ToggleItemCommand::new(self.commander.clone())
|
||||||
|
}
|
||||||
|
}
|
61
crates/hyperlog-tui/src/commands/update_graph.rs
Normal file
61
crates/hyperlog-tui/src/commands/update_graph.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
models::{IOEvent, Msg},
|
||||||
|
querier::Querier,
|
||||||
|
state::SharedState,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct UpdateGraphCommand {
|
||||||
|
querier: Querier,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateGraphCommand {
|
||||||
|
pub fn new(querier: Querier) -> Self {
|
||||||
|
Self { querier }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(self, root: &str, path: &[&str]) -> super::Command {
|
||||||
|
let root = root.to_owned();
|
||||||
|
let path = path.iter().map(|i| i.to_string()).collect_vec();
|
||||||
|
|
||||||
|
super::Command::new(|dispatch| {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let now = std::time::SystemTime::now();
|
||||||
|
dispatch.send(Msg::GraphUpdated(IOEvent::Initialized));
|
||||||
|
|
||||||
|
match self.querier.get_async(&root, path).await {
|
||||||
|
Ok(Some(graph)) => {
|
||||||
|
dispatch.send(Msg::GraphUpdated(IOEvent::Optimistic(graph.clone())));
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch.send(Msg::GraphUpdated(IOEvent::Success(graph)))
|
||||||
|
}
|
||||||
|
Ok(None) => dispatch.send(Msg::GraphUpdated(IOEvent::Failure(
|
||||||
|
"graph was not found user root".into(),
|
||||||
|
))),
|
||||||
|
Err(e) => dispatch.send(Msg::GraphUpdated(IOEvent::Failure(format!("{e}")))),
|
||||||
|
}
|
||||||
|
|
||||||
|
let elapsed = now.elapsed().expect("to be able to get time");
|
||||||
|
tracing::trace!("UpdateGraphCommand took: {}nanos", elapsed.as_nanos());
|
||||||
|
});
|
||||||
|
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait UpdateGraphCommandExt {
|
||||||
|
fn update_graph_command(&self) -> UpdateGraphCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateGraphCommandExt for SharedState {
|
||||||
|
fn update_graph_command(&self) -> UpdateGraphCommand {
|
||||||
|
UpdateGraphCommand::new(self.querier.clone())
|
||||||
|
}
|
||||||
|
}
|
76
crates/hyperlog-tui/src/commands/update_item.rs
Normal file
76
crates/hyperlog-tui/src/commands/update_item.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
use hyperlog_core::log::ItemState;
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
commander::{self, Commander},
|
||||||
|
models::IOEvent,
|
||||||
|
state::SharedState,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct UpdateItemCommand {
|
||||||
|
commander: Commander,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateItemCommand {
|
||||||
|
pub fn new(commander: Commander) -> Self {
|
||||||
|
Self { commander }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(
|
||||||
|
self,
|
||||||
|
root: &str,
|
||||||
|
path: &[&str],
|
||||||
|
title: &str,
|
||||||
|
description: &str,
|
||||||
|
state: ItemState,
|
||||||
|
) -> super::Command {
|
||||||
|
let root = root.to_owned();
|
||||||
|
let path = path.iter().map(|s| s.to_string()).collect_vec();
|
||||||
|
let title = title.to_string();
|
||||||
|
let description = description.to_string();
|
||||||
|
let state = state.clone();
|
||||||
|
|
||||||
|
super::Command::new(|dispatch| {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
dispatch.send(crate::models::Msg::ItemUpdated(IOEvent::Initialized));
|
||||||
|
|
||||||
|
match self
|
||||||
|
.commander
|
||||||
|
.execute(commander::Command::UpdateItem {
|
||||||
|
root,
|
||||||
|
path,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
state,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(()) => {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch.send(crate::models::Msg::ItemUpdated(IOEvent::Success(())));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
dispatch.send(crate::models::Msg::ItemUpdated(IOEvent::Failure(
|
||||||
|
e.to_string(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait UpdateItemCommandExt {
|
||||||
|
fn update_item_command(&self) -> UpdateItemCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateItemCommandExt for SharedState {
|
||||||
|
fn update_item_command(&self) -> UpdateItemCommand {
|
||||||
|
UpdateItemCommand::new(self.commander.clone())
|
||||||
|
}
|
||||||
|
}
|
@@ -1,9 +1,18 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use hyperlog_core::log::GraphItem;
|
use hyperlog_core::log::GraphItem;
|
||||||
|
use itertools::Itertools;
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{prelude::*, widgets::*};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command_parser::Commands, components::movement_graph::GraphItemType, models::Msg,
|
command_parser::Commands,
|
||||||
|
commands::{
|
||||||
|
batch::BatchCommand, create_item::CreateItemCommandExt,
|
||||||
|
create_section::CreateSectionCommandExt,
|
||||||
|
open_update_item_dialog::OpenUpdateItemDialogCommandExt, toggle_item::ToggleItemCommandExt,
|
||||||
|
update_graph::UpdateGraphCommandExt, Command, IntoCommand,
|
||||||
|
},
|
||||||
|
components::movement_graph::GraphItemType,
|
||||||
|
models::{IOEvent, Msg},
|
||||||
state::SharedState,
|
state::SharedState,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -46,6 +55,31 @@ pub struct GraphExplorerState<'a> {
|
|||||||
graph: Option<GraphItem>,
|
graph: Option<GraphItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> GraphExplorerState<'a> {
|
||||||
|
pub fn update(&mut self, msg: &Msg) -> Option<Command> {
|
||||||
|
if let Msg::GraphUpdated(graph_update) = msg {
|
||||||
|
match graph_update {
|
||||||
|
IOEvent::Initialized => {
|
||||||
|
tracing::trace!("initialized graph");
|
||||||
|
}
|
||||||
|
IOEvent::Success(graph) => {
|
||||||
|
tracing::trace!("graph updated successfully");
|
||||||
|
self.graph = Some(graph.clone());
|
||||||
|
}
|
||||||
|
IOEvent::Failure(e) => {
|
||||||
|
tracing::error!("graph update failed: {}", e);
|
||||||
|
}
|
||||||
|
IOEvent::Optimistic(graph) => {
|
||||||
|
tracing::trace!("graph updated optimistically");
|
||||||
|
self.graph = Some(graph.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> GraphExplorer<'a> {
|
impl<'a> GraphExplorer<'a> {
|
||||||
pub fn new(root: String, state: SharedState) -> Self {
|
pub fn new(root: String, state: SharedState) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -60,19 +94,31 @@ impl<'a> GraphExplorer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_graph(&mut self) -> Result<&mut Self> {
|
pub fn new_update_graph(&self) -> Command {
|
||||||
|
self.state.update_graph_command().command(
|
||||||
|
&self.inner.root,
|
||||||
|
&self
|
||||||
|
.inner
|
||||||
|
.current_path
|
||||||
|
.map(|p| p.split(".").collect_vec())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_graph(&mut self) -> Result<&mut Self> {
|
||||||
let now = std::time::SystemTime::now();
|
let now = std::time::SystemTime::now();
|
||||||
|
|
||||||
let graph = self
|
let graph = self
|
||||||
.state
|
.state
|
||||||
.querier
|
.querier
|
||||||
.get(
|
.get_async(
|
||||||
&self.inner.root,
|
&self.inner.root,
|
||||||
self.inner
|
self.inner
|
||||||
.current_path
|
.current_path
|
||||||
.map(|p| p.split('.').collect::<Vec<_>>())
|
.map(|p| p.split('.').collect::<Vec<_>>())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
)
|
)
|
||||||
|
.await?
|
||||||
.ok_or(anyhow::anyhow!("graph should've had an item"))?;
|
.ok_or(anyhow::anyhow!("graph should've had an item"))?;
|
||||||
|
|
||||||
self.inner.graph = Some(graph);
|
self.inner.graph = Some(graph);
|
||||||
@@ -184,7 +230,9 @@ impl<'a> GraphExplorer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute_command(&mut self, command: &Commands) -> anyhow::Result<Option<Msg>> {
|
pub fn execute_command(&mut self, command: &Commands) -> anyhow::Result<Option<Command>> {
|
||||||
|
let mut batch = BatchCommand::default();
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
Commands::Archive => {
|
Commands::Archive => {
|
||||||
if !self.get_current_path().is_empty() {
|
if !self.get_current_path().is_empty() {
|
||||||
@@ -194,14 +242,55 @@ impl<'a> GraphExplorer<'a> {
|
|||||||
Commands::CreateSection { name } => {
|
Commands::CreateSection { name } => {
|
||||||
if !name.is_empty() {
|
if !name.is_empty() {
|
||||||
let mut path = self.get_current_path();
|
let mut path = self.get_current_path();
|
||||||
path.push(name.replace(" ", "-").replace(".", "-"));
|
path.push(name.replace(".", "-"));
|
||||||
|
|
||||||
self.state.commander.execute(
|
// self.state
|
||||||
hyperlog_core::commander::Command::CreateSection {
|
// .commander
|
||||||
root: self.inner.root.clone(),
|
// .execute(commander::Command::CreateSection {
|
||||||
path,
|
// root: self.inner.root.clone(),
|
||||||
},
|
// path,
|
||||||
)?;
|
// })?;
|
||||||
|
|
||||||
|
let cmd = self.state.create_section_command().command(
|
||||||
|
&self.inner.root,
|
||||||
|
&path.iter().map(|i| i.as_str()).collect_vec(),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.with(cmd.into_command());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Commands::CreateItem { name } => {
|
||||||
|
if !name.is_empty() {
|
||||||
|
let mut path = self.get_current_path();
|
||||||
|
path.push(name.replace(".", " "));
|
||||||
|
let cmd = self.state.create_item_command().command(
|
||||||
|
&self.inner.root,
|
||||||
|
&path.iter().map(|i| i.as_str()).collect_vec(),
|
||||||
|
name,
|
||||||
|
"",
|
||||||
|
&hyperlog_core::log::ItemState::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.with(cmd.into_command());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Commands::CreateBelow { name } => {
|
||||||
|
if !name.is_empty() {
|
||||||
|
let path = self.get_current_path();
|
||||||
|
if let Some((_, path)) = path.split_last() {
|
||||||
|
let mut path = path.to_vec();
|
||||||
|
path.push(name.replace(".", " "));
|
||||||
|
|
||||||
|
let cmd = self.state.create_item_command().command(
|
||||||
|
&self.inner.root,
|
||||||
|
&path.iter().map(|i| i.as_str()).collect_vec(),
|
||||||
|
name,
|
||||||
|
"",
|
||||||
|
&hyperlog_core::log::ItemState::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.with(cmd.into_command());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Commands::Edit => {
|
Commands::Edit => {
|
||||||
@@ -218,11 +307,11 @@ impl<'a> GraphExplorer<'a> {
|
|||||||
todo!("cannot edit section at the moment")
|
todo!("cannot edit section at the moment")
|
||||||
}
|
}
|
||||||
GraphItemType::Item { .. } => {
|
GraphItemType::Item { .. } => {
|
||||||
if let Some(item) = self.state.querier.get(&self.inner.root, path) {
|
batch.with(
|
||||||
if let GraphItem::Item { .. } = item {
|
self.state
|
||||||
return Ok(Some(Msg::OpenEditItemDialog { item }));
|
.open_update_item_dialog_command()
|
||||||
}
|
.command(&self.inner.root, path),
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,29 +322,54 @@ impl<'a> GraphExplorer<'a> {
|
|||||||
Commands::HideDone => {
|
Commands::HideDone => {
|
||||||
self.inner.display_options.filter_by = FilterBy::NotDone;
|
self.inner.display_options.filter_by = FilterBy::NotDone;
|
||||||
}
|
}
|
||||||
|
Commands::Test => {
|
||||||
|
return Ok(Some(Command::new(|dispatch| {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
dispatch.send(Msg::MoveDown);
|
||||||
|
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||||
|
dispatch.send(Msg::EnterViewMode);
|
||||||
|
});
|
||||||
|
|
||||||
|
None
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_graph()?;
|
//self.update_graph()?;
|
||||||
|
|
||||||
Ok(None)
|
Ok(Some(batch.into_command()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn interact(&mut self) -> anyhow::Result<()> {
|
pub(crate) fn interact(&mut self) -> anyhow::Result<Command> {
|
||||||
|
let mut batch = BatchCommand::default();
|
||||||
|
|
||||||
if !self.get_current_path().is_empty() {
|
if !self.get_current_path().is_empty() {
|
||||||
tracing::info!("toggling state of items");
|
tracing::info!("toggling state of items");
|
||||||
|
|
||||||
self.state
|
// self.state
|
||||||
.commander
|
// .commander
|
||||||
.execute(hyperlog_core::commander::Command::ToggleItem {
|
// .execute(commander::Command::ToggleItem {
|
||||||
root: self.inner.root.to_string(),
|
// root: self.inner.root.to_string(),
|
||||||
path: self.get_current_path(),
|
// path: self.get_current_path(),
|
||||||
})?;
|
// })?;
|
||||||
|
|
||||||
|
let cmd = self.state.toggle_item_command().command(
|
||||||
|
&self.inner.root,
|
||||||
|
&self
|
||||||
|
.get_current_path()
|
||||||
|
.iter()
|
||||||
|
.map(|i| i.as_str())
|
||||||
|
.collect_vec(),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.with(cmd.into_command());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_graph()?;
|
//self.update_graph()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(batch.into_command())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
56
crates/hyperlog-tui/src/core_state.rs
Normal file
56
crates/hyperlog-tui/src/core_state.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
use tonic::transport::Channel;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
commander::Commander, events::Events, querier::Querier, shared_engine::SharedEngine,
|
||||||
|
storage::Storage,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct State {
|
||||||
|
engine: SharedEngine,
|
||||||
|
pub storage: Storage,
|
||||||
|
events: Events,
|
||||||
|
|
||||||
|
pub commander: Commander,
|
||||||
|
pub querier: Querier,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Backend {
|
||||||
|
Local,
|
||||||
|
Remote,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub async fn new(backend: Backend) -> anyhow::Result<Self> {
|
||||||
|
let storage = Storage::new();
|
||||||
|
let engine = storage.load()?;
|
||||||
|
let events = Events::default();
|
||||||
|
let engine = SharedEngine::from(engine);
|
||||||
|
|
||||||
|
let (querier, commander) = match backend {
|
||||||
|
Backend::Local => (
|
||||||
|
Querier::local(&engine),
|
||||||
|
Commander::local(engine.clone(), storage.clone(), events.clone())?,
|
||||||
|
),
|
||||||
|
Backend::Remote => {
|
||||||
|
let channel = Channel::from_static("http://localhost:4000")
|
||||||
|
.connect()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
(
|
||||||
|
Querier::remote(channel.clone()).await?,
|
||||||
|
Commander::remote(channel)?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
engine: engine.clone(),
|
||||||
|
storage: storage.clone(),
|
||||||
|
events: events.clone(),
|
||||||
|
|
||||||
|
commander,
|
||||||
|
querier,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +1,7 @@
|
|||||||
use std::{collections::BTreeMap, fmt::Display};
|
use std::{collections::BTreeMap, fmt::Display};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
|
use hyperlog_core::log::{Graph, GraphItem, ItemState};
|
||||||
use crate::log::{Graph, GraphItem, ItemState};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Engine {
|
pub struct Engine {
|
||||||
@@ -218,10 +217,9 @@ impl Display for Engine {
|
|||||||
mod test {
|
mod test {
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use hyperlog_core::log::{GraphItem, ItemState};
|
||||||
use similar_asserts::assert_eq;
|
use similar_asserts::assert_eq;
|
||||||
|
|
||||||
use crate::log::{GraphItem, ItemState};
|
|
||||||
|
|
||||||
use super::Engine;
|
use super::Engine;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -249,7 +247,7 @@ mod test {
|
|||||||
.create(
|
.create(
|
||||||
"kjuulh",
|
"kjuulh",
|
||||||
&["some-section"],
|
&["some-section"],
|
||||||
crate::log::GraphItem::Section(BTreeMap::default()),
|
GraphItem::Section(BTreeMap::default()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -275,7 +273,7 @@ mod test {
|
|||||||
.create(
|
.create(
|
||||||
"kjuulh",
|
"kjuulh",
|
||||||
&["some-section"],
|
&["some-section"],
|
||||||
crate::log::GraphItem::Section(BTreeMap::default()),
|
GraphItem::Section(BTreeMap::default()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -283,7 +281,7 @@ mod test {
|
|||||||
.create(
|
.create(
|
||||||
"kjuulh",
|
"kjuulh",
|
||||||
&["some-section", "some-sub-section"],
|
&["some-section", "some-sub-section"],
|
||||||
crate::log::GraphItem::Section(BTreeMap::default()),
|
GraphItem::Section(BTreeMap::default()),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@@ -1,13 +1,16 @@
|
|||||||
|
#![feature(map_try_insert)]
|
||||||
#![feature(fn_traits)]
|
#![feature(fn_traits)]
|
||||||
|
#![feature(let_chains)]
|
||||||
|
|
||||||
use std::{io::Stdout, time::Duration};
|
use std::io::Stdout;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use app::{render_app, App};
|
use app::{render_app, App};
|
||||||
use commands::IntoCommand;
|
use commands::{Dispatch, IntoCommand, Receiver};
|
||||||
use components::graph_explorer::GraphExplorer;
|
use components::graph_explorer::GraphExplorer;
|
||||||
use crossterm::event::{self, Event, KeyCode};
|
use core_state::State;
|
||||||
use hyperlog_core::state::State;
|
use crossterm::event::{Event, KeyCode, KeyEventKind};
|
||||||
|
use futures::{FutureExt, StreamExt};
|
||||||
use models::{EditMsg, Msg};
|
use models::{EditMsg, Msg};
|
||||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||||
|
|
||||||
@@ -19,7 +22,16 @@ pub(crate) mod app;
|
|||||||
pub(crate) mod command_parser;
|
pub(crate) mod command_parser;
|
||||||
pub(crate) mod commands;
|
pub(crate) mod commands;
|
||||||
pub(crate) mod components;
|
pub(crate) mod components;
|
||||||
pub(crate) mod state;
|
|
||||||
|
pub mod commander;
|
||||||
|
pub mod core_state;
|
||||||
|
pub mod shared_engine;
|
||||||
|
pub mod state;
|
||||||
|
|
||||||
|
mod engine;
|
||||||
|
mod events;
|
||||||
|
mod querier;
|
||||||
|
mod storage;
|
||||||
|
|
||||||
mod logging;
|
mod logging;
|
||||||
mod terminal;
|
mod terminal;
|
||||||
@@ -33,13 +45,13 @@ pub async fn execute(state: State) -> Result<()> {
|
|||||||
let state = SharedState::from(state);
|
let state = SharedState::from(state);
|
||||||
|
|
||||||
let mut terminal = TerminalInstance::new()?;
|
let mut terminal = TerminalInstance::new()?;
|
||||||
run(&mut terminal, state).context("app loop failed")?;
|
run(&mut terminal, state).await.context("app loop failed")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>, state: SharedState) -> Result<()> {
|
async fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>, state: SharedState) -> Result<()> {
|
||||||
let root = match state.querier.get_available_roots() {
|
let root = match state.querier.get_available_roots_async().await? {
|
||||||
// TODO: maybe present choose root screen
|
// TODO: maybe present choose root screen
|
||||||
Some(roots) => roots.first().cloned().unwrap(),
|
Some(roots) => roots.first().cloned().unwrap(),
|
||||||
None => {
|
None => {
|
||||||
@@ -49,14 +61,25 @@ fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>, state: SharedState) ->
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut graph_explorer = GraphExplorer::new(root.clone(), state.clone());
|
let mut graph_explorer = GraphExplorer::new(root.clone(), state.clone());
|
||||||
graph_explorer.update_graph()?;
|
graph_explorer.update_graph().await?;
|
||||||
|
|
||||||
let mut app = App::new(&root, state.clone(), graph_explorer);
|
let mut app = App::new(&root, state.clone(), graph_explorer);
|
||||||
|
let (dispatch, mut receiver) = commands::create_dispatch();
|
||||||
|
let mut event_stream = crossterm::event::EventStream::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
terminal.draw(|f| render_app(f, &mut app))?;
|
terminal.draw(|f| render_app(f, &mut app))?;
|
||||||
|
|
||||||
if update(terminal, &mut app)?.should_quit() {
|
if update(
|
||||||
|
terminal,
|
||||||
|
&mut app,
|
||||||
|
&dispatch,
|
||||||
|
&mut receiver,
|
||||||
|
&mut event_stream,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.should_quit()
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,53 +98,107 @@ impl UpdateConclusion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(
|
async fn update<'a>(
|
||||||
_terminal: &mut Terminal<CrosstermBackend<Stdout>>,
|
_terminal: &mut Terminal<CrosstermBackend<Stdout>>,
|
||||||
app: &mut App,
|
app: &mut App<'a>,
|
||||||
|
dispatch: &Dispatch,
|
||||||
|
receiver: &mut Receiver,
|
||||||
|
event_stream: &mut crossterm::event::EventStream,
|
||||||
) -> Result<UpdateConclusion> {
|
) -> Result<UpdateConclusion> {
|
||||||
if event::poll(Duration::from_millis(250)).context("event poll failed")? {
|
let cross_event = event_stream.next().fuse();
|
||||||
if let Event::Key(key) = event::read().context("event read failed")? {
|
|
||||||
let mut cmd = match &app.mode {
|
|
||||||
app::Mode::View => match key.code {
|
|
||||||
KeyCode::Enter => app.update(Msg::Interact)?,
|
|
||||||
KeyCode::Char('l') => app.update(Msg::MoveRight)?,
|
|
||||||
KeyCode::Char('h') => app.update(Msg::MoveLeft)?,
|
|
||||||
KeyCode::Char('j') => app.update(Msg::MoveDown)?,
|
|
||||||
KeyCode::Char('k') => app.update(Msg::MoveUp)?,
|
|
||||||
KeyCode::Char('a') => {
|
|
||||||
// TODO: batch commands
|
|
||||||
app.update(Msg::OpenCreateItemDialog)?;
|
|
||||||
app.update(Msg::EnterInsertMode)?
|
|
||||||
}
|
|
||||||
KeyCode::Char('i') => app.update(Msg::EnterInsertMode)?,
|
|
||||||
KeyCode::Char(':') => app.update(Msg::EnterCommandMode)?,
|
|
||||||
_ => return Ok(UpdateConclusion(false)),
|
|
||||||
},
|
|
||||||
|
|
||||||
app::Mode::Command | app::Mode::Insert => match key.code {
|
let mut handle_key_event = |maybe_event| -> anyhow::Result<UpdateConclusion> {
|
||||||
KeyCode::Backspace => app.update(Msg::Edit(EditMsg::Delete))?,
|
match maybe_event {
|
||||||
KeyCode::Enter => app.update(Msg::Edit(EditMsg::InsertNewLine))?,
|
Some(Ok(e)) => {
|
||||||
KeyCode::Tab => app.update(Msg::Edit(EditMsg::InsertTab))?,
|
if let Event::Key(key) = e
|
||||||
KeyCode::Delete => app.update(Msg::Edit(EditMsg::DeleteNext))?,
|
&& key.kind == KeyEventKind::Press
|
||||||
KeyCode::Char(c) => app.update(Msg::Edit(EditMsg::InsertChar(c)))?,
|
{
|
||||||
KeyCode::Left => app.update(Msg::Edit(EditMsg::MoveLeft))?,
|
let mut cmd = match &app.mode {
|
||||||
KeyCode::Right => app.update(Msg::Edit(EditMsg::MoveRight))?,
|
app::Mode::View => match key.code {
|
||||||
KeyCode::Esc => app.update(Msg::EnterViewMode)?,
|
KeyCode::Enter => app.update(Msg::Interact)?,
|
||||||
_ => return Ok(UpdateConclusion(false)),
|
KeyCode::Char('l') => app.update(Msg::MoveRight)?,
|
||||||
},
|
KeyCode::Char('h') => app.update(Msg::MoveLeft)?,
|
||||||
};
|
KeyCode::Char('j') => app.update(Msg::MoveDown)?,
|
||||||
|
KeyCode::Char('k') => app.update(Msg::MoveUp)?,
|
||||||
|
KeyCode::Char('a') => {
|
||||||
|
// TODO: batch commands
|
||||||
|
app.update(Msg::OpenCreateItemDialog)?;
|
||||||
|
app.update(Msg::EnterInsertMode)?
|
||||||
|
}
|
||||||
|
KeyCode::Char('o') => {
|
||||||
|
// TODO: batch commands
|
||||||
|
app.update(Msg::OpenCreateItemDialogBelow)?;
|
||||||
|
app.update(Msg::EnterInsertMode)?
|
||||||
|
}
|
||||||
|
KeyCode::Char('i') => app.update(Msg::EnterInsertMode)?,
|
||||||
|
KeyCode::Char(':') => app.update(Msg::EnterCommandMode)?,
|
||||||
|
_ => return Ok(UpdateConclusion(false)),
|
||||||
|
},
|
||||||
|
|
||||||
loop {
|
app::Mode::Command | app::Mode::Insert => match key.code {
|
||||||
let msg = cmd.into_command().execute();
|
KeyCode::Backspace => app.update(Msg::Edit(EditMsg::Delete))?,
|
||||||
match msg {
|
KeyCode::Enter => app.update(Msg::Edit(EditMsg::InsertNewLine))?,
|
||||||
Some(msg) => {
|
KeyCode::Tab => app.update(Msg::Edit(EditMsg::InsertTab))?,
|
||||||
if let Msg::QuitApp = msg {
|
KeyCode::Delete => app.update(Msg::Edit(EditMsg::DeleteNext))?,
|
||||||
return Ok(UpdateConclusion(true));
|
KeyCode::Char(c) => app.update(Msg::Edit(EditMsg::InsertChar(c)))?,
|
||||||
|
KeyCode::Left => app.update(Msg::Edit(EditMsg::MoveLeft))?,
|
||||||
|
KeyCode::Right => app.update(Msg::Edit(EditMsg::MoveRight))?,
|
||||||
|
KeyCode::Esc => app.update(Msg::EnterViewMode)?,
|
||||||
|
_ => return Ok(UpdateConclusion(false)),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let msg = cmd.into_command().execute(dispatch.clone());
|
||||||
|
match msg {
|
||||||
|
Some(msg) => {
|
||||||
|
if let Msg::QuitApp = msg {
|
||||||
|
return Ok(UpdateConclusion(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = app.update(msg)?;
|
||||||
|
}
|
||||||
|
None => break,
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = app.update(msg)?;
|
|
||||||
}
|
}
|
||||||
None => break,
|
}
|
||||||
|
}
|
||||||
|
Some(Err(e)) => {
|
||||||
|
tracing::warn!("failed to send event: {}", e);
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(UpdateConclusion(false))
|
||||||
|
};
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
maybe_event = cross_event => {
|
||||||
|
let conclusion = handle_key_event(maybe_event)?;
|
||||||
|
|
||||||
|
return Ok(conclusion)
|
||||||
|
},
|
||||||
|
|
||||||
|
msg = receiver.next() => {
|
||||||
|
if let Some(msg) = msg {
|
||||||
|
if let Msg::QuitApp = msg {
|
||||||
|
return Ok(UpdateConclusion(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cmd = app.update(msg)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let msg = cmd.into_command().execute(dispatch.clone());
|
||||||
|
match msg {
|
||||||
|
Some(msg) => {
|
||||||
|
if let Msg::QuitApp = msg {
|
||||||
|
return Ok(UpdateConclusion(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd = app.update(msg)?;
|
||||||
|
}
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ pub enum Msg {
|
|||||||
MoveUp,
|
MoveUp,
|
||||||
QuitApp,
|
QuitApp,
|
||||||
OpenCreateItemDialog,
|
OpenCreateItemDialog,
|
||||||
|
OpenCreateItemDialogBelow,
|
||||||
OpenEditItemDialog { item: GraphItem },
|
OpenEditItemDialog { item: GraphItem },
|
||||||
Interact,
|
Interact,
|
||||||
|
|
||||||
@@ -20,11 +21,27 @@ pub enum Msg {
|
|||||||
SubmitCommand { command: String },
|
SubmitCommand { command: String },
|
||||||
|
|
||||||
Edit(EditMsg),
|
Edit(EditMsg),
|
||||||
|
|
||||||
|
GraphUpdated(IOEvent<GraphItem>),
|
||||||
|
ItemCreated(IOEvent<()>),
|
||||||
|
ItemUpdated(IOEvent<()>),
|
||||||
|
SectionCreated(IOEvent<()>),
|
||||||
|
ItemToggled(IOEvent<()>),
|
||||||
|
|
||||||
|
OpenUpdateItemDialog(IOEvent<()>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum IOEvent<T> {
|
||||||
|
Initialized,
|
||||||
|
Optimistic(T),
|
||||||
|
Success(T),
|
||||||
|
Failure(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoCommand for Msg {
|
impl IntoCommand for Msg {
|
||||||
fn into_command(self) -> crate::commands::Command {
|
fn into_command(self) -> crate::commands::Command {
|
||||||
Command::new(|| Some(self))
|
Command::new(|_| Some(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
68
crates/hyperlog-tui/src/querier.rs
Normal file
68
crates/hyperlog-tui/src/querier.rs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
use hyperlog_core::log::GraphItem;
|
||||||
|
use tonic::transport::Channel;
|
||||||
|
|
||||||
|
use crate::shared_engine::SharedEngine;
|
||||||
|
|
||||||
|
mod local;
|
||||||
|
mod remote;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum QuerierVariant {
|
||||||
|
Local(local::Querier),
|
||||||
|
Remote(remote::Querier),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Querier {
|
||||||
|
variant: QuerierVariant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Querier {
|
||||||
|
pub fn local(engine: &SharedEngine) -> Self {
|
||||||
|
Self {
|
||||||
|
variant: QuerierVariant::Local(local::Querier::new(engine)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remote(channel: Channel) -> anyhow::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
variant: QuerierVariant::Remote(remote::Querier::new(channel).await?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(
|
||||||
|
&self,
|
||||||
|
root: &str,
|
||||||
|
path: impl IntoIterator<Item = impl Into<String>>,
|
||||||
|
) -> Option<GraphItem> {
|
||||||
|
match &self.variant {
|
||||||
|
QuerierVariant::Local(querier) => querier.get(root, path),
|
||||||
|
QuerierVariant::Remote(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_async(
|
||||||
|
&self,
|
||||||
|
root: &str,
|
||||||
|
path: impl IntoIterator<Item = impl Into<String>>,
|
||||||
|
) -> anyhow::Result<Option<GraphItem>> {
|
||||||
|
match &self.variant {
|
||||||
|
QuerierVariant::Local(querier) => Ok(querier.get(root, path)),
|
||||||
|
QuerierVariant::Remote(querier) => querier.get(root, path).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_available_roots(&self) -> Option<Vec<String>> {
|
||||||
|
match &self.variant {
|
||||||
|
QuerierVariant::Local(querier) => querier.get_available_roots(),
|
||||||
|
QuerierVariant::Remote(_) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_available_roots_async(&self) -> anyhow::Result<Option<Vec<String>>> {
|
||||||
|
match &self.variant {
|
||||||
|
QuerierVariant::Local(querier) => Ok(querier.get_available_roots()),
|
||||||
|
QuerierVariant::Remote(querier) => querier.get_available_roots().await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,12 +1,17 @@
|
|||||||
use crate::{log::GraphItem, shared_engine::SharedEngine};
|
use hyperlog_core::log::GraphItem;
|
||||||
|
|
||||||
|
use crate::shared_engine::SharedEngine;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Querier {
|
pub struct Querier {
|
||||||
engine: SharedEngine,
|
engine: SharedEngine,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Querier {
|
impl Querier {
|
||||||
pub fn new(engine: SharedEngine) -> Self {
|
pub fn new(engine: &SharedEngine) -> Self {
|
||||||
Self { engine }
|
Self {
|
||||||
|
engine: engine.clone(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_available_roots(&self) -> Option<Vec<String>> {
|
pub fn get_available_roots(&self) -> Option<Vec<String>> {
|
||||||
@@ -31,7 +36,10 @@ impl Querier {
|
|||||||
path.len()
|
path.len()
|
||||||
);
|
);
|
||||||
|
|
||||||
self.engine
|
let item = self
|
||||||
.get(root, &path.iter().map(|i| i.as_str()).collect::<Vec<_>>())
|
.engine
|
||||||
|
.get(root, &path.iter().map(|i| i.as_str()).collect::<Vec<_>>());
|
||||||
|
|
||||||
|
item
|
||||||
}
|
}
|
||||||
}
|
}
|
118
crates/hyperlog-tui/src/querier/remote.rs
Normal file
118
crates/hyperlog-tui/src/querier/remote.rs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use hyperlog_core::log::GraphItem;
|
||||||
|
use hyperlog_protos::hyperlog::{
|
||||||
|
graph_client::GraphClient, graph_item::Contents, GetAvailableRootsRequest, GetRequest,
|
||||||
|
};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use tonic::transport::Channel;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Querier {
|
||||||
|
channel: Channel,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code, unused_variables)]
|
||||||
|
impl Querier {
|
||||||
|
pub async fn new(channel: Channel) -> anyhow::Result<Self> {
|
||||||
|
Ok(Self { channel })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_available_roots(&self) -> anyhow::Result<Option<Vec<String>>> {
|
||||||
|
let channel = self.channel.clone();
|
||||||
|
|
||||||
|
let mut client = GraphClient::new(channel);
|
||||||
|
|
||||||
|
let request = tonic::Request::new(GetAvailableRootsRequest {});
|
||||||
|
let response = client.get_available_roots(request).await?;
|
||||||
|
|
||||||
|
let roots = response.into_inner();
|
||||||
|
|
||||||
|
if roots.roots.is_empty() {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Ok(Some(roots.roots))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(
|
||||||
|
&self,
|
||||||
|
root: &str,
|
||||||
|
path: impl IntoIterator<Item = impl Into<String>>,
|
||||||
|
) -> anyhow::Result<Option<GraphItem>> {
|
||||||
|
let paths = path.into_iter().map(|i| i.into()).collect_vec();
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
"quering: root:({}), path:({}), len: ({}))",
|
||||||
|
root,
|
||||||
|
paths.join("."),
|
||||||
|
paths.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
let channel = self.channel.clone();
|
||||||
|
|
||||||
|
let mut client = GraphClient::new(channel);
|
||||||
|
|
||||||
|
let request = tonic::Request::new(GetRequest {
|
||||||
|
root: root.into(),
|
||||||
|
paths,
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = client.get(request).await?;
|
||||||
|
|
||||||
|
let graph_item = response.into_inner();
|
||||||
|
|
||||||
|
if let Some(item) = graph_item.item {
|
||||||
|
let local_graph = transform_proto_to_local(&item);
|
||||||
|
Ok(local_graph)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_proto_to_local(input: &hyperlog_protos::hyperlog::GraphItem) -> Option<GraphItem> {
|
||||||
|
match &input.contents {
|
||||||
|
Some(item) => match item {
|
||||||
|
Contents::User(user) => {
|
||||||
|
let mut items = BTreeMap::new();
|
||||||
|
|
||||||
|
for (key, value) in &user.items {
|
||||||
|
if let Some(item) = transform_proto_to_local(value) {
|
||||||
|
items.insert(key.clone(), item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(GraphItem::User(items))
|
||||||
|
}
|
||||||
|
Contents::Section(section) => {
|
||||||
|
let mut items = BTreeMap::new();
|
||||||
|
|
||||||
|
for (key, value) in §ion.items {
|
||||||
|
if let Some(item) = transform_proto_to_local(value) {
|
||||||
|
items.insert(key.clone(), item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(GraphItem::Section(items))
|
||||||
|
}
|
||||||
|
Contents::Item(item) => Some(GraphItem::Item {
|
||||||
|
title: item.title.clone(),
|
||||||
|
description: item.description.clone(),
|
||||||
|
state: match &item.item_state {
|
||||||
|
Some(state) => match state {
|
||||||
|
hyperlog_protos::hyperlog::item_graph_item::ItemState::NotDone(_) => {
|
||||||
|
hyperlog_core::log::ItemState::NotDone
|
||||||
|
}
|
||||||
|
hyperlog_protos::hyperlog::item_graph_item::ItemState::Done(_) => {
|
||||||
|
hyperlog_core::log::ItemState::Done
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => hyperlog_core::log::ItemState::NotDone,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,8 @@
|
|||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use crate::{engine::Engine, log::GraphItem};
|
use hyperlog_core::log::GraphItem;
|
||||||
|
|
||||||
|
use crate::engine::Engine;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SharedEngine {
|
pub struct SharedEngine {
|
@@ -1,6 +1,6 @@
|
|||||||
use std::{ops::Deref, sync::Arc};
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
use hyperlog_core::state::State;
|
use crate::core_state::State;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SharedState {
|
pub struct SharedState {
|
||||||
|
@@ -149,10 +149,9 @@ impl Storage {
|
|||||||
mod test {
|
mod test {
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use hyperlog_core::log::GraphItem;
|
||||||
use similar_asserts::assert_eq;
|
use similar_asserts::assert_eq;
|
||||||
|
|
||||||
use crate::log::GraphItem;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
@@ -7,6 +7,7 @@ repository = "https://git.front.kjuulh.io/kjuulh/hyperlog"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
hyperlog-core.workspace = true
|
hyperlog-core.workspace = true
|
||||||
hyperlog-tui.workspace = true
|
hyperlog-tui.workspace = true
|
||||||
|
hyperlog-server = { workspace = true, optional = true }
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
@@ -15,21 +16,17 @@ tracing-subscriber.workspace = true
|
|||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
dotenv.workspace = true
|
dotenv.workspace = true
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
uuid.workspace = true
|
||||||
|
|
||||||
serde = { version = "1.0.201", features = ["derive"] }
|
|
||||||
sqlx = { version = "0.7.4", features = [
|
|
||||||
"runtime-tokio",
|
|
||||||
"tls-rustls",
|
|
||||||
"postgres",
|
|
||||||
"uuid",
|
|
||||||
"time",
|
|
||||||
] }
|
|
||||||
uuid = { version = "1.8.0", features = ["v4"] }
|
|
||||||
tower-http = { version = "0.5.2", features = ["cors", "trace"] }
|
|
||||||
bus = "2.4.1"
|
bus = "2.4.1"
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
similar-asserts = "1.5.0"
|
similar-asserts = "1.5.0"
|
||||||
tempfile = "3.10.1"
|
tempfile = "3.10.1"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
include_server = ["dep:hyperlog-server"]
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
-- Add migration script here
|
|
@@ -1,22 +1,44 @@
|
|||||||
use std::net::SocketAddr;
|
use clap::{Parser, Subcommand, ValueEnum};
|
||||||
|
use hyperlog_tui::{
|
||||||
use clap::{Parser, Subcommand};
|
commander,
|
||||||
use hyperlog_core::{commander, state};
|
core_state::{Backend, State},
|
||||||
|
};
|
||||||
use crate::server::serve;
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
struct Command {
|
struct Command {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: Option<Commands>,
|
command: Option<Commands>,
|
||||||
|
|
||||||
|
#[arg(long, default_value = "local")]
|
||||||
|
backend: BackendArg,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ValueEnum, Clone)]
|
||||||
|
enum BackendArg {
|
||||||
|
Local,
|
||||||
|
Remote,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BackendArg> for Backend {
|
||||||
|
fn from(value: BackendArg) -> Self {
|
||||||
|
match value {
|
||||||
|
BackendArg::Local => Backend::Local,
|
||||||
|
BackendArg::Remote => Backend::Remote,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
enum Commands {
|
enum Commands {
|
||||||
|
#[cfg(feature = "include_server")]
|
||||||
Serve {
|
Serve {
|
||||||
#[arg(env = "SERVICE_HOST", long, default_value = "127.0.0.1:3000")]
|
#[arg(env = "EXTERNAL_HOST", long, default_value = "127.0.0.1:3000")]
|
||||||
host: SocketAddr,
|
external_host: std::net::SocketAddr,
|
||||||
|
#[arg(env = "INTERNAL_HOST", long, default_value = "127.0.0.1:3001")]
|
||||||
|
internal_host: std::net::SocketAddr,
|
||||||
|
#[arg(env = "EXTERNAL_GRPC_HOST", long, default_value = "127.0.0.1:4000")]
|
||||||
|
external_grpc_host: std::net::SocketAddr,
|
||||||
},
|
},
|
||||||
Exec {
|
Exec {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
@@ -70,58 +92,85 @@ pub async fn execute() -> anyhow::Result<()> {
|
|||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
}
|
}
|
||||||
|
|
||||||
let state = state::State::new()?;
|
let backend = cli.backend;
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Some(Commands::Serve { host }) => {
|
#[cfg(feature = "include_server")]
|
||||||
|
Some(Commands::Serve {
|
||||||
|
external_host,
|
||||||
|
internal_host,
|
||||||
|
external_grpc_host,
|
||||||
|
}) => {
|
||||||
tracing::info!("Starting service");
|
tracing::info!("Starting service");
|
||||||
|
|
||||||
serve(host).await?;
|
hyperlog_server::serve(hyperlog_server::ServeOptions {
|
||||||
|
external_http: external_host,
|
||||||
|
internal_http: internal_host,
|
||||||
|
external_grpc: external_grpc_host,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
Some(Commands::Exec { commands }) => match commands {
|
Some(Commands::Exec { commands }) => {
|
||||||
ExecCommands::CreateRoot { root } => state
|
let state = State::new(backend.into()).await?;
|
||||||
.commander
|
match commands {
|
||||||
.execute(commander::Command::CreateRoot { root })?,
|
ExecCommands::CreateRoot { root } => {
|
||||||
ExecCommands::CreateSection { root, path } => {
|
state
|
||||||
state.commander.execute(commander::Command::CreateSection {
|
.commander
|
||||||
root,
|
.execute(commander::Command::CreateRoot { root })
|
||||||
path: path
|
.await?
|
||||||
.unwrap_or_default()
|
}
|
||||||
.split('.')
|
ExecCommands::CreateSection { root, path } => {
|
||||||
.map(|s| s.to_string())
|
state
|
||||||
.filter(|s| !s.is_empty())
|
.commander
|
||||||
.collect::<Vec<String>>(),
|
.execute(commander::Command::CreateSection {
|
||||||
})?
|
root,
|
||||||
|
path: path
|
||||||
|
.unwrap_or_default()
|
||||||
|
.split('.')
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Some(Commands::Query { commands }) => match commands {
|
Some(Commands::Query { commands }) => {
|
||||||
QueryCommands::Get { root, path } => {
|
let state = State::new(backend.into()).await?;
|
||||||
let res = state.querier.get(
|
match commands {
|
||||||
&root,
|
QueryCommands::Get { root, path } => {
|
||||||
path.unwrap_or_default()
|
let res = state.querier.get(
|
||||||
.split('.')
|
&root,
|
||||||
.filter(|s| !s.is_empty()),
|
path.unwrap_or_default()
|
||||||
);
|
.split('.')
|
||||||
|
.filter(|s| !s.is_empty()),
|
||||||
|
);
|
||||||
|
|
||||||
let output = serde_json::to_string_pretty(&res)?;
|
let output = serde_json::to_string_pretty(&res)?;
|
||||||
|
|
||||||
println!("{}", output);
|
println!("{}", output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Some(Commands::CreateRoot { name }) => {
|
Some(Commands::CreateRoot { name }) => {
|
||||||
|
let state = State::new(backend.into()).await?;
|
||||||
state
|
state
|
||||||
.commander
|
.commander
|
||||||
.execute(commander::Command::CreateRoot { root: name })?;
|
.execute(commander::Command::CreateRoot { root: name })
|
||||||
|
.await?;
|
||||||
println!("Root was successfully created, now run:\n\n$ hyperlog");
|
println!("Root was successfully created, now run:\n\n$ hyperlog");
|
||||||
}
|
}
|
||||||
Some(Commands::Info {}) => {
|
Some(Commands::Info {}) => {
|
||||||
|
let state = State::new(backend.into()).await?;
|
||||||
println!("graph stored at: {}", state.storage.info()?)
|
println!("graph stored at: {}", state.storage.info()?)
|
||||||
}
|
}
|
||||||
Some(Commands::ClearLock {}) => {
|
Some(Commands::ClearLock {}) => {
|
||||||
|
let state = State::new(backend.into()).await?;
|
||||||
state.storage.clear_lock_file();
|
state.storage.clear_lock_file();
|
||||||
println!("cleared lock file");
|
println!("cleared lock file");
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
let state = State::new(backend.into()).await?;
|
||||||
hyperlog_tui::execute(state).await?;
|
hyperlog_tui::execute(state).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
mod cli;
|
mod cli;
|
||||||
pub(crate) mod server;
|
|
||||||
pub(crate) mod state;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
@@ -1,42 +1 @@
|
|||||||
use std::{net::SocketAddr, sync::Arc};
|
|
||||||
|
|
||||||
use axum::{extract::MatchedPath, http::Request, routing::get, Router};
|
|
||||||
use tower_http::trace::TraceLayer;
|
|
||||||
|
|
||||||
use crate::state::{SharedState, State};
|
|
||||||
|
|
||||||
async fn root() -> &'static str {
|
|
||||||
"Hello, hyperlog!"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn serve(host: SocketAddr) -> anyhow::Result<()> {
|
|
||||||
let state = SharedState(Arc::new(State::new().await?));
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/", get(root))
|
|
||||||
.with_state(state.clone())
|
|
||||||
.layer(
|
|
||||||
TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
|
|
||||||
// Log the matched route's path (with placeholders not filled in).
|
|
||||||
// Use request.uri() or OriginalUri if you want the real path.
|
|
||||||
let matched_path = request
|
|
||||||
.extensions()
|
|
||||||
.get::<MatchedPath>()
|
|
||||||
.map(MatchedPath::as_str);
|
|
||||||
|
|
||||||
tracing::info_span!(
|
|
||||||
"http_request",
|
|
||||||
method = ?request.method(),
|
|
||||||
matched_path,
|
|
||||||
some_other_field = tracing::field::Empty,
|
|
||||||
)
|
|
||||||
}), // ...
|
|
||||||
);
|
|
||||||
|
|
||||||
tracing::info!("listening on {}", host);
|
|
||||||
let listener = tokio::net::TcpListener::bind(host).await.unwrap();
|
|
||||||
axum::serve(listener, app.into_make_service())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
@@ -1,37 +1 @@
|
|||||||
use std::{ops::Deref, sync::Arc};
|
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use sqlx::{Pool, Postgres};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SharedState(pub Arc<State>);
|
|
||||||
|
|
||||||
impl Deref for SharedState {
|
|
||||||
type Target = Arc<State>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct State {
|
|
||||||
pub _db: Pool<Postgres>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
pub async fn new() -> anyhow::Result<Self> {
|
|
||||||
let db = sqlx::PgPool::connect(
|
|
||||||
&std::env::var("DATABASE_URL").context("DATABASE_URL is not set")?,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
sqlx::migrate!("migrations/crdb")
|
|
||||||
.set_locking(false)
|
|
||||||
.run(&db)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let _ = sqlx::query("SELECT 1;").fetch_one(&db).await?;
|
|
||||||
|
|
||||||
Ok(Self { _db: db })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -15,3 +15,9 @@ please:
|
|||||||
api_url: https://git.front.kjuulh.io
|
api_url: https://git.front.kjuulh.io
|
||||||
actions:
|
actions:
|
||||||
rust:
|
rust:
|
||||||
|
|
||||||
|
scripts:
|
||||||
|
dev:
|
||||||
|
type: shell
|
||||||
|
install:
|
||||||
|
type: shell
|
||||||
|
484
demo.cast
484
demo.cast
@@ -1,187 +1,297 @@
|
|||||||
{"version": 2, "width": 121, "height": 31, "timestamp": 1715413204, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}}
|
{"version": 2, "width": 123, "height": 33, "timestamp": 1715717726, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}}
|
||||||
[0.28385, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
[0.289876, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
||||||
[0.356577, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0m\u001b[1;31mrs \u001b[0m\r\n\u001b[38;2;255;153;102m❯\u001b[0m \u001b[K"]
|
[0.363463, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0mis \u001b[1;38;5;208m📦 \u001b[0m\u001b[1;38;5;208mv0.1.0\u001b[0m \u001b[1;31mrs \u001b[0m\r\n\u001b[38;2;255;153;102m❯\u001b[0m \u001b[K"]
|
||||||
[0.35759, "o", "\u001b[6 q"]
|
[0.364423, "o", "\u001b[6 q"]
|
||||||
[0.358564, "o", "\u001b[6 q"]
|
[0.365312, "o", "\u001b[6 q"]
|
||||||
[0.358794, "o", "\u001b[?2004h"]
|
[0.365501, "o", "\u001b[?2004h"]
|
||||||
[1.196152, "o", "c"]
|
[1.080115, "o", "c"]
|
||||||
[1.200489, "o", "\b\u001b[32mc\u001b[39m"]
|
[1.082107, "o", "\b\u001b[32mc\u001b[39m"]
|
||||||
[1.241408, "o", "\b\u001b[32mc\u001b[39m\u001b[90mlear\u001b[39m\b\b\b\b"]
|
[1.101402, "o", "\b\u001b[32mc\u001b[39m\u001b[90margo run --release -- --backend remote\u001b[39m\u001b[38D"]
|
||||||
[1.340197, "o", "\b\u001b[32mc\u001b[32ma\u001b[39m\u001b[39m \u001b[39m \u001b[39m \b\b\b"]
|
[1.169782, "o", "\b\u001b[32mc\u001b[32ma\u001b[39m"]
|
||||||
[1.369897, "o", "\b\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31ma\u001b[0m\u001b[39m"]
|
[1.189287, "o", "\b\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31ma\u001b[0m\u001b[39m"]
|
||||||
[1.37249, "o", "\u001b[90mrgo run\u001b[39m\b\b\b\b\b\b\b"]
|
[1.249073, "o", "\b\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31ma\u001b[1m\u001b[31mr\u001b[0m\u001b[39m"]
|
||||||
[1.424331, "o", "\b\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31ma\u001b[1m\u001b[31mr\u001b[0m\u001b[39m"]
|
[1.43174, "o", "\b\u001b[1m\u001b[31mr\u001b[1m\u001b[31mg\u001b[0m\u001b[39m"]
|
||||||
[1.592372, "o", "\b\u001b[1m\u001b[31mr\u001b[1m\u001b[31mg\u001b[0m\u001b[39m"]
|
[1.578255, "o", "\b\u001b[1m\u001b[31mg\u001b[1m\u001b[31mo\u001b[0m\u001b[39m"]
|
||||||
[1.666639, "o", "\b\u001b[1m\u001b[31mg\u001b[1m\u001b[31mo\u001b[0m\u001b[39m"]
|
[1.580674, "o", "\b\b\b\b\b\u001b[0m\u001b[32mc\u001b[0m\u001b[32ma\u001b[0m\u001b[32mr\u001b[0m\u001b[32mg\u001b[0m\u001b[32mo\u001b[39m"]
|
||||||
[1.671179, "o", "\b\b\b\b\b\u001b[0m\u001b[32mc\u001b[0m\u001b[32ma\u001b[0m\u001b[32mr\u001b[0m\u001b[32mg\u001b[0m\u001b[32mo\u001b[39m"]
|
[2.050954, "o", "\u001b[39m \u001b[39mr\u001b[39mu\u001b[39mn\u001b[39m \u001b[39m-\u001b[39m-\u001b[39mr\u001b[39me\u001b[39ml\u001b[39me\u001b[39ma\u001b[39ms\u001b[39me\u001b[39m \u001b[39m-\u001b[39m-\u001b[39m \u001b[39m-\u001b[39m-\u001b[39mb\u001b[39ma\u001b[39mc\u001b[39mk\u001b[39me\u001b[39mn\u001b[39md\u001b[39m \u001b[39mr\u001b[39me\u001b[39mm\u001b[39mo\u001b[39mt\u001b[39me"]
|
||||||
[2.080073, "o", "\b\u001b[32mo\u001b[32m \u001b[39m"]
|
[2.553848, "o", "\u001b[?1l\u001b>"]
|
||||||
[2.084222, "o", "\b\b\u001b[32mo\u001b[39m\u001b[39m "]
|
[2.554218, "o", "\u001b[?2004l"]
|
||||||
[2.246919, "o", "\u001b[39mr"]
|
[2.56397, "o", "\u001b[0 q"]
|
||||||
[2.252663, "o", "\b\u001b[4mr\u001b[24m"]
|
[2.564341, "o", "\r\r\n"]
|
||||||
[2.347417, "o", "\b\u001b[4mr\u001b[39m\u001b[4mu\u001b[24m"]
|
[2.891881, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `std::net::SocketAddr`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog/src/cli.rs:1:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse std::net::SocketAddr;\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_imports)]` on by default\u001b[0m\r\n\r\n"]
|
||||||
[2.351929, "o", "\b\b\u001b[24mr\u001b[24mu"]
|
[2.892626, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `GraphItem`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_item.rs:1:26\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse hyperlog_core::log::{GraphItem, ItemState};\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_imports)]` on by default\u001b[0m\r\n\r\n"]
|
||||||
[2.501908, "o", "\u001b[39mn"]
|
[2.892638, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `hyperlog_core::log::GraphItem`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_section.rs:1:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse hyperlog_core::log::GraphItem;\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m\r\n\r\n"]
|
||||||
[2.800437, "o", "\u001b[?1l\u001b>"]
|
[2.892645, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused imports: `GraphItem`, `ItemState`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_available_roots.rs:1:26\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse hyperlog_core::log::{GraphItem, ItemState};\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\r\n"]
|
||||||
[2.800875, "o", "\u001b[?2004l"]
|
[2.892769, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `sqlx::types::Json`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_available_roots.rs:2:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m2\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse sqlx::types::Json;\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `root`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:102:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m102\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m root,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `root: _`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_variables)]` on by default\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variab"]
|
||||||
[2.807783, "o", "\u001b[0 q"]
|
[2.892853, "o", "le: `path`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:103:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m103\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m path,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `path: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `title`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:104:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m104\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m title,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `title: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `description`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:105:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;"]
|
||||||
[2.808107, "o", "\r\r\n"]
|
[2.89292, "o", "12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m105\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m description,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `description: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `state`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:106:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m106\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m state,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `state: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `root`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:108:35\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m108\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::ToggleItem { root, p"]
|
||||||
[3.35867, "o", "\u001b[1m\u001b[32m Finished\u001b[0m `dev` profile [unoptimized + debuginfo] target(s) in 0.49s\r\n"]
|
[2.892978, "o", "ath } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `root: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `path`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:108:41\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m108\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::ToggleItem { root, path } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `path: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `root`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:109:29\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m109\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::Move { root, src, dest } => todo!(),\u001b[0m\r\n\u001b[0m "]
|
||||||
[3.365514, "o", "\u001b[1m\u001b[32m Running\u001b[0m `target/debug/hyperlog`\r\n"]
|
[2.893044, "o", "\u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `root: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `src`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:109:35\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m109\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::Move { root, src, dest } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `src: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `dest`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:109:40\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m109\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::Move { root, src, dest } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m "]
|
||||||
[3.740156, "o", "\u001b[?1049h"]
|
[2.89314, "o", " \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `dest: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `req`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_available_roots.rs:26:33\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m26\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_req`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `root_name` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_item.rs:31:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m29\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Root {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[3"]
|
||||||
[3.743543, "o", "\u001b[1;1H\u001b[38;5;2mhyperlog\u001b[2;1H\u001b[39m─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[3;1Hsomething\u001b[3;11H~\u001b[3;13H(items:\u001b[3;21H3)\u001b[4;5Ha\u001b[4;7H~\u001b[4;9H(items:\u001b[4;17H2)\u001b[5;5H\u001b[38;5;8m...\u001b[6;5H\u001b[39mc\u001b[6;7H~\u001b[6;9H(items:\u001b[6;17H0)\u001b[31;2H--\u001b[31;5HVIEW\u001b[31;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[2.893227, "o", "8;5;12m----\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m30\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m31\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m root_name: String,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(dead_code)]` on by default\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `id` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_item.rs:36:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m35\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Section {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m-------\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m36\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0"]
|
||||||
[4.001932, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[2.893323, "o", "m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `root_name` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_section.rs:19:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m17\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Root {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m----\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m18\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m19\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m root_name: String,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `id` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_graph.rs:37:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m36\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Node {\u001b[0m\r\n\u001b[0m \u001b"]
|
||||||
[4.260086, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[2.893379, "o", "[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m----\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m37\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `Node` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: multiple associated items are never used\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_graph.rs:152:16\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m151\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m impl Engine {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m-----------\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12massociated items in this implementation\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m152\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b"]
|
||||||
[4.458203, "o", "\u001b[3;1H\u001b[38;2;255;165;0msomething ~ (items: 3)\u001b[5;5H\u001b[39m \u001b[5;9H[\u001b[5;11H]\u001b[5;13Hitem\u001b[6;5H \u001b[6;7H \u001b[6;9H[ ] something\u001b[7;5Hb\u001b[7;7H~\u001b[7;9H(items:\u001b[7;17H1)\u001b[8;9H[\u001b[8;11H]\u001b[8;13Hitem\u001b[9;5Hc\u001b[9;7H~\u001b[9;9H(items:\u001b[9;17H0)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[2.893458, "o", "[0m pub fn engine_from_str(input: &str) -> anyhow::Result<Self> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m158\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn to_str(&self) -> anyhow::Result<String> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m219\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn get_mut(&mut self, root: &str, path: &[&str]) -> Option<&mut GraphItem> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m225\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn take(&mut self, root: &str, path: &[&str]) -> Option<GraphItem> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m231\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m"]
|
||||||
[4.716099, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[2.893531, "o", "\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn section_move(\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m268\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn delete(&mut self, root: &str, path: &[&str]) -> anyhow::Result<()> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m274\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn toggle_item(&mut self, root: &str, path: &[&str]) -> anyhow::Result<()> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m290\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn update_item(\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m341\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m "]
|
||||||
[4.974526, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[2.893617, "o", " pub fn get_roots(&self) -> Option<Vec<String>> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\r\n"]
|
||||||
[5.235168, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[2.894153, "o", "\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m:\u001b[0m `hyperlog` (bin \"hyperlog\") generated 1 warning (run `cargo fix --bin \"hyperlog\"` to apply 1 suggestion)\r\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m:\u001b[0m `hyperlog-server` (lib) generated 20 warnings (run `cargo fix --lib -p hyperlog-server` to apply 4 suggestions)\r\n\u001b[1m\u001b[32m Finished\u001b[0m `release` profile [optimized] target(s) in 0.28s\r\n"]
|
||||||
[5.471231, "o", "\u001b[31;1H: \u001b[31;5H \u001b[31;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[2.903509, "o", "\u001b[1m\u001b[32m Running\u001b[0m `target/release/hyperlog --backend remote`\r\n"]
|
||||||
[5.670365, "o", "\u001b[31;2Hs\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[3.072772, "o", "\u001b[?1049h"]
|
||||||
[5.776552, "o", "\u001b[31;3Hh\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[3.088809, "o", "\u001b[1;1H\u001b[38;5;2mhyperlog\u001b[2;1H\u001b[39m───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[3;1Hsomething\u001b[3;11H~\u001b[3;13H(items:\u001b[3;21H2)\u001b[4;5Hi-can-do-this\u001b[4;19H~\u001b[4;21H(items:\u001b[4;29H1)\u001b[5;5Hsomething\u001b[5;15H~\u001b[5;17H(items:\u001b[5;25H3)\u001b[33;2H--\u001b[33;5HVIEW\u001b[33;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[5.907952, "o", "\u001b[31;4Ho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[3.99447, "o", "\u001b[3;1H\u001b[38;2;255;165;0msomething ~ (items: 2)\u001b[5;5H\u001b[39m [ ] with-\u001b[5;23H \u001b[5;25H \u001b[6;5Hsomething\u001b[6;15H~\u001b[6;17H(items:\u001b[6;25H3)\u001b[7;9H[\u001b[7;11H]\u001b[7;13Hand-another\u001b[8;9H\u001b[38;5;8m...\u001b[9;9H\u001b[39m[\u001b[9;11H]\u001b[9;13Hitem\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[6.035263, "o", "\u001b[31;5Ht\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[4.842467, "o", "\u001b[3;1Hsomething ~ (items: 2)\u001b[5;5Hsomething ~ (\u001b[5;23H:\u001b[5;25H3)\u001b[6;5H \u001b[6;15H \u001b[6;17H \u001b[6;25H \u001b[7;9H \u001b[7;11H \u001b[7;13H \u001b[8;9H \u001b[9;9H \u001b[9;11H \u001b[9;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[6.187704, "o", "\u001b[31;6H-\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[5.187689, "o", "\u001b[33;1H: \u001b[33;5H \u001b[33;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[6.299824, "o", "\u001b[31;7Ha\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[5.37781, "o", "\u001b[33;2Hc\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[6.438169, "o", "\u001b[31;8Hl\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[5.583938, "o", "\u001b[33;3Hs\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[6.577971, "o", "\u001b[31;9Hl\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[5.744397, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[6.836633, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[6.118579, "o", "\u001b[33;5Hi\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[6.968556, "o", "\u001b[31;9H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[6.262015, "o", "\u001b[33;6Ht\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[7.106562, "o", "\u001b[31;8H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[7.099969, "o", "\u001b[33;7H-\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[7.251411, "o", "\u001b[31;7H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[7.211383, "o", "\u001b[33;8Hw\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[7.395354, "o", "\u001b[31;6H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[7.326906, "o", "\u001b[33;9Ho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[7.54286, "o", "\u001b[31;5H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[7.40033, "o", "\u001b[33;10Hr\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[7.731859, "o", "\u001b[31;5Hw\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[7.546151, "o", "\u001b[33;11Hk\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[7.989235, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[7.670118, "o", "\u001b[33;12Hs\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[8.121554, "o", "\u001b[31;6H-\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[7.876666, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[8.299653, "o", "\u001b[31;7Ha\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[7.877584, "o", "\u001b[33;1H \u001b[33;5H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[8.399794, "o", "\u001b[31;8Hl\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[7.878029, "o", "\u001b[33;2H--\u001b[33;5HVIEW\u001b[33;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[8.549891, "o", "\u001b[31;9Hl\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[7.878496, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[8.785214, "o", "\u001b[9;17H5\u001b[10;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[10;13Hitem\u001b[11;9H\u001b[38;5;8m...\u001b[12;9H\u001b[39m[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[12;13Hitem-d\u001b[31;1H -- VIEW --\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[7.891922, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[9.044234, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[7.892175, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[9.303396, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[7.901476, "o", "\u001b[3;1Hit-works ~ (items: 0) \u001b[4;1Hsomething ~ (items:\u001b[4;21H2) \u001b[4;29H \u001b[5;5Hi-can-do-this ~ (items:\u001b[5;29H1)\u001b[6;5Hsomething\u001b[6;15H~\u001b[6;17H(items:\u001b[6;25H3)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[9.554819, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[7.901907, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[9.813405, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[8.476726, "o", "\u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 0)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[10.073436, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[9.1102, "o", "\u001b[1;10H~\u001b[1;12Hcreate\u001b[1;19Hitem\u001b[3;1H\u001b[38;5;8mpath: kjuulh.it-works \u001b[4;1H \u001b[5;1H\u001b[39m┌title────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\u001b[6;1H│\u001b[38;5;0m\u001b[48;5;5m \u001b[6;5H\u001b[39m\u001b[49m \u001b[6;15H \u001b[6;17H \u001b[6;25H \u001b[6;123H│\u001b[7;1H└───────────────────────────────────────────────────────────────────────────────────"]
|
||||||
[10.077833, "o", "\u001b[3;1Hsomething ~ (items: 3)\u001b[5;5H\u001b[38;5;8m...\u001b[5;9H\u001b[39m \u001b[5;11H \u001b[5;13H \u001b[6;5Hc\u001b[6;7H~\u001b[6;9H(items: 5) \u001b[7;5H \u001b[7;7H \u001b[7;9H \u001b[7;17H \u001b[8;9H \u001b[8;11H \u001b[8;13H \u001b[9;5H \u001b[9;7H \u001b[9;9H \u001b[9;17H \u001b[10;9H \u001b[10;13H \u001b[11;9H \u001b[12;9H \u001b[12;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[9.110401, "o", "──────────────────────────────────────┘\u001b[8;1H\u001b[38;5;8m┌description──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\u001b[9;1H│ │\u001b[10;1H└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\u001b[33;5H\u001b[39mEDIT\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?"]
|
||||||
[10.33073, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[9.110539, "o", "25l"]
|
||||||
[10.590022, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[9.875008, "o", "\u001b[6;2HI\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[10.750514, "o", "\u001b[3;1H\u001b[38;2;255;165;0msomething ~ (items: 3)\u001b[5;5H\u001b[39m \u001b[5;9H[\u001b[5;11H]\u001b[5;13Hitem\u001b[6;5H \u001b[6;7H \u001b[6;9H[ ] something\u001b[7;5Hb\u001b[7;7H~\u001b[7;9H(items:\u001b[7;17H1)\u001b[8;9H[\u001b[8;11H]\u001b[8;13Hitem\u001b[9;5Hc\u001b[9;7H~\u001b[9;9H(items:\u001b[9;17H5)\u001b[10;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[10;13Hitem\u001b[11;9H\u001b[38;5;8m...\u001b[12;9H\u001b[39m[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[12;13Hitem-d\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[9.979676, "o", "\u001b[6;3H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[11.009542, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[10.159581, "o", "\u001b[6;4Hc\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[11.101987, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[10.224824, "o", "\u001b[6;5Ha\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[11.310184, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[10.344486, "o", "\u001b[6;6Hn\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[11.569878, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[10.424478, "o", "\u001b[6;7H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[11.646194, "o", "\u001b[3;1Hsomething ~ (items: 3)\u001b[4;5H\u001b[38;2;255;165;0ma ~ (items: 2)\u001b[7;5H\u001b[39m \u001b[7;7H \u001b[7;9H \u001b[7;17H \u001b[8;5Hb\u001b[8;7H~\u001b[8;9H(items: 1)\u001b[9;5H \u001b[9;7H \u001b[9;9H[ ] item \u001b[10;5Hc\u001b[10;7H~\u001b[10;9H(items: 5)\u001b[11;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[11;13Hitem\u001b[12;9H\u001b[38;5;8m...\u001b[12;13H\u001b[39m \u001b[13;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[13;13Hitem-d\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[10.616037, "o", "\u001b[6;8Ha\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[11.905614, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[10.717012, "o", "\u001b[6;9Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[11.9512, "o", "\u001b[4;5Ha ~ (items: 2)\u001b[7;5H\u001b[38;2;255;165;0mb ~ (items: 1)\u001b[8;5H\u001b[39m \u001b[8;7H \u001b[8;9H[ ] item \u001b[9;9H \u001b[9;11H \u001b[9;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[10.867539, "o", "\u001b[6;10Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[12.171517, "o", "\u001b[7;5Hb ~ (items: 1)\u001b[9;5H\u001b[38;2;255;165;0mc ~ (items: 5)\u001b[10;5H\u001b[39m \u001b[10;7H \u001b[10;9H[\u001b[38;2;127;255;0mx\u001b[39m] item \u001b[11;17H-a\u001b[12;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[12;13Hitem-b\u001b[13;18Hc\u001b[14;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[14;13Hitem-d\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[10.965244, "o", "\u001b[6;11H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[12.428831, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[11.038338, "o", "\u001b[6;12Hi\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[12.687811, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[11.146079, "o", "\u001b[6;13Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[12.710816, "o", "\u001b[4;5Hc\u001b[4;17H5\u001b[5;9H\u001b[38;2;255;165;0m[x] item\u001b[6;10H\u001b[38;2;127;255;0mx\u001b[6;13H\u001b[39mitem-a \u001b[7;5H \u001b[7;7H \u001b[7;9H[\u001b[38;2;127;255;0mx\u001b[39m] item-b\u001b[8;10H\u001b[38;2;127;255;0mx\u001b[8;17H\u001b[39m-c\u001b[9;5H [\u001b[38;2;127;255;0mx\u001b[39m] item-d\u001b[10;9H \u001b[10;13H \u001b[11;9H \u001b[11;13H \u001b[12;9H \u001b[12;13H \u001b[13;9H \u001b[13;13H \u001b[14;9H \u001b[14;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[11.224729, "o", "\u001b[6;14He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[12.970938, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[11.298028, "o", "\u001b[6;15Hm\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[13.229238, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[11.493598, "o", "\u001b[6;16Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[13.402499, "o", "\u001b[5;10H\u001b[38;2;255;165;0m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[11.859838, "o", "\u001b[6;17H \u001b[33;5HVIEW\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[13.661623, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[12.18879, "o", "\u001b[33;1H: \u001b[33;5H \u001b[33;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[13.709679, "o", "\u001b[5;9H[ ] item\u001b[6;9H\u001b[38;2;255;165;0m[x] item-a\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[12.27651, "o", "\u001b[33;2Hw\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[13.969177, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[12.348568, "o", "\u001b[33;3Hq\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[13.980927, "o", "\u001b[6;10H\u001b[38;2;255;165;0m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[12.518587, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[14.240333, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[12.519161, "o", "\u001b[1;10H \u001b[1;12H \u001b[1;19H \u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 0)\u001b[39m \u001b[4;1Hsomething ~ (items: 2) \u001b[5;1H i-can-do-this ~ (items: 1) \u001b[6;1H \u001b[6;4H something ~ (items:\u001b[6;25H3)\u001b[6;123H \u001b[7;1H \u001b[8;1H \u001b[9;1H \u001b[10;1H \u001b[33;1H \u001b[39"]
|
||||||
[14.453095, "o", "\u001b[4;5Ha\u001b[4;17H2\u001b[6;9H[ ] something\u001b[7;5Hb\u001b[7;7H~\u001b[7;9H(items: 1)\u001b[8;10H \u001b[8;17H \u001b[9;5H\u001b[38;2;255;165;0mc ~ (items: 5)\u001b[10;9H\u001b[39m[\u001b[10;11H]\u001b[10;13Hitem\u001b[11;9H[\u001b[11;11H]\u001b[11;13Hitem-a\u001b[12;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[12;13Hitem-b\u001b[13;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[13;13Hitem-c\u001b[14;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[14;13Hitem-d\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[12.519414, "o", "m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[14.638745, "o", "\u001b[3;1H\u001b[38;2;255;165;0msomething ~ (items: 3)\u001b[9;5H\u001b[39mc ~ (items: 5)\u001b[11;9H\u001b[38;5;8m...\u001b[11;13H\u001b[39m \u001b[12;18Hd\u001b[13;9H \u001b[13;13H \u001b[14;9H \u001b[14;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[12.51976, "o", "\u001b[33;2H--\u001b[33;5HVIEW\u001b[33;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[14.897952, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[12.520255, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[15.0327, "o", "\u001b[31;1H: \u001b[31;5H \u001b[31;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[12.542732, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[15.291396, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[12.542941, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[15.549911, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[12.552336, "o", "\u001b[3;20H\u001b[38;2;255;165;0m1\u001b[4;1H\u001b[39m [ ] I-can-add-items\u001b[5;5H \u001b[5;19H \u001b[5;21H \u001b[5;29H \u001b[6;1Hsomething ~ (items: 2) \u001b[6;25H \u001b[7;5Hi-can-do-this\u001b[7;19H~\u001b[7;21H(items:\u001b[7;29H1)\u001b[8;5Hsomething\u001b[8;15H~\u001b[8;17H(items:\u001b[8;25H3)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[15.658184, "o", "\u001b[31;2Hh\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[12.552493, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[15.809793, "o", "\u001b[31;3Hi\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[13.299017, "o", "\u001b[3;1Hit-works ~ (items: 1)\u001b[4;5H\u001b[38;2;255;165;0m[ ] I-can-add-items\u001b[6;1H\u001b[39m \u001b[6;11H \u001b[6;13H \u001b[6;21H \u001b[7;5H \u001b[7;19H \u001b[7;21H \u001b[7;29H \u001b[8;5H \u001b[8;15H \u001b[8;17H \u001b[8;25H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[15.949529, "o", "\u001b[31;4Hd\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[13.686285, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[16.096886, "o", "\u001b[31;5He\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[13.68677, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[16.254773, "o", "\u001b[31;6H-\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[13.74053, "o", "\u001b[31mWell, this is embarrassing.\n\nhyperlog-tui had a problem and crashed. To help us diagnose the problem you can send us a crash report.\n\nWe have generated a report file at \"/var/folders/qp/6jwp97j95qg2ks56vrxt_l1m0000gn/T/report-1778ea7a-cf43-4c76-b260-d88c7bacebcc.toml\". Submit an issue or email with the subject of \"hyperlog-tui Crash Report\" and include the report as an attachment.\n\n\nWe take privacy seriously, and do not perform any automated error collection. In order to improve the software, we rely on people to submit reports.\n\nThank you kindly!\n\u001b[0m"]
|
||||||
[16.470747, "o", "\u001b[31;7Hd\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[15.682146, "o", "\u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 1)\u001b[4;5H\u001b[39m[ ] I-can-add-items\u001b[6;1Hsomething\u001b[6;11H~\u001b[6;13H(items:\u001b[6;21H2)\u001b[7;5Hi-can-do-this\u001b[7;19H~\u001b[7;21H(items:\u001b[7;29H1)\u001b[8;5Hsomething\u001b[8;15H~\u001b[8;17H(items:\u001b[8;25H3)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[16.551481, "o", "\u001b[31;8Ho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[17.020829, "o", "\u001b[1;10H~\u001b[1;12Hcreate\u001b[1;19Hitem\u001b[3;1H\u001b[38;5;8mpath: kjuulh.it-works \u001b[4;1H \u001b[5;1H\u001b[39m┌title────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\u001b[6;1H│\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[6;11H \u001b[6;13H \u001b[6;21H \u001b[6;123H│\u001b[7;1H└──────────────────────────────────────────────────────────────────────────────────────"]
|
||||||
[16.709128, "o", "\u001b[31;9Hn\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[17.020983, "o", "───────────────────────────────────┘\u001b[8;1H\u001b[38;5;8m┌description──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\u001b[9;1H│ │\u001b[10;1H└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\u001b[33;5H\u001b[39mEDIT\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[16.827375, "o", "\u001b[31;10He\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[17.678389, "o", "\u001b[6;2Ho\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[17.005258, "o", "\u001b[9;17H2\u001b[11;9H[ ]\u001b[11;13Hitem-a\u001b[12;9H \u001b[12;13H \u001b[31;1H -- VIEW --\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[17.804009, "o", "\u001b[6;3Hn\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[17.263877, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[17.865221, "o", "\u001b[6;4H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[17.523133, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[17.982226, "o", "\u001b[6;5Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[17.570586, "o", "\u001b[3;1Hsomething ~ (items: 3)\u001b[4;5H\u001b[38;2;255;165;0ma ~ (items: 2)\u001b[7;5H\u001b[39m \u001b[7;7H \u001b[7;9H \u001b[7;17H \u001b[8;5Hb\u001b[8;7H~\u001b[8;9H(items: 1)\u001b[9;5H \u001b[9;7H \u001b[9;9H[ ] item \u001b[10;5Hc\u001b[10;7H~\u001b[10;9H(items: 2)\u001b[11;17H \u001b[12;9H[\u001b[12;11H]\u001b[12;13Hitem-a\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[18.06543, "o", "\u001b[6;6Hh\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[17.829292, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[18.171003, "o", "\u001b[6;7He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[17.986953, "o", "\u001b[4;5Ha ~ (items: 2)\u001b[7;5H\u001b[38;2;255;165;0mb ~ (items: 1)\u001b[8;5H\u001b[39m \u001b[8;7H \u001b[8;9H[ ] item \u001b[9;9H \u001b[9;11H \u001b[9;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[18.259035, "o", "\u001b[6;8H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[18.191003, "o", "\u001b[7;5Hb ~ (items: 1)\u001b[9;5H\u001b[38;2;255;165;0mc ~ (items: 2)\u001b[10;5H\u001b[39m \u001b[10;7H \u001b[10;9H[ ] item \u001b[11;17H-a\u001b[12;9H \u001b[12;11H \u001b[12;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[18.503667, "o", "\u001b[6;9Hb\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[18.44961, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[18.613161, "o", "\u001b[6;10Ha\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[18.709575, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[18.683243, "o", "\u001b[6;11Hc\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[18.968623, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[18.753564, "o", "\u001b[6;12Hk\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[19.193329, "o", "\u001b[31;1H: \u001b[31;5H \u001b[31;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[18.913502, "o", "\u001b[6;13He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[19.45148, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[18.992754, "o", "\u001b[6;14Hn\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[19.45588, "o", "\u001b[31;2Hs\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[19.124873, "o", "\u001b[6;15Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[19.506083, "o", "\u001b[31;3Hh\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[20.230883, "o", "\u001b[6;16H \u001b[33;5HVIEW\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[19.767374, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[20.543105, "o", "\u001b[33;1H: \u001b[33;5H \u001b[33;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[19.772362, "o", "\u001b[31;4Hw\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[20.627545, "o", "\u001b[33;2Hw\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[20.031712, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[20.704119, "o", "\u001b[33;3Hq\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[20.156021, "o", "\u001b[31;4H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[20.83678, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[20.414819, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[20.837696, "o", "\u001b[1;10H \u001b[1;12H \u001b[1;19H \u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 1)\u001b[39m \u001b[4;1H [ ] I-can-add-items \u001b[5;1H \u001b[6;1Hs\u001b[6;3Hme\u001b[6;7Hing ~ (items:\u001b[6;21H2)\u001b[6;123H \u001b[7;1H i-can-do-this ~ (items: 1) \u001b[8;1H something ~ (items: 3) \u001b[9;1H \u001b[10;1H \u001b[33;1H \u001b[39"]
|
||||||
[20.422959, "o", "\u001b[31;4Ho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[20.837737, "o", "m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[20.564511, "o", "\u001b[31;5Hw\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[20.838339, "o", "\u001b[33;2H--\u001b[33;5HVIEW\u001b[33;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[20.686556, "o", "\u001b[31;6H-\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[20.838812, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[20.891174, "o", "\u001b[31;7Ha\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[20.85079, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[21.014086, "o", "\u001b[31;8Hl\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[20.851148, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[21.168709, "o", "\u001b[31;9Hl\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[20.855853, "o", "\u001b[3;20H\u001b[38;2;255;165;0m2\u001b[5;5H\u001b[39m[\u001b[5;7H]\u001b[5;9Hon-the-backend\u001b[6;1H \u001b[6;11H \u001b[6;13H \u001b[6;21H \u001b[7;1Hsomething ~ (items:\u001b[7;21H2) \u001b[7;29H \u001b[8;5Hi-can-do-this ~ (items:\u001b[8;29H1)\u001b[9;5Hsomething\u001b[9;15H~\u001b[9;17H(items:\u001b[9;25H3)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[21.427999, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[20.856076, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[21.436176, "o", "\u001b[9;17H\u001b[38;2;255;165;0m5\u001b[12;9H\u001b[39m[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[12;13Hitem-b\u001b[13;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[13;13Hitem-c\u001b[14;9H[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[14;13Hitem-d\u001b[31;1H -- VIEW --\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[21.341665, "o", "\u001b[3;1Hit-works ~ (items: 2)\u001b[6;1H\u001b[38;2;255;165;0msomething ~ (items: 2)\u001b[7;1H\u001b[39m i-can-do-this ~\u001b[7;21H(items:\u001b[7;29H1)\u001b[8;5H [ ] with-items \u001b[8;29H \u001b[10;9H[\u001b[10;11H]\u001b[10;13Hand-another\u001b[11;9H\u001b[38;5;8m...\u001b[12;9H\u001b[39m[\u001b[12;11H]\u001b[12;13Hitem\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[21.695984, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[21.516868, "o", "\u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 2)\u001b[6;1H\u001b[39m \u001b[7;1Hsomething ~ (items:\u001b[7;21H2) \u001b[7;29H \u001b[8;5Hi-can-do-this ~ (items:\u001b[8;29H1)\u001b[10;9H \u001b[10;11H \u001b[10;13H \u001b[11;9H \u001b[12;9H \u001b[12;11H \u001b[12;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[21.957095, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[21.734986, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[22.04995, "o", "\u001b[7;5H\u001b[38;2;255;165;0mb ~ (items: 1)\u001b[9;5H\u001b[39m \u001b[10;5Hc\u001b[10;7H~\u001b[10;9H(items: 5)\u001b[11;17H \u001b[12;9H\u001b[38;5;8m...\u001b[12;13H\u001b[39m \u001b[13;18Hd\u001b[14;9H \u001b[14;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[22.153707, "o", "\u001b[1;10H~\u001b[1;12Hcreate\u001b[1;19Hitem\u001b[3;1H\u001b[38;5;8mpath: kjuulh.it-works \u001b[4;1H \u001b[5;1H\u001b[39m┌title────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\u001b[6;1H│\u001b[38;5;0m\u001b[48;5;5m \u001b[6;123H\u001b[39m\u001b[49m│\u001b[7;1H└───────────────────────────────────────────────────────────────────────────────────────────────────"]
|
||||||
[22.311034, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[22.153915, "o", "──────────────────────┘\u001b[8;1H\u001b[38;5;8m┌description──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐\u001b[9;1H│ │\u001b[10;1H└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\u001b[33;5H\u001b[39mEDIT\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[22.568875, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[22.83143, "o", "\u001b[6;2Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[22.795873, "o", "\u001b[4;5Hb\u001b[4;17H1\u001b[5;9H\u001b[38;2;255;165;0m[ ] item\u001b[6;9H\u001b[39m \u001b[6;11H \u001b[6;13H \u001b[7;5H \u001b[8;9H \u001b[8;11H \u001b[8;13H \u001b[10;5H \u001b[10;7H \u001b[10;9H \u001b[10;17H \u001b[11;9H \u001b[11;11H \u001b[11;13H \u001b[12;9H \u001b[13;9H \u001b[13;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[22.930796, "o", "\u001b[6;3Ho\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[23.054478, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[23.07636, "o", "\u001b[6;4Hg\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[23.24198, "o", "\u001b[5;10H\u001b[38;2;255;165;0mx\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[23.231599, "o", "\u001b[6;5Hg\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[23.500408, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[23.308648, "o", "\u001b[6;6Hl\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[23.646615, "o", "\u001b[4;5Ha\u001b[4;17H2\u001b[5;9H[ ] item\u001b[6;9H[\u001b[6;11H]\u001b[6;13Hsomething\u001b[7;5H\u001b[38;2;255;165;0mb ~ (items: 1)\u001b[8;9H\u001b[39m[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[8;13Hitem\u001b[10;5Hc\u001b[10;7H~\u001b[10;9H(items:\u001b[10;17H5)\u001b[11;9H[\u001b[11;11H]\u001b[11;13Hitem\u001b[12;9H\u001b[38;5;8m...\u001b[13;9H\u001b[39m[\u001b[38;2;127;255;0mx\u001b[39m]\u001b[13;13Hitem-d\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[23.420762, "o", "\u001b[6;7He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[23.90405, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[23.470615, "o", "\u001b[6;8H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[24.162272, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[23.62503, "o", "\u001b[6;9Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[24.335133, "o", "\u001b[31;1H: \u001b[31;5H \u001b[31;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[23.710239, "o", "\u001b[6;10Ho\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[24.590424, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[23.826044, "o", "\u001b[6;11He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[24.825885, "o", "\u001b[31;2Hh\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[23.935563, "o", "\u001b[6;12Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[24.994253, "o", "\u001b[31;3Hi\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[23.94551, "o", "\u001b[6;13Hn\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[25.102804, "o", "\u001b[31;4Hd\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[24.15886, "o", "\u001b[6;14H'\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[25.2455, "o", "\u001b[31;5He\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[24.231506, "o", "\u001b[6;15Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[25.327672, "o", "\u001b[31;6H-\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[24.292527, "o", "\u001b[6;16H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[25.47797, "o", "\u001b[31;7Hd\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[24.45627, "o", "\u001b[6;17Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[25.603255, "o", "\u001b[31;8Ho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[24.521203, "o", "\u001b[6;18He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[25.749521, "o", "\u001b[31;9Hn\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[24.689421, "o", "\u001b[6;19He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[25.862533, "o", "\u001b[31;10He\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[24.849768, "o", "\u001b[6;20Hm\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[26.026357, "o", "\u001b[7;17H\u001b[38;2;255;165;0m0\u001b[8;5H\u001b[39mc\u001b[8;7H~\u001b[8;9H(items: 2)\u001b[9;9H[\u001b[9;11H]\u001b[9;13Hitem\u001b[10;5H \u001b[10;7H \u001b[10;9H[ ] item-a\u001b[11;9H \u001b[11;11H \u001b[11;13H \u001b[12;9H \u001b[13;9H \u001b[13;13H \u001b[31;1H -- VIEW --\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[25.075116, "o", "\u001b[6;21H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[26.285752, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[25.198095, "o", "\u001b[6;22Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[26.546241, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[25.276978, "o", "\u001b[6;23Ho\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[26.80384, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[25.372935, "o", "\u001b[6;24H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[27.061451, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[25.782976, "o", "\u001b[6;24H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[27.318053, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[25.934207, "o", "\u001b[6;23H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[27.435387, "o", "\u001b[31;1H: \u001b[31;5H \u001b[31;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[26.081359, "o", "\u001b[6;22H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[27.678991, "o", "\u001b[31;2Hq\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
[26.223661, "o", "\u001b[6;21H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[27.801458, "o", "\u001b[?1049l\u001b[?25h"]
|
[26.367423, "o", "\u001b[6;20H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[27.803865, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
[26.515238, "o", "\u001b[6;19H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[27.863445, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0m\u001b[1;31mrs \u001b[0m\u001b[33m25s\u001b[0m \r\n\u001b[38;2;255;153;102m❯\u001b[0m \u001b[K"]
|
[26.659355, "o", "\u001b[6;18H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[27.864508, "o", "\u001b[6 q"]
|
[26.803997, "o", "\u001b[6;17H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[27.865397, "o", "\u001b[6 q"]
|
[26.953345, "o", "\u001b[6;16H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[27.865567, "o", "\u001b[?2004h"]
|
[27.095016, "o", "\u001b[6;15H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.397871, "o", "c"]
|
[27.244151, "o", "\u001b[6;14H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.402538, "o", "\b\u001b[32mc\u001b[39m"]
|
[27.393007, "o", "\u001b[6;13H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.440037, "o", "\b\u001b[32mc\u001b[39m\u001b[90margo run\u001b[39m\u001b[8D"]
|
[27.535424, "o", "\u001b[6;12H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.478861, "o", "\b\u001b[32mc\u001b[32ml\u001b[39m\u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \u001b[39m \b\b\b\b\b\b\b"]
|
[27.683701, "o", "\u001b[6;11H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.484213, "o", "\b\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31ml\u001b[0m\u001b[39m"]
|
[27.834869, "o", "\u001b[6;10H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.505375, "o", "\u001b[90mear\u001b[39m\b\b\b"]
|
[27.979099, "o", "\u001b[6;9H\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.619796, "o", "\b\b\u001b[1m\u001b[31mc\u001b[1m\u001b[31ml\u001b[1m\u001b[31me\u001b[0m\u001b[39m"]
|
[28.205228, "o", "\u001b[6;9Hi\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.674043, "o", "\b\u001b[1m\u001b[31me\u001b[1m\u001b[31ma\u001b[0m\u001b[39m"]
|
[28.335979, "o", "\u001b[6;10Hs\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.762176, "o", "\b\u001b[1m\u001b[31ma\u001b[1m\u001b[31mr\u001b[0m\u001b[39m"]
|
[28.402599, "o", "\u001b[6;11Hn\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.764782, "o", "\b\b\b\b\b\u001b[0m\u001b[32mc\u001b[0m\u001b[32ml\u001b[0m\u001b[32me\u001b[0m\u001b[32ma\u001b[0m\u001b[32mr\u001b[39m"]
|
[28.557636, "o", "\u001b[6;12H'\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.816778, "o", "\u001b[?1l\u001b>"]
|
[28.65723, "o", "\u001b[6;13Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.817025, "o", "\u001b[?2004l"]
|
[28.724086, "o", "\u001b[6;14H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.821673, "o", "\u001b[0 q"]
|
[28.89684, "o", "\u001b[6;15Hi\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.82202, "o", "\r\r\n"]
|
[29.052154, "o", "\u001b[6;16Hm\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.866023, "o", "\u001b[3J\u001b[H\u001b[2J"]
|
[29.168321, "o", "\u001b[6;17Hp\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.866306, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
[29.337871, "o", "\u001b[6;18Hl\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.910374, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0m\u001b[1;31mrs \u001b[0m\r\n\u001b[38;2;255;153;102m❯\u001b[0m \u001b[K"]
|
[29.444157, "o", "\u001b[6;19He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.911365, "o", "\u001b[6 q"]
|
[29.519464, "o", "\u001b[6;20Hm\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.912156, "o", "\u001b[6 q"]
|
[29.621293, "o", "\u001b[6;21He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[28.91233, "o", "\u001b[?2004h"]
|
[29.69862, "o", "\u001b[6;22Hn\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
[29.510648, "o", "\u001b[?2004l\r\r\n"]
|
[29.783626, "o", "\u001b[6;23Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[29.865553, "o", "\u001b[6;24He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[30.065802, "o", "\u001b[6;25Hd\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[30.132494, "o", "\u001b[6;26H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[30.296093, "o", "\u001b[6;27Hy\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[30.333876, "o", "\u001b[6;28He\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[30.432812, "o", "\u001b[6;29Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[30.507427, "o", "\u001b[6;30H \u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[30.652257, "o", "\u001b[6;31Ht\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[30.693191, "o", "\u001b[6;32Hh\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[30.845259, "o", "\u001b[6;33Ho\u001b[38;5;0m\u001b[48;5;5m \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[31.687752, "o", "\u001b[6;34H \u001b[33;5HVIEW\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[32.058966, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[32.119782, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[32.242868, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[33.6518, "o", "\u001b[33;1H: \u001b[33;5H \u001b[33;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[33.815445, "o", "\u001b[33;2Hw\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[33.932085, "o", "\u001b[33;3Hq\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[34.356435, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[34.357091, "o", "\u001b[1;10H \u001b[1;12H \u001b[1;19H \u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 2)\u001b[39m \u001b[4;1H [ ] I-can-add-items \u001b[5;1H [ ] on-the-backend \u001b[6;1H \u001b[6;9H \u001b[6;15H \u001b[6;27H \u001b[6;31H \u001b[6;123H \u001b[7;1Hsomething ~ (items: 2) \u001b[8;1H i-can-do-this ~ (items: 1) \u001b[9;1H something ~ (items: 3) \u001b[10;1H "]
|
||||||
|
[34.357262, "o", " \u001b[33;1H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[34.357672, "o", "\u001b[33;2H--\u001b[33;5HVIEW\u001b[33;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[34.358206, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[34.386872, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[34.387286, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[34.397925, "o", "\u001b[3;20H\u001b[38;2;255;165;0m3\u001b[6;5H\u001b[39m[\u001b[6;7H]\u001b[6;9Htoggle-isn't-implemented-yet-tho\u001b[7;1H \u001b[7;11H \u001b[7;13H \u001b[7;21H \u001b[8;1Hsomething ~ (items:\u001b[8;21H2) \u001b[8;29H \u001b[9;5Hi-can-do-this ~ (items:\u001b[9;29H1)\u001b[10;5Hsomething\u001b[10;15H~\u001b[10;17H(items:\u001b[10;25H3)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[34.398137, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[36.157515, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[36.609848, "o", "\u001b[33;1H: \u001b[33;5H \u001b[33;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[36.785846, "o", "\u001b[33;2Hq\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[40.410158, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[40.410779, "o", "\u001b[33;1H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[40.411947, "o", "\u001b[?1049l\u001b[?25h"]
|
||||||
|
[40.417589, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
||||||
|
[40.471035, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0mis \u001b[1;38;5;208m📦 \u001b[0m\u001b[1;38;5;208mv0.1.0\u001b[0m \u001b[1;31mrs \u001b[0m\u001b[33m37s\u001b[0m \r\n\u001b[38;2;255;153;102m❯\u001b[0m \u001b[K"]
|
||||||
|
[40.471969, "o", "\u001b[6 q"]
|
||||||
|
[40.472835, "o", "\u001b[6 q"]
|
||||||
|
[40.473001, "o", "\u001b[?2004h"]
|
||||||
|
[41.797938, "o", "\r\r\n"]
|
||||||
|
[41.821612, "o", "\u001b[?1049h\u001b[?1000h\u001b[?1002h\u001b[?1003h\u001b[?1015h\u001b[?1006h\u001b[?2004h\u001b[>13u"]
|
||||||
|
[41.976945, "o", "\u001b[1;2H\u001b[1mAtuin v18.2.0\u001b[1;26H\u001b[22m\u001b[38;5;8m \u001b[1m<esc>\u001b[22m: exit, \u001b[1m<tab>\u001b[22m: edit, \u001b[1m<enter>\u001b[22m: run, \u001b[1m<ctrl-o>\u001b[22m: inspect history count: 23952\u001b[2;3H\u001b[1m\u001b[38;5;15m\u001b[48;5;0mSearch\u001b[2;10H\u001b[22m\u001b[39m\u001b[49m│\u001b[2;12HInspect\u001b[3;2H╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮\u001b[4;2H│\u001b[4;6H\u001b[38;5;2m0s\u001b[4;13H\u001b[38;5;4m1h ago\u001b[4;20H\u001b[39mpsql\u001b[4;25H-h\u001b[4;28Hlocalhost\u001b[4;38H-p\u001b[4;41H26257\u001b[4;47H-U\u001b[4;50Hroot\u001b[4;55H-d\u001b[4;58Hdefaultdb\u001b[4;122H│\u001b[5;2H│\u001b[5;6H\u001b[38;5;1m124ms\u001b[5;13H\u001b[38;5;4m1h ago\u001b[5;20H\u001b[39mgrpcurl\u001b[5;28H-import-path\u001b[5;41Hcrates/hyperlog-protos/proto\u001b[5;70H-proto\u001b[5;77Hhyperlog.proto\u001b[5;92H-d\u001b[5;95H'{\"root\":\u001b[5;105H\"kjuulh\",\u001b[5;115H\"path\":│\u001b[6;2H│\u001b[6;6H\u001b[38;"]
|
||||||
|
[41.977026, "o", "5;1m33ms\u001b[6;13H\u001b[38;5;4m1h ago\u001b[6;20H\u001b[39mgrpcurl\u001b[6;28H-import-path\u001b[6;41Hcrates/hyperlog-protos/proto\u001b[6;70H-proto\u001b[6;77Hhyperlog.proto\u001b[6;92H-plaintext\u001b[6;103Hlocalhost:4000\u001b[6;118Hhype│\u001b[7;2H│\u001b[7;6H\u001b[38;5;1m688ms\u001b[7;13H\u001b[38;5;4m1h ago\u001b[7;20H\u001b[39mcargo\u001b[7;26Hrun\u001b[7;30H--\u001b[7;33H--backend\u001b[7;43Hremote\u001b[7;50Hcreate-root\u001b[7;122H│\u001b[8;2H│\u001b[8;6H\u001b[38;5;2m712ms\u001b[8;13H\u001b[38;5;4m1h ago\u001b[8;20H\u001b[39mcargo\u001b[8;26Hrun\u001b[8;30H--\u001b[8;33H--backend\u001b[8;43Hremote\u001b[8;50Hcreate-root\u001b[8;62H--name\u001b[8;69Hkjuulh\u001b[8;122H│\u001b[9;2H│\u001b[9;6H\u001b[38;5;1m721ms\u001b[9;13H\u001b[38;5;4m1h ago\u001b[9;20H\u001b[39mcargo\u001b[9;26Hrun\u001b[9;30H--\u001b[9;33H--backend\u001b[9;43Hremote\u001b[9;50Hcreate-section\u001b[9;65H--name\u001b[9;72Hkjuulh\u001b[9;122H│\u001b[10;2H│\u001b[10;6H\u001b[38;5;2m684ms\u001b[10;13H\u001b[38;5;4m1h ago\u001b[10;20H\u001b[39mcargo\u001b[10;26Hrun\u001b[10;30H--\u001b[10;33H--backend\u001b[10;43Hremote\u001b[10;50H-h\u001b[10;122H│\u001b[11;2H│\u001b[11;6H\u001b[38;5;1m746ms\u001b[11;13H\u001b[38;5;4m1h ago\u001b[11;20H\u001b[39mcargo\u001b[11;26Hrun\u001b[11;30H--\u001b[11;33H--backend\u001b[11;43Hremote\u001b[11;50Hexec\u001b[11;122H│\u001b[12;2H│\u001b[12;6H\u001b[38;5;1m698ms\u001b[12;13H\u001b[38;5;4m1"]
|
||||||
|
[41.977185, "o", "h ago\u001b[12;20H\u001b[39mcargo\u001b[12;26Hrun\u001b[12;30H--\u001b[12;33H--backend\u001b[12;43Hremote\u001b[12;50Hexec\u001b[12;55Hcreate-section\u001b[12;122H│\u001b[13;2H│\u001b[13;6H\u001b[38;5;1m727ms\u001b[13;13H\u001b[38;5;4m1h ago\u001b[13;20H\u001b[39mcargo\u001b[13;26Hrun\u001b[13;30H--\u001b[13;33H--backend\u001b[13;43Hremote\u001b[13;50Hexec\u001b[13;55Hcreate-section\u001b[13;70H--root\u001b[13;77Hkjuulh\u001b[13;122H│\u001b[14;2H│\u001b[14;6H\u001b[38;5;2m685ms\u001b[14;13H\u001b[38;5;4m1h ago\u001b[14;20H\u001b[39mcargo\u001b[14;26Hrun\u001b[14;30H--\u001b[14;33H--backend\u001b[14;43Hremote\u001b[14;50Hexec\u001b[14;55Hcreate-section\u001b[14;70H--root\u001b[14;77Hkjuulh\u001b[14;84H-h\u001b[14;122H│\u001b[15;2H│\u001b[15;6H\u001b[38;5;2m688ms\u001b[15;13H\u001b[38;5;4m1h ago\u001b[15;20H\u001b[39mcargo\u001b[15;26Hrun\u001b[15;30H--\u001b[15;33H--backend\u001b[15;43Hremote\u001b[15;50Hexec\u001b[15;55Hcreate-section\u001b[15;70H--root\u001b[15;77Hkjuulh\u001b[15;84H--path\u001b[15;91Hsomething\u001b[15;122H│\u001b[16;2H│\u001b[16;6H\u001b[38;5;2m742ms\u001b[16;13H\u001b[38;5;4m1h ago\u001b[16;20H\u001b[39mcargo\u001b[16;26Hrun\u001b[16;30H--\u001b[16;33H--backend\u001b[16;43Hremote\u001b[16;50Hexec\u001b[16;55Hcreate-section\u001b[16;70H--root\u001b[16;77Hkjuulh\u001b[16;84H--path\u001b[16;91Hsomething.something\u001b[16;122H│\u001b[17;2H│\u001b[17;6H\u001b[38;5;2m1s\u001b"]
|
||||||
|
[41.97723, "o", "[17;13H\u001b[38;5;4m1h ago\u001b[17;20H\u001b[39mcargo\u001b[17;26Hrun\u001b[17;30H--\u001b[17;33H--backend\u001b[17;43Hremote\u001b[17;50Hexec\u001b[17;55H-h\u001b[17;122H│\u001b[18;2H│\u001b[18;6H\u001b[38;5;2m31ms\u001b[18;13H\u001b[38;5;4m1h ago\u001b[18;20H\u001b[39mz\u001b[18;22Hhyperlog\u001b[18;122H│\u001b[19;2H│\u001b[19;6H\u001b[38;5;2m2s\u001b[19;13H\u001b[38;5;4m1h ago\u001b[19;20H\u001b[39mrm\u001b[19;23H-rf\u001b[19;27Htarget\u001b[19;122H│\u001b[20;2H│\u001b[20;6H\u001b[38;5;1m1s\u001b[20;13H\u001b[38;5;4m1h ago\u001b[20;20H\u001b[39mcargo\u001b[20;26Hrun\u001b[20;30H--\u001b[20;33H--backend\u001b[20;43Hremote\u001b[20;122H│\u001b[21;2H│\u001b[21;4H9\u001b[21;6H\u001b[38;5;2m1s\u001b[21;13H\u001b[38;5;4m1h ago\u001b[21;20H\u001b[39mgca\u001b[21;24H\"feat:\u001b[21;31Hcan\u001b[21;35Hget\u001b[21;39Hactual\u001b[21;46Havailable\u001b[21;56Hroots\"\u001b[21;122H│\u001b[22;2H│\u001b[22;4H8\u001b[22;6H\u001b[38;5;2m2s\u001b[22;13H\u001b[38;5;4m1h ago\u001b[22;20H\u001b[39mgp\u001b[22;122H│\u001b[23;2H│\u001b[23;4H7\u001b[23;6H\u001b[38;5;2m22ms\u001b[23;13H\u001b[38;5;4m1h ago\u001b[23;20H\u001b[39mclear\u001b[23;122H│\u001b[24;2H│\u001b[24;4H6\u001b[24;6H\u001b[38;5;1m86ms\u001b[24;12H\u001b[38;5;4m57m ago\u001b[24;20H\u001b[39mgrpcurl\u001b[24;28H-import-path\u001b[24;41Hcrates/hyperlog-protos/proto\u001b[24;70H-proto\u001b[24;77Hhyperlog.proto\u001b[24;92H-d\u001b[24;95H'{\"root\":\u001b[24;105H\"kjuulh\"}'\u001b["]
|
||||||
|
[41.977287, "o", "24;116H-plain│\u001b[25;2H│\u001b[25;4H5\u001b[25;6H\u001b[38;5;2m10s\u001b[25;12H\u001b[38;5;4m16m ago\u001b[25;20H\u001b[39mhyperlog\u001b[25;122H│\u001b[26;2H│\u001b[26;4H4\u001b[26;6H\u001b[38;5;2m38s\u001b[26;13H\u001b[38;5;4m3m ago\u001b[26;20H\u001b[39mcargo\u001b[26;26Hrun\u001b[26;30H--\u001b[26;33H--backend\u001b[26;43Hremote\u001b[26;122H│\u001b[27;2H│\u001b[27;4H3\u001b[27;6H\u001b[38;5;1m49s\u001b[27;13H\u001b[38;5;4m2m ago\u001b[27;20H\u001b[39mcargo\u001b[27;26Hrun\u001b[27;30H--release\u001b[27;40H--\u001b[27;43H--remote\u001b[27;52Hbackend\u001b[27;122H│\u001b[28;2H│\u001b[28;4H2\u001b[28;6H\u001b[38;5;2m42s\u001b[28;13H\u001b[38;5;4m1m ago\u001b[28;20H\u001b[39mcargo\u001b[28;26Hrun\u001b[28;30H--release\u001b[28;40H--\u001b[28;43H--backend\u001b[28;53Hremote\u001b[28;122H│\u001b[29;2H│\u001b[29;4H1\u001b[29;6H\u001b[38;5;2m0s\u001b[29;12H\u001b[38;5;4m42s ago\u001b[29;20H\u001b[39masciinema\u001b[29;30Hrec\u001b[29;34Hdemo.cast\u001b[29;44H--overwrite\u001b[29;122H│\u001b[30;2H│\u001b[30;4H>\u001b[30;6H\u001b[38;5;2m37s\u001b[30;12H\u001b[38;5;4m39s ago\u001b[1m\u001b[38;5;1m cargo run --release -- --backend remote\u001b[30;122H\u001b[22m\u001b[39m│\u001b[31;2H│───────────────────────────────────────────────────────"]
|
||||||
|
[41.97742, "o", "────────────────────────────────────────────────────────────────│\u001b[32;2H│[\u001b[32;8HGLOBAL\u001b[32;18H]\u001b[32;122H│\u001b[33;2H╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25h\u001b[32;20H"]
|
||||||
|
[42.066499, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25h\u001b[32;20H"]
|
||||||
|
[42.319608, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25h\u001b[32;20H"]
|
||||||
|
[42.571829, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25h\u001b[32;20H"]
|
||||||
|
[42.727815, "o", "\u001b[?1000h\u001b[?1002h\u001b[?1003h\u001b[?1015h\u001b[?1006h\u001b[?1006l\u001b[?1015l\u001b[?1003l\u001b[?1002l\u001b[?1000l"]
|
||||||
|
[42.728273, "o", "\u001b[<1u\u001b[?1049l\u001b[?1006l\u001b[?1015l\u001b[?1003l\u001b[?1002l\u001b[?1000l\u001b[?2004l"]
|
||||||
|
[42.802586, "o", "\u001b[A\r\u001b[A\u001b[A\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0mis \u001b[1;38;5;208m📦 \u001b[0m\u001b[1;38;5;208mv0.1.0\u001b[0m \u001b[1;31mrs \u001b[0m\u001b[33m37s\u001b[0m \r\n\u001b[38;2;255;153;102m❯\u001b[0m \u001b[K"]
|
||||||
|
[42.803675, "o", "cargo run --release -- --backend remote"]
|
||||||
|
[42.809234, "o", "\u001b[39D\u001b[32mc\u001b[32ma\u001b[32mr\u001b[32mg\u001b[32mo\u001b[39m\u001b[34C"]
|
||||||
|
[42.809682, "o", "\u001b[?1l\u001b>"]
|
||||||
|
[42.809828, "o", "\u001b[?2004l"]
|
||||||
|
[42.814158, "o", "\u001b[0 q"]
|
||||||
|
[42.814414, "o", "\r\r\n"]
|
||||||
|
[43.125266, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `GraphItem`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_item.rs:1:26\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse hyperlog_core::log::{GraphItem, ItemState};\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_imports)]` on by default\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `hyperlog_core::log::GraphItem`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_section.rs:1:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse hyperlog_core::log::GraphItem;\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused i"]
|
||||||
|
[43.125298, "o", "mports: `GraphItem`, `ItemState`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_available_roots.rs:1:26\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse hyperlog_core::log::{GraphItem, ItemState};\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\r\n"]
|
||||||
|
[43.125414, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `sqlx::types::Json`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_available_roots.rs:2:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m2\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse sqlx::types::Json;\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `root`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:102:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m102\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m root,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `root: _`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_variables)]` on by default\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variab"]
|
||||||
|
[43.125455, "o", "le: `path`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:103:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m103\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m path,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `path: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `title`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:104:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m104\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m title,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `title: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `description`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:105:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;"]
|
||||||
|
[43.125526, "o", "12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m105\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m description,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `description: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `state`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:106:17\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m106\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m state,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `state: _`\u001b[0m\r\n\r\n"]
|
||||||
|
[43.125576, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `root`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:108:35\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m108\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::ToggleItem { root, path } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `root: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `path`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:108:41\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m108\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::ToggleItem { root, path } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `path: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b["]
|
||||||
|
[43.125659, "o", "1m: unused variable: `root`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:109:29\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m109\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::Move { root, src, dest } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `root: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `src`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:109:35\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m109\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::Move { root, src, dest } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `src: _`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `dest`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0"]
|
||||||
|
[43.125748, "o", "m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/commands.rs:109:40\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m109\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m Command::Move { root, src, dest } => todo!(),\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: try ignoring the field: `dest: _`\u001b[0m\r\n\r\n"]
|
||||||
|
[43.125824, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused variable: `req`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_available_roots.rs:26:33\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m26\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub async fn execute(&self, req: Request) -> anyhow::Result<Response> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_req`\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `root_name` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_item.rs:31:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m29\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Root {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m----\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m30\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12"]
|
||||||
|
[43.125897, "o", "m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m31\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m root_name: String,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(dead_code)]` on by default\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `id` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_item.rs:36:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m35\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Section {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m-------\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m36\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `root_name` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m"]
|
||||||
|
[43.126022, "o", "\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/create_section.rs:19:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m17\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Root {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m----\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m18\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m19\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m root_name: String,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: field `id` is never read\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_graph.rs:37:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m36\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0mstruct Node {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m----\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12mfield in this struct\u001b[0"]
|
||||||
|
[43.126115, "o", "m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m37\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m id: uuid::Uuid,\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `Node` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis\u001b[0m\r\n\r\n\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: multiple associated items are never used\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog-server/src/services/get_graph.rs:152:16\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m151\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m impl Engine {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m-----------\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12massociated items in this implementation\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m152\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn engine_from_str(input: &str) -> anyhow::Result<Self> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m "]
|
||||||
|
[43.126211, "o", " \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m158\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn to_str(&self) -> anyhow::Result<String> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m219\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn get_mut(&mut self, root: &str, path: &[&str]) -> Option<&mut GraphItem> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m225\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn take(&mut self, root: &str, path: &[&str]) -> Option<GraphItem> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m231\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn section_move(\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b"]
|
||||||
|
[43.126253, "o", "[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m268\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn delete(&mut self, root: &str, path: &[&str]) -> anyhow::Result<()> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m274\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn toggle_item(&mut self, root: &str, path: &[&str]) -> anyhow::Result<()> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m290\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn update_item(\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m...\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m341\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m pub fn get_roots(&self) -> Option<Vec<String>> {\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0"]
|
||||||
|
[43.126325, "o", "m\u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\r\n\r\n"]
|
||||||
|
[43.126992, "o", "\u001b[0m\u001b[1m\u001b[33mwarning\u001b[0m\u001b[0m\u001b[1m: unused import: `std::net::SocketAddr`\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m--> \u001b[0m\u001b[0mcrates/hyperlog/src/cli.rs:1:5\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m\u001b[1m\u001b[38;5;12m1\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0muse std::net::SocketAddr;\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m|\u001b[0m\r\n\u001b[0m \u001b[0m\u001b[0m\u001b[1m\u001b[38;5;12m= \u001b[0m\u001b[0m\u001b[1mnote\u001b[0m\u001b[0m: `#[warn(unused_imports)]` on by default\u001b[0m\r\n\r\n"]
|
||||||
|
[43.127883, "o", "\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m:\u001b[0m `hyperlog-server` (lib) generated 20 warnings (run `cargo fix --lib -p hyperlog-server` to apply 4 suggestions)\r\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m:\u001b[0m `hyperlog` (bin \"hyperlog\") generated 1 warning (run `cargo fix --bin \"hyperlog\"` to apply 1 suggestion)\r\n"]
|
||||||
|
[43.1279, "o", "\u001b[1m\u001b[32m Finished\u001b[0m `release` profile [optimized] target(s) in 0.29s\r\n"]
|
||||||
|
[43.137727, "o", "\u001b[1m\u001b[32m Running\u001b[0m `target/release/hyperlog --backend remote`\r\n"]
|
||||||
|
[43.314772, "o", "\u001b[?1049h"]
|
||||||
|
[43.323208, "o", "\u001b[1;1H\u001b[38;5;2mhyperlog\u001b[2;1H\u001b[39m───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[3;1Hit-works\u001b[3;10H~\u001b[3;12H(items:\u001b[3;20H3)\u001b[4;5H[\u001b[4;7H]\u001b[4;9HI-can-add-items\u001b[5;5H\u001b[38;5;8m...\u001b[6;5H\u001b[39m[\u001b[6;7H]\u001b[6;9Htoggle-isn't-implemented-yet-tho\u001b[8;1Hsomething\u001b[8;11H~\u001b[8;13H(items:\u001b[8;21H2)\u001b[9;5Hi-can-do-this\u001b[9;19H~\u001b[9;21H(items:\u001b[9;29H1)\u001b[10;5Hsomething\u001b[10;15H~\u001b[10;17H(items:\u001b[10;25H3)\u001b[33;2H--\u001b[33;5HVIEW\u001b[33;10H--\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[43.929581, "o", "\u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 3)\u001b[5;5H\u001b[39m[ ]\u001b[5;9Hon-the-backend\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[44.075108, "o", "\u001b[3;1Hit-works ~ (items: 3)\u001b[4;5H\u001b[38;2;255;165;0m[ ] I-can-add-items\u001b[8;1H\u001b[39m \u001b[8;11H \u001b[8;13H \u001b[8;21H \u001b[9;5H \u001b[9;19H \u001b[9;21H \u001b[9;29H \u001b[10;5H \u001b[10;15H \u001b[10;17H \u001b[10;25H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[44.874727, "o", "\u001b[4;5H[ ] I-can-add-items\u001b[5;5H\u001b[38;2;255;165;0m[ ] on-the-backend\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[45.061332, "o", "\u001b[5;5H[ ] on-the-backend\u001b[6;5H\u001b[38;2;255;165;0m[ ] toggle-isn't-implemented-yet-tho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[45.206712, "o", "\u001b[5;5H\u001b[38;2;255;165;0m[ ] on-the-backend\u001b[6;5H\u001b[39m[ ] toggle-isn't-implemented-yet-tho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[45.37008, "o", "\u001b[4;5H\u001b[38;2;255;165;0m[ ] I-can-add-items\u001b[5;5H\u001b[39m[ ] on-the-backend\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[45.673509, "o", "\u001b[4;5H[ ] I-can-add-items\u001b[5;5H\u001b[38;2;255;165;0m[ ] on-the-backend\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[46.007542, "o", "\u001b[4;5H\u001b[38;2;255;165;0m[ ] I-can-add-items\u001b[5;5H\u001b[39m[ ] on-the-backend\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[46.189383, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[46.345585, "o", "\u001b[4;5H[ ] I-can-add-items\u001b[5;5H\u001b[38;2;255;165;0m[ ] on-the-backend\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[46.567759, "o", "\u001b[5;5H[ ] on-the-backend\u001b[6;5H\u001b[38;2;255;165;0m[ ] toggle-isn't-implemented-yet-tho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[46.698547, "o", "\u001b[5;5H\u001b[38;2;255;165;0m[ ] on-the-backend\u001b[6;5H\u001b[39m[ ] toggle-isn't-implemented-yet-tho\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[47.043763, "o", "\u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 3)\u001b[5;5H\u001b[39m[ ] on-the-backend\u001b[8;1Hsomething\u001b[8;11H~\u001b[8;13H(items:\u001b[8;21H2)\u001b[9;5Hi-can-do-this\u001b[9;19H~\u001b[9;21H(items:\u001b[9;29H1)\u001b[10;5Hsomething\u001b[10;15H~\u001b[10;17H(items:\u001b[10;25H3)\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[47.720694, "o", "\u001b[3;1Hit-works ~ (items: 3)\u001b[5;5H\u001b[38;5;8m...\u001b[5;9H\u001b[39m \u001b[8;1H\u001b[38;2;255;165;0msomething ~ (items: 2)\u001b[10;5H\u001b[39m [ ] with-\u001b[10;23H \u001b[10;25H \u001b[11;5Hsomething\u001b[11;15H~\u001b[11;17H(items:\u001b[11;25H3)\u001b[12;9H[\u001b[12;11H]\u001b[12;13Hand-another\u001b[13;9H\u001b[38;5;8m...\u001b[14;9H\u001b[39m[\u001b[14;11H]\u001b[14;13Hitem\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[47.90605, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[48.194979, "o", "\u001b[3;1H\u001b[38;2;255;165;0mit-works ~ (items: 3)\u001b[5;5H\u001b[39m[ ]\u001b[5;9Hon-the-backend\u001b[8;1Hsomething ~ (items: 2)\u001b[10;5Hsomething ~ (\u001b[10;23H:\u001b[10;25H3)\u001b[11;5H \u001b[11;15H \u001b[11;17H \u001b[11;25H \u001b[12;9H \u001b[12;11H \u001b[12;13H \u001b[13;9H \u001b[14;9H \u001b[14;11H \u001b[14;13H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[48.367241, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[49.205066, "o", "\u001b[33;1H: \u001b[33;5H \u001b[33;10H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[49.360314, "o", "\u001b[33;2Hq\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[49.565543, "o", "\u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[49.567508, "o", "\u001b[33;1H \u001b[39m\u001b[49m\u001b[59m\u001b[0m\u001b[?25l"]
|
||||||
|
[49.568849, "o", "\u001b[?1049l\u001b[?25h"]
|
||||||
|
[49.570699, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
||||||
|
[49.627447, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0mis \u001b[1;38;5;208m📦 \u001b[0m\u001b[1;38;5;208mv0.1.0\u001b[0m \u001b[1;31mrs \u001b[0m\u001b[33m6s\u001b[0m \r\n\u001b[38;2;255;153;102m❯\u001b[0m \u001b[K"]
|
||||||
|
[49.628604, "o", "\u001b[6 q"]
|
||||||
|
[49.629752, "o", "\u001b[6 q"]
|
||||||
|
[49.62996, "o", "\u001b[?2004h"]
|
||||||
|
[51.218262, "o", "\u001b[2 q"]
|
||||||
|
[51.265765, "o", "\r\r\u001b[A\u001b[A\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0mis \u001b[1;38;5;208m📦 \u001b[0m\u001b[1;38;5;208mv0.1.0\u001b[0m \u001b[1;31mrs \u001b[0m\u001b[33m6s\u001b[0m \r\n\u001b[32m❮\u001b[0m \u001b[K"]
|
||||||
|
[52.056217, "o", "\u001b[6 q"]
|
||||||
|
[52.095427, "o", "\r\r\u001b[A\u001b[A\u001b[0m\u001b[27m\u001b[24m\u001b[J\r\n\u001b[38;2;255;153;102mhyperlog\u001b[0m \u001b[90mmain\u001b[0m\u001b[38;2;255;153;102m \u001b[0mis \u001b[1;38;5;208m📦 \u001b[0m\u001b[1;38;5;208mv0.1.0\u001b[0m \u001b[1;31mrs \u001b[0m\u001b[33m6s\u001b[0m \r\n\u001b[38;2;255;153;102m❯\u001b[0m \u001b[K"]
|
||||||
|
[52.478423, "o", "\u001b[?2004l\r\r\n"]
|
||||||
|
15
scripts/dev.sh
Executable file
15
scripts/dev.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env zsh
|
||||||
|
|
||||||
|
echo "starting services"
|
||||||
|
docker compose -f templates/docker-compose.yaml up -d --remove-orphans
|
||||||
|
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
tear_down() {
|
||||||
|
echo "cleaning up services in the background"
|
||||||
|
(docker compose -f templates/docker-compose.yaml down -v &) > /dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
trap tear_down SIGINT
|
||||||
|
|
||||||
|
RUST_LOG=info,hyperlog=trace cargo watch -x 'run -F include_server -- serve'
|
5
scripts/install.sh
Executable file
5
scripts/install.sh
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/usr/bin/env zsh
|
||||||
|
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
cargo install --path crates/hyperlog --force
|
@@ -1,4 +1,3 @@
|
|||||||
version: "3"
|
|
||||||
services:
|
services:
|
||||||
crdb:
|
crdb:
|
||||||
restart: 'always'
|
restart: 'always'
|
||||||
@@ -11,5 +10,5 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
start_period: '20s'
|
start_period: '20s'
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 28080:8080
|
||||||
- '26257:26257'
|
- '26257:26257'
|
||||||
|
Reference in New Issue
Block a user