tokens: switch over to using token serialization
This removes all of the old serialization code Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
@@ -1,19 +1,15 @@
|
|||||||
use std::{collections::HashMap, fmt, str::FromStr};
|
use std::{collections::HashMap, fmt, str::FromStr};
|
||||||
|
|
||||||
use enostr::{NoteId, Pubkey};
|
use enostr::Pubkey;
|
||||||
use nostrdb::Ndb;
|
use nostrdb::Ndb;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
|
||||||
use strum_macros::EnumIter;
|
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
accounts::AccountsRoute,
|
|
||||||
column::{Columns, IntermediaryRoute},
|
column::{Columns, IntermediaryRoute},
|
||||||
decks::{Deck, Decks, DecksCache},
|
decks::{Deck, Decks, DecksCache},
|
||||||
route::Route,
|
route::Route,
|
||||||
timeline::{kind::ListKind, AlgoTimeline, PubkeySource, TimelineKind, TimelineRoute},
|
timeline::TimelineKind,
|
||||||
ui::add_column::{AddAlgoRoute, AddColumnRoute},
|
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -286,9 +282,9 @@ fn serialize_columns(columns: &Columns) -> Vec<Vec<String>> {
|
|||||||
for column in columns.columns() {
|
for column in columns.columns() {
|
||||||
let mut column_routes = Vec::new();
|
let mut column_routes = Vec::new();
|
||||||
for route in column.router().routes() {
|
for route in column.router().routes() {
|
||||||
if let Some(route_str) = serialize_route(route, columns) {
|
let mut writer = TokenWriter::default();
|
||||||
column_routes.push(route_str);
|
route.serialize_tokens(&mut writer);
|
||||||
}
|
column_routes.push(writer.str().to_string());
|
||||||
}
|
}
|
||||||
cols_serialized.push(column_routes);
|
cols_serialized.push(column_routes);
|
||||||
}
|
}
|
||||||
@@ -296,27 +292,26 @@ fn serialize_columns(columns: &Columns) -> Vec<Vec<String>> {
|
|||||||
cols_serialized
|
cols_serialized
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_columns(ndb: &Ndb, deck_user: &[u8; 32], serialized: Vec<Vec<String>>) -> Columns {
|
fn deserialize_columns(ndb: &Ndb, deck_user: &[u8; 32], columns: Vec<Vec<String>>) -> Columns {
|
||||||
let mut cols = Columns::new();
|
let mut cols = Columns::new();
|
||||||
for serialized_routes in serialized {
|
for column in columns {
|
||||||
let mut cur_routes = Vec::new();
|
let mut cur_routes = Vec::new();
|
||||||
for serialized_route in serialized_routes {
|
|
||||||
let selections = Selection::from_serialized(&serialized_route);
|
for route in column {
|
||||||
if let Some(route_intermediary) = selections_to_route(&selections) {
|
let tokens: Vec<&str> = route.split(":").collect();
|
||||||
if let Some(ir) = route_intermediary.intermediary_route(ndb, Some(deck_user)) {
|
let mut parser = TokenParser::new(&tokens);
|
||||||
match &ir {
|
|
||||||
IntermediaryRoute::Route(Route::Timeline(TimelineRoute::Thread(_)))
|
match CleanIntermediaryRoute::parse_from_tokens(&mut parser) {
|
||||||
| IntermediaryRoute::Route(Route::Timeline(TimelineRoute::Profile(_))) => {
|
Ok(route_intermediary) => {
|
||||||
// Do nothing. TimelineRoute Threads & Profiles not yet supported for deserialization
|
if let Some(ir) =
|
||||||
}
|
route_intermediary.into_intermediary_route(ndb, Some(deck_user))
|
||||||
_ => cur_routes.push(ir),
|
{
|
||||||
|
cur_routes.push(ir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
Err(err) => {
|
||||||
error!(
|
error!("could not turn tokens to RouteIntermediary: {:?}", err);
|
||||||
"could not turn selections to RouteIntermediary: {:?}",
|
}
|
||||||
selections
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,223 +323,17 @@ fn deserialize_columns(ndb: &Ndb, deck_user: &[u8; 32], serialized: Vec<Vec<Stri
|
|||||||
cols
|
cols
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Different token types for our deck serializer/deserializer
|
|
||||||
///
|
|
||||||
/// We have more than one token type so that we can avoid match catch-alls
|
|
||||||
/// in different parts of our parser.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
enum Selection {
|
|
||||||
Keyword(Keyword),
|
|
||||||
Algo(AlgoKeyword),
|
|
||||||
List(ListKeyword),
|
|
||||||
PubkeySource(PubkeySourceKeyword),
|
|
||||||
Payload(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Selection {
|
|
||||||
type Err = Error;
|
|
||||||
|
|
||||||
fn from_str(serialized: &str) -> Result<Self, Self::Err> {
|
|
||||||
Ok(parse_selection(serialized))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, EnumIter)]
|
|
||||||
enum AlgoKeyword {
|
|
||||||
LastPerPubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AlgoKeyword {
|
|
||||||
#[inline]
|
|
||||||
pub fn name(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
AlgoKeyword::LastPerPubkey => "last_per_pubkey",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, EnumIter)]
|
|
||||||
enum ListKeyword {
|
|
||||||
Contact,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ListKeyword {
|
|
||||||
#[inline]
|
|
||||||
pub fn name(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
ListKeyword::Contact => "contact",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, EnumIter)]
|
|
||||||
enum PubkeySourceKeyword {
|
|
||||||
Explicit,
|
|
||||||
DeckAuthor,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PubkeySourceKeyword {
|
|
||||||
#[inline]
|
|
||||||
pub fn name(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
PubkeySourceKeyword::Explicit => "explicit",
|
|
||||||
PubkeySourceKeyword::DeckAuthor => "deck_author",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, EnumIter)]
|
|
||||||
enum Keyword {
|
|
||||||
Notifs,
|
|
||||||
Universe,
|
|
||||||
Profile,
|
|
||||||
Hashtag,
|
|
||||||
Generic,
|
|
||||||
Thread,
|
|
||||||
Reply,
|
|
||||||
Quote,
|
|
||||||
Account,
|
|
||||||
Show,
|
|
||||||
New,
|
|
||||||
Relay,
|
|
||||||
Compose,
|
|
||||||
Column,
|
|
||||||
AlgoSelection,
|
|
||||||
NotificationSelection,
|
|
||||||
ExternalNotifSelection,
|
|
||||||
HashtagSelection,
|
|
||||||
Support,
|
|
||||||
Deck,
|
|
||||||
Edit,
|
|
||||||
IndividualSelection,
|
|
||||||
ExternalIndividualSelection,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Keyword {
|
|
||||||
fn name(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
Keyword::Notifs => "notifs",
|
|
||||||
Keyword::Universe => "universe",
|
|
||||||
Keyword::Profile => "profile",
|
|
||||||
Keyword::Hashtag => "hashtag",
|
|
||||||
Keyword::Generic => "generic",
|
|
||||||
Keyword::Thread => "thread",
|
|
||||||
Keyword::Reply => "reply",
|
|
||||||
Keyword::Quote => "quote",
|
|
||||||
Keyword::Account => "account",
|
|
||||||
Keyword::Show => "show",
|
|
||||||
Keyword::New => "new",
|
|
||||||
Keyword::Relay => "relay",
|
|
||||||
Keyword::Compose => "compose",
|
|
||||||
Keyword::Column => "column",
|
|
||||||
Keyword::AlgoSelection => "algo_selection",
|
|
||||||
Keyword::NotificationSelection => "notification_selection",
|
|
||||||
Keyword::ExternalNotifSelection => "external_notif_selection",
|
|
||||||
Keyword::IndividualSelection => "individual_selection",
|
|
||||||
Keyword::ExternalIndividualSelection => "external_individual_selection",
|
|
||||||
Keyword::HashtagSelection => "hashtag_selection",
|
|
||||||
Keyword::Support => "support",
|
|
||||||
Keyword::Deck => "deck",
|
|
||||||
Keyword::Edit => "edit",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Keyword {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for AlgoKeyword {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ListKeyword {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for PubkeySourceKeyword {
|
|
||||||
type Err = Error;
|
|
||||||
|
|
||||||
fn from_str(serialized: &str) -> Result<Self, Self::Err> {
|
|
||||||
for keyword in Self::iter() {
|
|
||||||
if serialized == keyword.name() {
|
|
||||||
return Ok(keyword);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::Generic(
|
|
||||||
"Could not convert string to Keyword enum".to_owned(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for ListKeyword {
|
|
||||||
type Err = Error;
|
|
||||||
|
|
||||||
fn from_str(serialized: &str) -> Result<Self, Self::Err> {
|
|
||||||
for keyword in Self::iter() {
|
|
||||||
if serialized == keyword.name() {
|
|
||||||
return Ok(keyword);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::Generic(
|
|
||||||
"Could not convert string to Keyword enum".to_owned(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for PubkeySourceKeyword {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for AlgoKeyword {
|
|
||||||
type Err = Error;
|
|
||||||
|
|
||||||
fn from_str(serialized: &str) -> Result<Self, Self::Err> {
|
|
||||||
for keyword in Self::iter() {
|
|
||||||
if serialized == keyword.name() {
|
|
||||||
return Ok(keyword);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::Generic(
|
|
||||||
"Could not convert string to Keyword enum".to_owned(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Keyword {
|
|
||||||
type Err = Error;
|
|
||||||
|
|
||||||
fn from_str(serialized: &str) -> Result<Self, Self::Err> {
|
|
||||||
for keyword in Self::iter() {
|
|
||||||
if serialized == keyword.name() {
|
|
||||||
return Ok(keyword);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::Generic(
|
|
||||||
"Could not convert string to Keyword enum".to_owned(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CleanIntermediaryRoute {
|
enum CleanIntermediaryRoute {
|
||||||
ToTimeline(TimelineKind),
|
ToTimeline(TimelineKind),
|
||||||
ToRoute(Route),
|
ToRoute(Route),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CleanIntermediaryRoute {
|
impl CleanIntermediaryRoute {
|
||||||
fn intermediary_route(self, ndb: &Ndb, user: Option<&[u8; 32]>) -> Option<IntermediaryRoute> {
|
fn into_intermediary_route(
|
||||||
|
self,
|
||||||
|
ndb: &Ndb,
|
||||||
|
user: Option<&[u8; 32]>,
|
||||||
|
) -> Option<IntermediaryRoute> {
|
||||||
match self {
|
match self {
|
||||||
CleanIntermediaryRoute::ToTimeline(timeline_kind) => Some(IntermediaryRoute::Timeline(
|
CleanIntermediaryRoute::ToTimeline(timeline_kind) => Some(IntermediaryRoute::Timeline(
|
||||||
timeline_kind.into_timeline(ndb, user)?,
|
timeline_kind.into_timeline(ndb, user)?,
|
||||||
@@ -554,411 +343,35 @@ impl CleanIntermediaryRoute {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: The public-accessible version will be a subset of this
|
impl TokenSerializable for CleanIntermediaryRoute {
|
||||||
fn serialize_route(route: &Route, columns: &Columns) -> Option<String> {
|
fn serialize_tokens(&self, writer: &mut TokenWriter) {
|
||||||
let mut selections: Vec<Selection> = Vec::new();
|
|
||||||
match route {
|
|
||||||
Route::Timeline(timeline_route) => match timeline_route {
|
|
||||||
TimelineRoute::Timeline(timeline_id) => {
|
|
||||||
if let Some(timeline) = columns.find_timeline(*timeline_id) {
|
|
||||||
match &timeline.kind {
|
|
||||||
TimelineKind::List(list_kind) => match list_kind {
|
|
||||||
ListKind::Contact(pubkey_source) => {
|
|
||||||
selections.push(Selection::List(ListKeyword::Contact));
|
|
||||||
selections.extend(generate_pubkey_selections(pubkey_source));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
TimelineKind::Algo(AlgoTimeline::LastPerPubkey(list_kind)) => {
|
|
||||||
match list_kind {
|
|
||||||
ListKind::Contact(pk_src) => {
|
|
||||||
selections.push(Selection::Algo(AlgoKeyword::LastPerPubkey));
|
|
||||||
selections.push(Selection::List(ListKeyword::Contact));
|
|
||||||
selections.extend(generate_pubkey_selections(pk_src));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TimelineKind::Notifications(pubkey_source) => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Notifs));
|
|
||||||
selections.extend(generate_pubkey_selections(pubkey_source));
|
|
||||||
}
|
|
||||||
TimelineKind::Profile(pubkey_source) => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Profile));
|
|
||||||
selections.extend(generate_pubkey_selections(pubkey_source));
|
|
||||||
}
|
|
||||||
TimelineKind::Universe => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Universe))
|
|
||||||
}
|
|
||||||
TimelineKind::Thread(root_id) => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Thread));
|
|
||||||
selections.push(Selection::Payload(hex::encode(root_id.bytes())));
|
|
||||||
}
|
|
||||||
TimelineKind::Generic => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Generic))
|
|
||||||
}
|
|
||||||
TimelineKind::Hashtag(hashtag) => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Hashtag));
|
|
||||||
selections.push(Selection::Payload(hashtag.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TimelineRoute::Thread(note_id) => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Thread));
|
|
||||||
selections.push(Selection::Payload(note_id.hex()));
|
|
||||||
}
|
|
||||||
TimelineRoute::Profile(pubkey) => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Profile));
|
|
||||||
selections.push(Selection::PubkeySource(PubkeySourceKeyword::Explicit));
|
|
||||||
selections.push(Selection::Payload(pubkey.hex()));
|
|
||||||
}
|
|
||||||
TimelineRoute::Reply(note_id) => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Reply));
|
|
||||||
selections.push(Selection::Payload(note_id.hex()));
|
|
||||||
}
|
|
||||||
TimelineRoute::Quote(note_id) => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Quote));
|
|
||||||
selections.push(Selection::Payload(note_id.hex()));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Route::Accounts(accounts_route) => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Account));
|
|
||||||
match accounts_route {
|
|
||||||
AccountsRoute::Accounts => selections.push(Selection::Keyword(Keyword::Show)),
|
|
||||||
AccountsRoute::AddAccount => selections.push(Selection::Keyword(Keyword::New)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Route::Relays => selections.push(Selection::Keyword(Keyword::Relay)),
|
|
||||||
Route::ComposeNote => selections.push(Selection::Keyword(Keyword::Compose)),
|
|
||||||
Route::AddColumn(add_column_route) => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Column));
|
|
||||||
match add_column_route {
|
|
||||||
AddColumnRoute::Base => (),
|
|
||||||
AddColumnRoute::Algo(algo_route) => match algo_route {
|
|
||||||
AddAlgoRoute::Base => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::AlgoSelection))
|
|
||||||
}
|
|
||||||
|
|
||||||
AddAlgoRoute::LastPerPubkey => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::AlgoSelection));
|
|
||||||
selections.push(Selection::Algo(AlgoKeyword::LastPerPubkey));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AddColumnRoute::UndecidedNotification => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::NotificationSelection))
|
|
||||||
}
|
|
||||||
AddColumnRoute::ExternalNotification => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::ExternalNotifSelection))
|
|
||||||
}
|
|
||||||
AddColumnRoute::Hashtag => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::HashtagSelection))
|
|
||||||
}
|
|
||||||
AddColumnRoute::UndecidedIndividual => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::IndividualSelection))
|
|
||||||
}
|
|
||||||
AddColumnRoute::ExternalIndividual => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::ExternalIndividualSelection))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Route::Support => selections.push(Selection::Keyword(Keyword::Support)),
|
|
||||||
Route::NewDeck => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Deck));
|
|
||||||
selections.push(Selection::Keyword(Keyword::New));
|
|
||||||
}
|
|
||||||
Route::EditDeck(index) => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Deck));
|
|
||||||
selections.push(Selection::Keyword(Keyword::Edit));
|
|
||||||
selections.push(Selection::Payload(index.to_string()));
|
|
||||||
}
|
|
||||||
Route::EditProfile(pubkey) => {
|
|
||||||
selections.push(Selection::Keyword(Keyword::Profile));
|
|
||||||
selections.push(Selection::Keyword(Keyword::Edit));
|
|
||||||
selections.push(Selection::Payload(pubkey.hex()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if selections.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(
|
|
||||||
selections
|
|
||||||
.iter()
|
|
||||||
.map(|k| k.to_string())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(":"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_pubkey_selections(source: &PubkeySource) -> Vec<Selection> {
|
|
||||||
let mut selections = Vec::new();
|
|
||||||
match source {
|
|
||||||
PubkeySource::Explicit(pubkey) => {
|
|
||||||
selections.push(Selection::PubkeySource(PubkeySourceKeyword::Explicit));
|
|
||||||
selections.push(Selection::Payload(pubkey.hex()));
|
|
||||||
}
|
|
||||||
PubkeySource::DeckAuthor => {
|
|
||||||
selections.push(Selection::PubkeySource(PubkeySourceKeyword::DeckAuthor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selections
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parses a selection
|
|
||||||
fn parse_selection(token: &str) -> Selection {
|
|
||||||
AlgoKeyword::from_str(token)
|
|
||||||
.map(Selection::Algo)
|
|
||||||
.or_else(|_| ListKeyword::from_str(token).map(Selection::List))
|
|
||||||
.or_else(|_| PubkeySourceKeyword::from_str(token).map(Selection::PubkeySource))
|
|
||||||
.or_else(|_| Keyword::from_str(token).map(Selection::Keyword))
|
|
||||||
.unwrap_or_else(|_| Selection::Payload(token.to_owned()))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Selection {
|
|
||||||
fn from_serialized(buffer: &str) -> Vec<Self> {
|
|
||||||
let mut selections = Vec::new();
|
|
||||||
let seperator = ":";
|
|
||||||
let sep_len = seperator.len();
|
|
||||||
let mut pos = 0;
|
|
||||||
|
|
||||||
while let Some(offset) = buffer[pos..].find(seperator) {
|
|
||||||
selections.push(parse_selection(&buffer[pos..pos + offset]));
|
|
||||||
pos = pos + offset + sep_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
selections.push(parse_selection(&buffer[pos..]));
|
|
||||||
|
|
||||||
selections
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse an explicit:abdef... or deck_author from a Selection token stream.
|
|
||||||
///
|
|
||||||
/// Also handle the case where there is nothing. We assume this means deck_author.
|
|
||||||
fn parse_pubkey_src_selection(tokens: &[Selection]) -> Option<PubkeySource> {
|
|
||||||
match tokens.first() {
|
|
||||||
// we handle bare payloads and assume they are explicit pubkey sources
|
|
||||||
Some(Selection::Payload(hex)) => {
|
|
||||||
let pk = Pubkey::from_hex(hex.as_str()).ok()?;
|
|
||||||
Some(PubkeySource::Explicit(pk))
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Selection::PubkeySource(PubkeySourceKeyword::Explicit)) => {
|
|
||||||
if let Selection::Payload(hex) = tokens.get(1)? {
|
|
||||||
let pk = Pubkey::from_hex(hex.as_str()).ok()?;
|
|
||||||
Some(PubkeySource::Explicit(pk))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None | Some(Selection::PubkeySource(PubkeySourceKeyword::DeckAuthor)) => {
|
|
||||||
Some(PubkeySource::DeckAuthor)
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Selection::Keyword(_kw)) => None,
|
|
||||||
Some(Selection::Algo(_kw)) => None,
|
|
||||||
Some(Selection::List(_kw)) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse ListKinds from Selections
|
|
||||||
fn parse_list_kind_selections(tokens: &[Selection]) -> Option<ListKind> {
|
|
||||||
// only list selections are valid in this position
|
|
||||||
let list_kw = if let Selection::List(list_kw) = tokens.first()? {
|
|
||||||
list_kw
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let pubkey_src = parse_pubkey_src_selection(&tokens[1..])?;
|
|
||||||
|
|
||||||
Some(match list_kw {
|
|
||||||
ListKeyword::Contact => ListKind::contact_list(pubkey_src),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selections_to_route(selections: &[Selection]) -> Option<CleanIntermediaryRoute> {
|
|
||||||
match selections.first()? {
|
|
||||||
Selection::Keyword(Keyword::AlgoSelection) => {
|
|
||||||
let r = match selections.get(1) {
|
|
||||||
None => AddColumnRoute::Algo(AddAlgoRoute::Base),
|
|
||||||
Some(Selection::Algo(algo_kw)) => match algo_kw {
|
|
||||||
AlgoKeyword::LastPerPubkey => AddColumnRoute::Algo(AddAlgoRoute::LastPerPubkey),
|
|
||||||
},
|
|
||||||
// other keywords are invalid here
|
|
||||||
Some(_) => {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(CleanIntermediaryRoute::ToRoute(Route::AddColumn(r)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Algorithm timelines
|
|
||||||
Selection::Algo(algo_kw) => {
|
|
||||||
let timeline_kind = match algo_kw {
|
|
||||||
AlgoKeyword::LastPerPubkey => {
|
|
||||||
let list_kind = parse_list_kind_selections(&selections[1..])?;
|
|
||||||
TimelineKind::last_per_pubkey(list_kind)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(CleanIntermediaryRoute::ToTimeline(timeline_kind))
|
|
||||||
}
|
|
||||||
|
|
||||||
// We never have PubkeySource keywords at the top level
|
|
||||||
Selection::PubkeySource(_pk_src) => None,
|
|
||||||
|
|
||||||
Selection::List(ListKeyword::Contact) => {
|
|
||||||
// only pubkey/src is allowed in this position
|
|
||||||
let pubkey_src = parse_pubkey_src_selection(&selections[1..])?;
|
|
||||||
Some(CleanIntermediaryRoute::ToTimeline(
|
|
||||||
TimelineKind::contact_list(pubkey_src),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
Selection::Keyword(Keyword::Notifs) => {
|
|
||||||
let pubkey_src = parse_pubkey_src_selection(&selections[1..])?;
|
|
||||||
Some(CleanIntermediaryRoute::ToTimeline(
|
|
||||||
TimelineKind::notifications(pubkey_src),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
Selection::Keyword(Keyword::Profile) => {
|
|
||||||
// we only expect PubkeySource in this position
|
|
||||||
let pubkey_src = parse_pubkey_src_selection(&selections[1..])?;
|
|
||||||
Some(CleanIntermediaryRoute::ToTimeline(TimelineKind::profile(
|
|
||||||
pubkey_src,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
Selection::Keyword(Keyword::Universe) => {
|
|
||||||
Some(CleanIntermediaryRoute::ToTimeline(TimelineKind::Universe))
|
|
||||||
}
|
|
||||||
|
|
||||||
Selection::Keyword(Keyword::Hashtag) => {
|
|
||||||
if let Selection::Payload(hashtag) = selections.get(1)? {
|
|
||||||
Some(CleanIntermediaryRoute::ToTimeline(TimelineKind::Hashtag(
|
|
||||||
hashtag.to_string(),
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Selection::Keyword(Keyword::Generic) => {
|
|
||||||
Some(CleanIntermediaryRoute::ToTimeline(TimelineKind::Generic))
|
|
||||||
}
|
|
||||||
|
|
||||||
Selection::Keyword(Keyword::Thread) => {
|
|
||||||
if let Selection::Payload(hex) = selections.get(1)? {
|
|
||||||
Some(CleanIntermediaryRoute::ToRoute(Route::thread(
|
|
||||||
NoteId::from_hex(hex.as_str()).ok()?,
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Selection::Keyword(Keyword::Reply) => {
|
|
||||||
if let Selection::Payload(hex) = selections.get(1)? {
|
|
||||||
Some(CleanIntermediaryRoute::ToRoute(Route::reply(
|
|
||||||
NoteId::from_hex(hex.as_str()).ok()?,
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Selection::Keyword(Keyword::Quote) => {
|
|
||||||
if let Selection::Payload(hex) = selections.get(1)? {
|
|
||||||
Some(CleanIntermediaryRoute::ToRoute(Route::quote(
|
|
||||||
NoteId::from_hex(hex.as_str()).ok()?,
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Selection::Keyword(Keyword::Account) => match selections.get(1)? {
|
|
||||||
Selection::Keyword(Keyword::Show) => Some(CleanIntermediaryRoute::ToRoute(
|
|
||||||
Route::Accounts(AccountsRoute::Accounts),
|
|
||||||
)),
|
|
||||||
Selection::Keyword(Keyword::New) => Some(CleanIntermediaryRoute::ToRoute(
|
|
||||||
Route::Accounts(AccountsRoute::AddAccount),
|
|
||||||
)),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
Selection::Keyword(Keyword::Relay) => Some(CleanIntermediaryRoute::ToRoute(Route::Relays)),
|
|
||||||
Selection::Keyword(Keyword::Compose) => {
|
|
||||||
Some(CleanIntermediaryRoute::ToRoute(Route::ComposeNote))
|
|
||||||
}
|
|
||||||
Selection::Keyword(Keyword::Column) => match selections.get(1)? {
|
|
||||||
Selection::Keyword(Keyword::NotificationSelection) => {
|
|
||||||
Some(CleanIntermediaryRoute::ToRoute(Route::AddColumn(
|
|
||||||
AddColumnRoute::UndecidedNotification,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
Selection::Keyword(Keyword::ExternalNotifSelection) => {
|
|
||||||
Some(CleanIntermediaryRoute::ToRoute(Route::AddColumn(
|
|
||||||
AddColumnRoute::ExternalNotification,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
Selection::Keyword(Keyword::HashtagSelection) => Some(CleanIntermediaryRoute::ToRoute(
|
|
||||||
Route::AddColumn(AddColumnRoute::Hashtag),
|
|
||||||
)),
|
|
||||||
Selection::Keyword(Keyword::IndividualSelection) => {
|
|
||||||
Some(CleanIntermediaryRoute::ToRoute(Route::AddColumn(
|
|
||||||
AddColumnRoute::UndecidedIndividual,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
Selection::Keyword(Keyword::ExternalIndividualSelection) => {
|
|
||||||
Some(CleanIntermediaryRoute::ToRoute(Route::AddColumn(
|
|
||||||
AddColumnRoute::ExternalIndividual,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
Selection::Keyword(Keyword::Support) => {
|
|
||||||
Some(CleanIntermediaryRoute::ToRoute(Route::Support))
|
|
||||||
}
|
|
||||||
Selection::Keyword(Keyword::Deck) => match selections.get(1)? {
|
|
||||||
Selection::Keyword(Keyword::New) => {
|
|
||||||
Some(CleanIntermediaryRoute::ToRoute(Route::NewDeck))
|
|
||||||
}
|
|
||||||
Selection::Keyword(Keyword::Edit) => {
|
|
||||||
if let Selection::Payload(index_str) = selections.get(2)? {
|
|
||||||
let parsed_index = index_str.parse::<usize>().ok()?;
|
|
||||||
Some(CleanIntermediaryRoute::ToRoute(Route::EditDeck(
|
|
||||||
parsed_index,
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
Selection::Payload(_)
|
|
||||||
| Selection::Keyword(Keyword::New)
|
|
||||||
| Selection::Keyword(Keyword::Show)
|
|
||||||
| Selection::Keyword(Keyword::NotificationSelection)
|
|
||||||
| Selection::Keyword(Keyword::ExternalNotifSelection)
|
|
||||||
| Selection::Keyword(Keyword::HashtagSelection)
|
|
||||||
| Selection::Keyword(Keyword::IndividualSelection)
|
|
||||||
| Selection::Keyword(Keyword::ExternalIndividualSelection)
|
|
||||||
| Selection::Keyword(Keyword::Edit) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Selection {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
match self {
|
||||||
Selection::Keyword(keyword) => write!(f, "{}", keyword),
|
CleanIntermediaryRoute::ToTimeline(tlk) => {
|
||||||
Selection::Payload(payload) => write!(f, "{}", payload),
|
tlk.serialize_tokens(writer);
|
||||||
Selection::Algo(algo_kw) => write!(f, "{}", algo_kw),
|
}
|
||||||
Selection::List(list_kw) => write!(f, "{}", list_kw),
|
CleanIntermediaryRoute::ToRoute(route) => {
|
||||||
Selection::PubkeySource(pk_src_kw) => write!(f, "{}", pk_src_kw),
|
route.serialize_tokens(writer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_from_tokens<'a>(parser: &mut TokenParser<'a>) -> Result<Self, ParseError<'a>> {
|
||||||
|
TokenParser::alt(
|
||||||
|
parser,
|
||||||
|
&[
|
||||||
|
|p| {
|
||||||
|
Ok(CleanIntermediaryRoute::ToTimeline(
|
||||||
|
TimelineKind::parse_from_tokens(p)?,
|
||||||
|
))
|
||||||
|
},
|
||||||
|
|p| {
|
||||||
|
Ok(CleanIntermediaryRoute::ToRoute(Route::parse_from_tokens(
|
||||||
|
p,
|
||||||
|
)?))
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in New Issue
Block a user