aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils/src
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2024-05-15 11:20:36 +0200
committerGitHub <noreply@github.com>2024-05-15 11:20:36 +0200
commit90a62217fdd033fb2dd99b6f2ee3a945ae6b9baa (patch)
treeb0c701b1956b3eee058cfc2cd2674e74503478d3 /zellij-utils/src
parentchore(version): bump development version (diff)
downloadsl0ppij-90a62217fdd033fb2dd99b6f2ee3a945ae6b9baa.tar.gz
sl0ppij-90a62217fdd033fb2dd99b6f2ee3a945ae6b9baa.tar.xz
sl0ppij-90a62217fdd033fb2dd99b6f2ee3a945ae6b9baa.zip
feat(layouts): allow consuming a layout from a url (#3351)
* feat(cli): allow loading layouts directly from a url * feat(plugins): allow loading layouts directly from a url * style(fmt): rustfmt
Diffstat (limited to 'zellij-utils/src')
-rw-r--r--zellij-utils/src/data.rs3
-rw-r--r--zellij-utils/src/downloader.rs25
-rw-r--r--zellij-utils/src/input/actions.rs20
-rw-r--r--zellij-utils/src/input/config.rs2
-rw-r--r--zellij-utils/src/input/layout.rs38
-rw-r--r--zellij-utils/src/kdl/mod.rs1
-rw-r--r--zellij-utils/src/plugin_api/event.rs5
-rw-r--r--zellij-utils/src/setup.rs20
8 files changed, 109 insertions, 5 deletions
diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs
index 9914a797..c09c0d43 100644
--- a/zellij-utils/src/data.rs
+++ b/zellij-utils/src/data.rs
@@ -797,6 +797,7 @@ pub struct SessionInfo {
pub enum LayoutInfo {
BuiltIn(String),
File(String),
+ Url(String),
}
impl LayoutInfo {
@@ -804,12 +805,14 @@ impl LayoutInfo {
match self {
LayoutInfo::BuiltIn(name) => &name,
LayoutInfo::File(name) => &name,
+ LayoutInfo::Url(url) => &url,
}
}
pub fn is_builtin(&self) -> bool {
match self {
LayoutInfo::BuiltIn(_name) => true,
LayoutInfo::File(_name) => false,
+ LayoutInfo::Url(_url) => false,
}
}
}
diff --git a/zellij-utils/src/downloader.rs b/zellij-utils/src/downloader.rs
index aca0c8cd..b92a0ff0 100644
--- a/zellij-utils/src/downloader.rs
+++ b/zellij-utils/src/downloader.rs
@@ -16,6 +16,8 @@ pub enum DownloaderError {
Io(#[source] std::io::Error),
#[error("File name cannot be found in URL: {0}")]
NotFoundFileName(String),
+ #[error("Failed to parse URL body: {0}")]
+ InvalidUrlBody(String),
}
#[derive(Debug)]
@@ -110,6 +112,29 @@ impl Downloader {
Ok(())
}
+ pub async fn download_without_cache(url: &str) -> Result<String, DownloaderError> {
+ // result is the stringified body
+ let client = surf::client().with(surf::middleware::Redirect::default());
+
+ let res = client
+ .get(url)
+ .header("Content-Type", "application/octet-stream")
+ .await
+ .map_err(|e| DownloaderError::Request(e))?;
+
+ let mut downloaded_bytes: Vec<u8> = vec![];
+ let mut stream = res.bytes();
+ while let Some(byte) = stream.next().await {
+ let byte = byte.map_err(|e| DownloaderError::Io(e))?;
+ downloaded_bytes.push(byte);
+ }
+
+ log::debug!("Download complete");
+ let stringified = String::from_utf8(downloaded_bytes)
+ .map_err(|e| DownloaderError::InvalidUrlBody(format!("{}", e)))?;
+
+ Ok(stringified)
+ }
fn parse_name(&self, url: &str) -> Result<String, DownloaderError> {
Url::parse(url)
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs
index e36ca763..7cc3dd30 100644
--- a/zellij-utils/src/input/actions.rs
+++ b/zellij-utils/src/input/actions.rs
@@ -529,9 +529,25 @@ impl Action {
let layout_dir = layout_dir
.or_else(|| config.and_then(|c| c.options.layout_dir))
.or_else(|| get_layout_dir(find_default_config_dir()));
- let (path_to_raw_layout, raw_layout, swap_layouts) =
+
+ let (path_to_raw_layout, raw_layout, swap_layouts) = if let Some(layout_url) =
+ layout_path.to_str().and_then(|l| {
+ if l.starts_with("http://") || l.starts_with("https://") {
+ Some(l)
+ } else {
+ None
+ }
+ }) {
+ (
+ layout_url.to_owned(),
+ Layout::stringified_from_url(layout_url)
+ .map_err(|e| format!("Failed to load layout: {}", e))?,
+ None,
+ )
+ } else {
Layout::stringified_from_path_or_default(Some(&layout_path), layout_dir)
- .map_err(|e| format!("Failed to load layout: {}", e))?;
+ .map_err(|e| format!("Failed to load layout: {}", e))?
+ };
let layout = Layout::from_str(&raw_layout, path_to_raw_layout, swap_layouts.as_ref().map(|(f, p)| (f.as_str(), p.as_str())), cwd).map_err(|e| {
let stringified_error = match e {
ConfigError::KdlError(kdl_error) => {
diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs
index cf6620a9..56c6f338 100644
--- a/zellij-utils/src/input/config.rs
+++ b/zellij-utils/src/input/config.rs
@@ -96,6 +96,8 @@ pub enum ConfigError {
PluginsError(#[from] PluginsConfigError),
#[error("{0}")]
ConversionError(#[from] ConversionError),
+ #[error("{0}")]
+ DownloadError(String),
}
impl ConfigError {
diff --git a/zellij-utils/src/input/layout.rs b/zellij-utils/src/input/layout.rs
index 05541026..94122d9f 100644
--- a/zellij-utils/src/input/layout.rs
+++ b/zellij-utils/src/input/layout.rs
@@ -8,6 +8,8 @@
// place.
// If plugins should be able to depend on the layout system
// then [`zellij-utils`] could be a proper place.
+#[cfg(not(target_family = "wasm"))]
+use crate::downloader::Downloader;
use crate::{
data::{Direction, LayoutInfo},
home::{default_layout_dir, find_default_config_dir},
@@ -18,6 +20,8 @@ use crate::{
pane_size::{Constraint, Dimension, PaneGeom},
setup::{self},
};
+#[cfg(not(target_family = "wasm"))]
+use async_std::task;
use std::cmp::Ordering;
use std::fmt::{Display, Formatter};
@@ -1145,6 +1149,7 @@ impl Layout {
LayoutInfo::BuiltIn(layout_name) => {
Self::stringified_from_default_assets(&PathBuf::from(layout_name))?
},
+ LayoutInfo::Url(url) => (url.clone(), Self::stringified_from_url(&url)?, None),
};
Layout::from_kdl(
&raw_layout,
@@ -1179,6 +1184,20 @@ impl Layout {
),
}
}
+ pub fn stringified_from_url(url: &str) -> Result<String, ConfigError> {
+ #[cfg(not(target_family = "wasm"))]
+ let raw_layout = task::block_on(async move {
+ let download = Downloader::download_without_cache(url).await;
+ match download {
+ Ok(stringified) => Ok(stringified),
+ Err(e) => Err(ConfigError::DownloadError(format!("{}", e))),
+ }
+ })?;
+ // silently fail - this should not happen in plugins and legacy architecture is hard
+ #[cfg(target_family = "wasm")]
+ let raw_layout = String::new();
+ Ok(raw_layout)
+ }
pub fn from_path_or_default(
layout_path: Option<&PathBuf>,
layout_dir: Option<PathBuf>,
@@ -1197,6 +1216,25 @@ impl Layout {
let config = Config::from_kdl(&raw_layout, Some(config))?; // this merges the two config, with
Ok((layout, config))
}
+ #[cfg(not(target_family = "wasm"))]
+ pub fn from_url(url: &str, config: Config) -> Result<(Layout, Config), ConfigError> {
+ let raw_layout = task::block_on(async move {
+ let download = Downloader::download_without_cache(url).await;
+ match download {
+ Ok(stringified) => Ok(stringified),
+ Err(e) => Err(ConfigError::DownloadError(format!("{}", e))),
+ }
+ })?;
+ let layout = Layout::from_kdl(&raw_layout, url.into(), None, None)?;
+ let config = Config::from_kdl(&raw_layout, Some(config))?; // this merges the two config, with
+ Ok((layout, config))
+ }
+ #[cfg(target_family = "wasm")]
+ pub fn from_url(url: &str, config: Config) -> Result<(Layout, Config), ConfigError> {
+ Err(ConfigError::DownloadError(format!(
+ "Unsupported platform, cannot download layout from the web"
+ )))
+ }
pub fn from_path_or_default_without_config(
layout_path: Option<&PathBuf>,
layout_dir: Option<PathBuf>,
diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs
index 1d0da5af..59c6705d 100644
--- a/zellij-utils/src/kdl/mod.rs
+++ b/zellij-utils/src/kdl/mod.rs
@@ -2166,6 +2166,7 @@ impl SessionInfo {
let (layout_name, layout_source) = match layout_info {
LayoutInfo::File(name) => (name.clone(), "file"),
LayoutInfo::BuiltIn(name) => (name.clone(), "built-in"),
+ LayoutInfo::Url(url) => (url.clone(), "url"),
};
let mut layout_node = KdlNode::new(format!("{}", layout_name));
let layout_source = KdlEntry::new_prop("source", layout_source);
diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs
index 04380cff..f4df31f2 100644
--- a/zellij-utils/src/plugin_api/event.rs
+++ b/zellij-utils/src/plugin_api/event.rs
@@ -545,6 +545,10 @@ impl TryFrom<LayoutInfo> for ProtobufLayoutInfo {
source: "built-in".to_owned(),
name,
}),
+ LayoutInfo::Url(name) => Ok(ProtobufLayoutInfo {
+ source: "url".to_owned(),
+ name,
+ }),
}
}
}
@@ -555,6 +559,7 @@ impl TryFrom<ProtobufLayoutInfo> for LayoutInfo {
match protobuf_layout_info.source.as_str() {
"file" => Ok(LayoutInfo::File(protobuf_layout_info.name)),
"built-in" => Ok(LayoutInfo::BuiltIn(protobuf_layout_info.name)),
+ "url" => Ok(LayoutInfo::Url(protobuf_layout_info.name)),
_ => Err("Unknown source for layout"),
}
}
diff --git a/zellij-utils/src/setup.rs b/zellij-utils/src/setup.rs
index e41a2061..780530d7 100644
--- a/zellij-utils/src/setup.rs
+++ b/zellij-utils/src/setup.rs
@@ -671,9 +671,23 @@ impl Setup {
.and_then(|cli_options| cli_options.default_layout.clone())
})
.or_else(|| config.options.default_layout.clone());
- // we merge-override the config here because the layout might contain configuration
- // that needs to take precedence
- Layout::from_path_or_default(chosen_layout.as_ref(), layout_dir.clone(), config)
+ if let Some(layout_url) = chosen_layout
+ .as_ref()
+ .and_then(|l| l.to_str())
+ .and_then(|l| {
+ if l.starts_with("http://") || l.starts_with("https://") {
+ Some(l)
+ } else {
+ None
+ }
+ })
+ {
+ Layout::from_url(layout_url, config)
+ } else {
+ // we merge-override the config here because the layout might contain configuration
+ // that needs to take precedence
+ Layout::from_path_or_default(chosen_layout.as_ref(), layout_dir.clone(), config)
+ }
}
fn handle_setup_commands(cli_args: &CliArgs) {
if let Some(Command::Setup(ref setup)) = &cli_args.command {