Skip to content

Commit

Permalink
Move config into file
Browse files Browse the repository at this point in the history
Supporting live changes to the config is going to get increasingly complicated, and doesn't provide much value. The file is a necessary long-term solution anyway, for things like configurable theme and keybindings.

Closes #89
  • Loading branch information
LucasPickering committed Dec 21, 2023
1 parent 4ddb106 commit 7b32791
Show file tree
Hide file tree
Showing 18 changed files with 111 additions and 205 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@

## [Unreleased] - ReleaseDate

### Added

- Move app-level configuration into a file ([#89](https://github.com/LucasPickering/slumber/issues/89))
- Right now the only supported field is `preview_templates`

### Changed

- Show profile contents while in the profile list ([#26](https://github.com/LucasPickering/slumber/issues/26))
- Remove settings modal in favor of the settings file
- Supporting changing configuration values during a session adds a lot of complexity

## [0.11.0] - 2023-12-20

Expand Down
2 changes: 1 addition & 1 deletion run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# latest watchexec we can get rid of this.
# https://github.com/watchexec/cargo-watch/issues/269

RUST_LOG=${RUST_LOG:-slumber=trace} watchexec --restart \
RUST_LOG=${RUST_LOG:-slumber=trace} watchexec --restart --no-process-group \
--watch Cargo.toml --watch Cargo.lock --watch src/ \
-- cargo run \
-- $@
33 changes: 13 additions & 20 deletions src/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
mod cereal;
mod insomnia;

use crate::template::Template;
use crate::{
template::Template,
util::{parse_yaml, ResultExt},
};
use anyhow::{anyhow, Context};
use derive_more::{Deref, Display, From};
use equivalent::Equivalent;
Expand All @@ -17,7 +20,6 @@ use std::{
hash::Hash,
path::{Path, PathBuf},
};
use tokio::fs;
use tracing::{info, warn};

/// The support file names to be automatically loaded as a config. We only
Expand Down Expand Up @@ -222,25 +224,16 @@ impl RequestCollection<PathBuf> {
/// [Self::detect_path] to find the file themself. This pattern enables the
/// TUI to start up and watch the collection file, even if it's invalid.
pub async fn load(path: PathBuf) -> anyhow::Result<Self> {
// Figure out which file we want to load from
info!(?path, "Loading collection file");

// First, parse the file to raw YAML values, so we can apply
// anchor/alias merging. Then parse that to our config type
let future = async {
let content = fs::read(&path).await?;
let mut yaml_value =
serde_yaml::from_slice::<serde_yaml::Value>(&content)?;
yaml_value.apply_merge()?;
Ok::<RequestCollection, anyhow::Error>(serde_yaml::from_value(
yaml_value,
)?)
};

Ok(future
.await
.context(format!("Error loading collection from {path:?}"))?
.with_source(path))
// This async block is really just a try block
let collection: RequestCollection = async {
let bytes = tokio::fs::read(&path).await?;
parse_yaml(&bytes).map_err(anyhow::Error::from)
}
.await
.context(format!("Error loading data from {path:?}"))
.traced()?;
Ok(collection.with_source(path))
}

/// Reload a new collection from the same file used for this one.
Expand Down
51 changes: 51 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use crate::util::{parse_yaml, Directory, ResultExt};
use anyhow::Context;
use serde::Deserialize;
use std::fs;
use tracing::info;

/// TODO
#[derive(Debug, Deserialize)]
#[serde(default)]
pub struct Config {
/// Should templates be rendered inline in the UI, or should we show the
/// raw text?
pub preview_templates: bool,
}

impl Config {
const FILE: &'static str = "config.yml";

/// Load configuration from the file, if present. If not, just return a
/// default value. This only returns an error if the file could be read, but
/// deserialization failed. This is *not* async because it's only run during
/// startup, when all operations are synchronous.
pub fn load() -> anyhow::Result<Self> {
let path = Directory::root().create()?.join(Self::FILE);
info!(?path, "Loading configuration file");

match fs::read(&path) {
Ok(bytes) => parse_yaml::<Self>(&bytes)
.context(format!("Error loading configuration from {path:?}"))
.traced(),
// An error here is probably just the file missing, so don't make
// a big stink about it
Err(error) => {
info!(
?path,
error = &error as &dyn std::error::Error,
"Error reading configuration file"
);
Ok(Self::default())
}
}
}
}

impl Default for Config {
fn default() -> Self {
Self {
preview_templates: true,
}
}
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

mod cli;
mod collection;
mod config;
mod db;
#[cfg(test)]
mod factory;
Expand Down
4 changes: 3 additions & 1 deletion src/tui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod view;

use crate::{
collection::{ProfileId, RequestCollection, RequestRecipeId},
config::Config,
db::{CollectionDatabase, Database},
http::{HttpEngine, RequestBuilder},
template::{Prompter, Template, TemplateChunk, TemplateContext},
Expand Down Expand Up @@ -78,13 +79,14 @@ impl Tui {
// ===== Initialize global state =====
// This stuff only needs to be set up *once per session*

let config = Config::load()?;
// Create a message queue for handling async tasks
let (messages_tx, messages_rx) = mpsc::unbounded_channel();
let messages_tx = MessageSender::new(messages_tx);
// Load a database for this particular collection
let database = Database::load()?.into_collection(&collection_file)?;
// Initialize global view context
TuiContext::init(messages_tx.clone(), database.clone());
TuiContext::init(config, messages_tx.clone(), database.clone());

// ===== Initialize collection & view =====

Expand Down
11 changes: 10 additions & 1 deletion src/tui/context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{
config::Config,
db::CollectionDatabase,
tui::{
input::InputEngine,
Expand Down Expand Up @@ -28,6 +29,8 @@ static CONTEXT: OnceLock<TuiContext> = OnceLock::new();
/// Both are safe to access in statics!
#[derive(Debug)]
pub struct TuiContext {
/// TODO
pub config: Config,
/// Visual theme. Colors!
pub theme: Theme,
/// Input:action bindings
Expand All @@ -41,9 +44,14 @@ pub struct TuiContext {

impl TuiContext {
/// Initialize global context. Should be called only once, during startup.
pub fn init(messages_tx: MessageSender, database: CollectionDatabase) {
pub fn init(
config: Config,
messages_tx: MessageSender,
database: CollectionDatabase,
) {
CONTEXT
.set(Self {
config,
theme: Theme::default(),
input_engine: InputEngine::default(),
messages_tx,
Expand Down Expand Up @@ -72,6 +80,7 @@ impl TuiContext {
pub fn tui_context() {
use tokio::sync::mpsc;
TuiContext::init(
Config::default(),
MessageSender::new(mpsc::unbounded_channel().0),
CollectionDatabase::testing(),
);
Expand Down
4 changes: 0 additions & 4 deletions src/tui/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ impl InputEngine {
)
.hide(),
InputBinding::new(KeyCode::Char('x'), Action::OpenActions),
InputBinding::new(KeyCode::Char('s'), Action::OpenSettings),
InputBinding::new(KeyCode::Char('?'), Action::OpenHelp),
InputBinding::new(KeyCode::Char('f'), Action::Fullscreen),
InputBinding::new(KeyCode::Char('r'), Action::ReloadCollection),
Expand Down Expand Up @@ -177,9 +176,6 @@ pub enum Action {
/// Open the actions modal
#[display("Actions")]
OpenActions,
/// Open the settings modal
#[display("Settings")]
OpenSettings,
#[display("Help")]
/// Open the help modal
OpenHelp,
Expand Down
30 changes: 2 additions & 28 deletions src/tui/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,12 @@ use tracing::{error, trace, trace_span};
/// controller and exposed via event passing.
#[derive(Debug)]
pub struct View {
config: ViewConfig,
root: Component<Root>,
}

impl View {
pub fn new(collection: &RequestCollection) -> Self {
let mut view = Self {
config: ViewConfig::default(),
root: Root::new(collection).into(),
};
// Tell the components to wake up
Expand All @@ -53,14 +51,7 @@ impl View {
/// to render input bindings as help messages to the user.
pub fn draw<'a>(&'a self, frame: &'a mut Frame) {
let chunk = frame.size();
self.root.draw(
&mut DrawContext {
config: &self.config,
frame,
},
(),
chunk,
)
self.root.draw(&mut DrawContext { frame }, (), chunk)
}

/// Update the request state for the given profile+recipe. The state will
Expand Down Expand Up @@ -126,8 +117,7 @@ impl View {

let span = trace_span!("View event", ?event);
span.in_scope(|| {
let mut context =
UpdateContext::new(&mut event_queue, &mut self.config);
let mut context = UpdateContext::new(&mut event_queue);

let update =
Self::update_all(self.root.as_child(), &mut context, event);
Expand Down Expand Up @@ -184,19 +174,3 @@ impl View {
})
}
}

/// Settings that control the behavior of the view
#[derive(Debug)]
struct ViewConfig {
/// Should templates be rendered inline in the UI, or should we show the
/// raw text?
preview_templates: bool,
}

impl Default for ViewConfig {
fn default() -> Self {
Self {
preview_templates: true,
}
}
}
14 changes: 4 additions & 10 deletions src/tui/view/common/template_preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ use std::{
/// changed globally.
#[derive(Debug)]
pub enum TemplatePreview {
// TODO key state on preview_templates
/// Template previewing is disabled, just show the raw text
Disabled { template: Template },
/// Template previewing is enabled, render the template
Expand All @@ -37,14 +36,10 @@ pub enum TemplatePreview {

impl TemplatePreview {
/// Create a new template preview. This will spawn a background task to
/// render the template. Profile ID defines which profile to use for the
/// render.
pub fn new(
template: Template,
profile_id: Option<ProfileId>,
enabled: bool,
) -> Self {
if enabled {
/// render the template, *if* template preview is enabled. Profile ID
/// defines which profile to use for the render.
pub fn new(template: Template, profile_id: Option<ProfileId>) -> Self {
if TuiContext::get().config.preview_templates {
// Tell the controller to start rendering the preview, and it'll
// store it back here when done
let lock = Arc::new(OnceLock::new());
Expand Down Expand Up @@ -74,7 +69,6 @@ impl Generate for &TemplatePreview {
where
Self: 'this,
{
// The raw template string
match self {
TemplatePreview::Disabled { template } => template.deref().into(),
// If the preview render is ready, show it. Otherwise fall back
Expand Down
1 change: 0 additions & 1 deletion src/tui/view/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ mod recipe_list;
mod request;
mod response;
mod root;
mod settings;

pub use root::Root;

Expand Down
5 changes: 0 additions & 5 deletions src/tui/view/component/primary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use crate::{
recipe_list::{RecipeListPane, RecipeListPaneProps},
request::{RequestPane, RequestPaneProps},
response::{ResponsePane, ResponsePaneProps},
settings::SettingsModal,
},
draw::{Draw, DrawContext},
event::{Event, EventHandler, Update, UpdateContext},
Expand Down Expand Up @@ -214,10 +213,6 @@ impl EventHandler for PrimaryView {
context.open_modal_default::<EmptyActionsModal>();
Update::Consumed
}
Action::OpenSettings => {
context.open_modal_default::<SettingsModal>();
Update::Consumed
}
Action::OpenHelp => {
context.open_modal_default::<HelpModal>();
Update::Consumed
Expand Down
1 change: 0 additions & 1 deletion src/tui/view/component/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ impl<'a> Draw<ProfilePaneProps<'a>> for ProfilePane {
FieldValue::Template(TemplatePreview::new(
template.clone(),
Some(props.profile.id.clone()),
context.config.preview_templates,
))
}
};
Expand Down

0 comments on commit 7b32791

Please sign in to comment.