Compare commits
2 Commits
37a7bab30f
...
28ed8240f9
| Author | SHA1 | Date | |
|---|---|---|---|
| 28ed8240f9 | |||
| 276bc67dbe |
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2330,6 +2330,7 @@ dependencies = [
|
|||||||
"dioxus-code",
|
"dioxus-code",
|
||||||
"dioxus-code-editor",
|
"dioxus-code-editor",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
"rfd",
|
||||||
"ron",
|
"ron",
|
||||||
"serde",
|
"serde",
|
||||||
"strum",
|
"strum",
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ dioxus = { version = "0.7.1", features = ["router"] }
|
|||||||
dioxus-code = "0.1.2"
|
dioxus-code = "0.1.2"
|
||||||
dioxus-code-editor = { version = "0.1.2", features = ["all-languages"] }
|
dioxus-code-editor = { version = "0.1.2", features = ["all-languages"] }
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
|
# Dieselbe Konfiguration, die dioxus-desktop ohnehin zieht. Mit den
|
||||||
|
# Default-Features kaemen neun Wayland-Pakete dazu, die xdg-portal nicht
|
||||||
|
# braucht — der Dialog laeuft unter Linux ueber das Portal.
|
||||||
|
rfd = { version = "0.17.2", default-features = false, features = ["xdg-portal"] }
|
||||||
ron = "0.12.2"
|
ron = "0.12.2"
|
||||||
serde = { version = "1.0.229", features = ["derive"] }
|
serde = { version = "1.0.229", features = ["derive"] }
|
||||||
strum = { version = "0.28.0", features = ["derive"] }
|
strum = { version = "0.28.0", features = ["derive"] }
|
||||||
|
|||||||
@@ -167,6 +167,56 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Dialog --------------------------------------------------------------------
|
||||||
|
Für Unterbrechungen einer laufenden Aktion. Zustände gehören dagegen inline
|
||||||
|
an ihren Ort, nicht in ein zweites Fenster.
|
||||||
|
-------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.dialog-scrim {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: var(--dn-space-4);
|
||||||
|
background: rgb(0 0 0 / 0.55);
|
||||||
|
z-index: 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
width: min(28rem, 100%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--dn-space-4);
|
||||||
|
padding: var(--dn-space-5);
|
||||||
|
border: 1px solid var(--dn-border-strong);
|
||||||
|
border-radius: var(--dn-radius-lg);
|
||||||
|
background: var(--dn-bg-sunken);
|
||||||
|
box-shadow: var(--dn-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog h2 {
|
||||||
|
font-size: var(--dn-text-md);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog p {
|
||||||
|
color: var(--dn-fg-muted);
|
||||||
|
line-height: var(--dn-leading-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog code {
|
||||||
|
font-family: var(--dn-font-code);
|
||||||
|
font-size: var(--dn-text-sm);
|
||||||
|
color: var(--dn-fg);
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--dn-space-2);
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Utilities ---------------------------------------------------------------- */
|
/* --- Utilities ---------------------------------------------------------------- */
|
||||||
|
|
||||||
.visually-hidden {
|
.visually-hidden {
|
||||||
|
|||||||
@@ -1,24 +1,126 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::route::Route;
|
||||||
|
use crate::workspace::{dir_is_empty, Workspace};
|
||||||
|
use crate::workspace_list::WorkspaceList;
|
||||||
|
|
||||||
/// Der Bildschirm vor dem Editor: einen bekannten Workspace öffnen oder einen
|
/// Der Bildschirm vor dem Editor: einen bekannten Workspace öffnen oder einen
|
||||||
/// hinzufügen. Eine einzige Route, keine Reiter — die Workspace-Liste führt,
|
/// hinzufügen. Eine einzige Route, keine Reiter — die Workspace-Liste führt,
|
||||||
/// das Hinzufügen ist Nebenaktion.
|
/// das Hinzufügen ist Nebenaktion.
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Selector() -> Element {
|
pub fn Selector() -> Element {
|
||||||
|
let mut workspace = use_context::<Signal<Option<Workspace>>>();
|
||||||
|
let mut list = use_signal(WorkspaceList::load);
|
||||||
|
let navigator = use_navigator();
|
||||||
|
|
||||||
|
// Ordner, der auf die Bestätigung „ist nicht leer" wartet.
|
||||||
|
let mut pending = use_signal(|| None::<PathBuf>);
|
||||||
|
let mut error = use_signal(|| None::<String>);
|
||||||
|
|
||||||
|
// Übernimmt einen Workspace: Liste voranstellen und sichern, in den
|
||||||
|
// Context legen, in den Editor wechseln.
|
||||||
|
let mut accept = move |opened: Workspace| {
|
||||||
|
list.write().promote(opened.root.clone());
|
||||||
|
if let Err(err) = list.read().save() {
|
||||||
|
// Kein Abbruch: Der Workspace ist offen, nur das Merken schlug fehl.
|
||||||
|
error.set(Some(format!("Der Workspace ist offen, aber die Liste ließ sich nicht speichern: {err}")));
|
||||||
|
}
|
||||||
|
workspace.set(Some(opened));
|
||||||
|
navigator.push(Route::Home {});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wird sowohl nach dem Ordner-Dialog als auch nach der Rückfrage gebraucht.
|
||||||
|
let mut create_at = move |root: PathBuf| match Workspace::create(root) {
|
||||||
|
Ok(opened) => accept(opened),
|
||||||
|
Err(err) => error.set(Some(format!("Anlegen fehlgeschlagen: {err}"))),
|
||||||
|
};
|
||||||
|
|
||||||
|
let choose = move |_| async move {
|
||||||
|
// Abbruch liefert None und ist kein Fehler — keine Meldung.
|
||||||
|
let Some(handle) = rfd::AsyncFileDialog::new().pick_folder().await else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let root = handle.path().to_path_buf();
|
||||||
|
error.set(None);
|
||||||
|
|
||||||
|
// Ein bereits vorhandener Workspace wird geöffnet, nicht abgelehnt:
|
||||||
|
// derselbe Knopf nimmt neue und wiedergefundene Workspaces auf.
|
||||||
|
if Workspace::has_marker(&root) {
|
||||||
|
match Workspace::open(root) {
|
||||||
|
Ok(opened) => accept(opened),
|
||||||
|
// Die Display-Impl benennt den Zustand bereits: „beschädigt",
|
||||||
|
// „kein Workspace mehr", „nicht erreichbar".
|
||||||
|
Err(err) => error.set(Some(format!("Öffnen fehlgeschlagen: {err}"))),
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match dir_is_empty(&root) {
|
||||||
|
Ok(true) => create_at(root),
|
||||||
|
// Nicht leer ist der Normalfall beim Import — deshalb eine
|
||||||
|
// Rückfrage, keine Ablehnung.
|
||||||
|
Ok(false) => pending.set(Some(root)),
|
||||||
|
Err(err) => error.set(Some(format!("Ordner nicht lesbar: {err}"))),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div { class: "workspace-selector-layout",
|
div { class: "workspace-selector-layout",
|
||||||
div { class: "workspace-selector-view",
|
div { class: "workspace-selector-view",
|
||||||
// TODO: Workspace-Liste aus workspaces.ron laden und darstellen
|
// TODO: Workspace-Liste darstellen (Ordnername, Pfad, Statusabzeichen)
|
||||||
// (Ordnername, Pfad darunter, Statusabzeichen). Bis dahin nur
|
// statt des leeren Zustands, sobald Einträge vorhanden sind.
|
||||||
// der leere Zustand.
|
|
||||||
div { class: "empty-state",
|
div { class: "empty-state",
|
||||||
span { class: "empty-state-title", "Noch kein Workspace" }
|
span { class: "empty-state-title", "Noch kein Workspace" }
|
||||||
span { "Der erste Workspace ist ein Ordner deiner Wahl." }
|
span { "Der erste Workspace ist ein Ordner deiner Wahl." }
|
||||||
// TODO: Nativen Ordner-Dialog anbinden, Marker anlegen und
|
button { class: "btn btn-primary", onclick: choose,
|
||||||
// den Workspace in den Context schreiben.
|
|
||||||
button { class: "btn btn-primary", disabled: true,
|
|
||||||
"Workspace hinzufügen…"
|
"Workspace hinzufügen…"
|
||||||
}
|
}
|
||||||
|
if let Some(message) = error() {
|
||||||
|
span { class: "field-error", "{message}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(root) = pending() {
|
||||||
|
NotEmptyDialog {
|
||||||
|
root: root.clone(),
|
||||||
|
onconfirm: move |root| {
|
||||||
|
pending.set(None);
|
||||||
|
create_at(root);
|
||||||
|
},
|
||||||
|
oncancel: move |_| pending.set(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rückfrage vor dem Anlegen in einem Ordner, der bereits Dateien enthält.
|
||||||
|
/// Sie soll informieren, nicht beunruhigen — deshalb steht ausdrücklich da,
|
||||||
|
/// dass nichts Vorhandenes verändert wird.
|
||||||
|
#[component]
|
||||||
|
fn NotEmptyDialog(root: PathBuf, onconfirm: EventHandler<PathBuf>, oncancel: EventHandler<()>) -> Element {
|
||||||
|
let shown = root.display().to_string();
|
||||||
|
let confirmed = root.clone();
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
div { class: "dialog-scrim",
|
||||||
|
div { class: "dialog",
|
||||||
|
h2 { "Ordner ist nicht leer" }
|
||||||
|
p {
|
||||||
|
code { "{shown}" }
|
||||||
|
" enthält bereits Dateien. DioNotes legt darin nur den Ordner "
|
||||||
|
code { ".dionotes" }
|
||||||
|
" an und verändert nichts Vorhandenes."
|
||||||
|
}
|
||||||
|
div { class: "dialog-actions",
|
||||||
|
button { class: "btn btn-ghost", onclick: move |_| oncancel.call(()), "Abbrechen" }
|
||||||
|
button {
|
||||||
|
class: "btn btn-primary",
|
||||||
|
onclick: move |_| onconfirm.call(confirmed.clone()),
|
||||||
|
"Workspace anlegen"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
192
src/workspace.rs
192
src/workspace.rs
@@ -1,4 +1,26 @@
|
|||||||
use std::path::PathBuf;
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt;
|
||||||
|
use std::fs;
|
||||||
|
use std::io;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
const MARKER_DIR: &str = ".dionotes";
|
||||||
|
const MARKER_FILE: &str = "workspace.ron";
|
||||||
|
|
||||||
|
/// Aktuelle Fassung des Marker-Schemas. Erhöhen, sobald Felder dazukommen,
|
||||||
|
/// die eine ältere App nicht bloß ignorieren kann.
|
||||||
|
const SCHEMA_VERSION: u32 = 1;
|
||||||
|
|
||||||
|
/// Inhalt von `.dionotes/workspace.ron`.
|
||||||
|
///
|
||||||
|
/// Heute genau ein Feld. Kein `created_at` — das Erstellungsdatum kennt das
|
||||||
|
/// Dateisystem, es hier zu duplizieren schafft eine zweite Wahrheit, die beim
|
||||||
|
/// Kopieren des Ordners auseinanderläuft. Weitere Felder entstehen mit dem
|
||||||
|
/// Feature, das sie braucht; `#[serde(default)]` macht das bruchfrei.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
struct Marker {
|
||||||
|
schema_version: u32,
|
||||||
|
}
|
||||||
|
|
||||||
/// Ein geöffneter Workspace — der Ordner auf der Platte, in dem die Notizen liegen.
|
/// Ein geöffneter Workspace — der Ordner auf der Platte, in dem die Notizen liegen.
|
||||||
///
|
///
|
||||||
@@ -10,6 +32,50 @@ pub struct Workspace {
|
|||||||
pub root: PathBuf,
|
pub root: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Warum ein Ordner sich nicht als Workspace öffnen ließ.
|
||||||
|
///
|
||||||
|
/// Die ersten drei Varianten sind genau die Zustände, mit denen ein
|
||||||
|
/// Listeneintrag markiert wird — sie führen zu verschiedenen Handlungen:
|
||||||
|
/// Laufwerk einhängen, Ordner wiederherstellen, Datei reparieren.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum OpenError {
|
||||||
|
/// Der Ordner existiert nicht. Meist ein abgezogener Stick oder ein nicht
|
||||||
|
/// eingehängtes Netzlaufwerk — deshalb wird der Eintrag nie automatisch
|
||||||
|
/// aus der Liste entfernt.
|
||||||
|
Unreachable,
|
||||||
|
/// Der Ordner ist da, trägt aber keinen Marker.
|
||||||
|
NotAWorkspace,
|
||||||
|
/// Marker vorhanden, aber nicht lesbar. Der Workspace wird **nicht**
|
||||||
|
/// geöffnet und der Marker **nicht** neu angelegt: Sobald dort ein
|
||||||
|
/// Passwort-Hash liegt, wäre stillschweigendes Neuanlegen ein Weg, den
|
||||||
|
/// Schutz auszuhebeln.
|
||||||
|
Damaged,
|
||||||
|
/// Der Marker stammt aus einer neueren Fassung der App.
|
||||||
|
TooNew { found: u32 },
|
||||||
|
Io(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for OpenError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Unreachable => write!(f, "nicht erreichbar"),
|
||||||
|
Self::NotAWorkspace => write!(f, "kein Workspace mehr"),
|
||||||
|
Self::Damaged => write!(f, "beschädigt"),
|
||||||
|
Self::TooNew { found } => write!(
|
||||||
|
f,
|
||||||
|
"mit einer neueren Version angelegt (Schema {found}, unterstützt wird {SCHEMA_VERSION})"
|
||||||
|
),
|
||||||
|
Self::Io(err) => write!(f, "{err}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for OpenError {
|
||||||
|
fn from(err: io::Error) -> Self {
|
||||||
|
Self::Io(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Workspace {
|
impl Workspace {
|
||||||
/// Anzeigename: der Ordnername. Bewusst abgeleitet statt gespeichert —
|
/// Anzeigename: der Ordnername. Bewusst abgeleitet statt gespeichert —
|
||||||
/// ein eigenes Namensfeld wäre nach dem ersten Umbenennen des Ordners falsch.
|
/// ein eigenes Namensfeld wäre nach dem ersten Umbenennen des Ordners falsch.
|
||||||
@@ -19,16 +85,138 @@ impl Workspace {
|
|||||||
.map(|n| n.to_string_lossy().into_owned())
|
.map(|n| n.to_string_lossy().into_owned())
|
||||||
.unwrap_or_else(|| self.root.to_string_lossy().into_owned())
|
.unwrap_or_else(|| self.root.to_string_lossy().into_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn marker_path(root: &Path) -> PathBuf {
|
||||||
|
root.join(MARKER_DIR).join(MARKER_FILE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ob dieser Ordner einen Marker trägt — die Frage „ist das hier überhaupt
|
||||||
|
/// ein Workspace?". Sagt nichts darüber, ob der Marker lesbar ist.
|
||||||
|
pub fn has_marker(root: &Path) -> bool {
|
||||||
|
Self::marker_path(root).is_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Einen bestehenden Workspace öffnen.
|
||||||
|
pub fn open(root: PathBuf) -> Result<Self, OpenError> {
|
||||||
|
if !root.is_dir() {
|
||||||
|
return Err(OpenError::Unreachable);
|
||||||
|
}
|
||||||
|
let marker_path = Self::marker_path(&root);
|
||||||
|
if !marker_path.is_file() {
|
||||||
|
return Err(OpenError::NotAWorkspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = fs::read_to_string(&marker_path).map_err(|_| OpenError::Damaged)?;
|
||||||
|
let marker: Marker = ron::from_str(&text).map_err(|_| OpenError::Damaged)?;
|
||||||
|
if marker.schema_version > SCHEMA_VERSION {
|
||||||
|
return Err(OpenError::TooNew { found: marker.schema_version });
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { root })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Einen Ordner zum Workspace machen: `.dionotes/workspace.ron` anlegen.
|
||||||
|
///
|
||||||
|
/// Vorhandene Dateien im Ordner bleiben unberührt — der Marker ist additiv.
|
||||||
|
pub fn create(root: PathBuf) -> Result<Self, OpenError> {
|
||||||
|
if !root.is_dir() {
|
||||||
|
return Err(OpenError::Unreachable);
|
||||||
|
}
|
||||||
|
let marker_path = Self::marker_path(&root);
|
||||||
|
if let Some(dir) = marker_path.parent() {
|
||||||
|
fs::create_dir_all(dir)?;
|
||||||
|
}
|
||||||
|
let marker = Marker { schema_version: SCHEMA_VERSION };
|
||||||
|
let text = ron::ser::to_string_pretty(&marker, ron::ser::PrettyConfig::default())
|
||||||
|
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
|
||||||
|
fs::write(&marker_path, text)?;
|
||||||
|
Ok(Self { root })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ob ein Ordner leer ist — Grundlage für die Rückfrage vor dem Anlegen.
|
||||||
|
pub fn dir_is_empty(path: &Path) -> io::Result<bool> {
|
||||||
|
Ok(fs::read_dir(path)?.next().is_none())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Workspace;
|
use super::{dir_is_empty, OpenError, Workspace};
|
||||||
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Frisches Testverzeichnis. Der Testname macht es eindeutig, deshalb
|
||||||
|
/// braucht es keine Zufallszahl und keine tempfile-Crate.
|
||||||
|
fn scratch(name: &str) -> PathBuf {
|
||||||
|
let dir = std::env::temp_dir().join(format!("dionotes-test-{name}"));
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
fs::create_dir_all(&dir).expect("Testverzeichnis anlegbar");
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn name_is_the_folder_name() {
|
fn name_is_the_folder_name() {
|
||||||
let ws = Workspace { root: PathBuf::from("C:/Users/marvin/Notizen") };
|
let ws = Workspace { root: PathBuf::from("C:/Users/marvin/Notizen") };
|
||||||
assert_eq!(ws.name(), "Notizen");
|
assert_eq!(ws.name(), "Notizen");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_then_open_roundtrip() {
|
||||||
|
let dir = scratch("create-open");
|
||||||
|
let created = Workspace::create(dir.clone()).expect("anlegbar");
|
||||||
|
assert!(Workspace::has_marker(&dir));
|
||||||
|
assert_eq!(Workspace::open(dir.clone()).expect("oeffenbar"), created);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn create_leaves_existing_files_alone() {
|
||||||
|
let dir = scratch("create-keeps-files");
|
||||||
|
fs::write(dir.join("notiz.md"), "# Hallo").unwrap();
|
||||||
|
Workspace::create(dir.clone()).expect("anlegbar");
|
||||||
|
assert_eq!(fs::read_to_string(dir.join("notiz.md")).unwrap(), "# Hallo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_folder_is_unreachable() {
|
||||||
|
let dir = scratch("gone").join("weg");
|
||||||
|
assert!(matches!(Workspace::open(dir), Err(OpenError::Unreachable)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn folder_without_marker_is_not_a_workspace() {
|
||||||
|
let dir = scratch("no-marker");
|
||||||
|
assert!(matches!(Workspace::open(dir), Err(OpenError::NotAWorkspace)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unreadable_marker_is_damaged_and_not_recreated() {
|
||||||
|
let dir = scratch("damaged");
|
||||||
|
Workspace::create(dir.clone()).expect("anlegbar");
|
||||||
|
let marker = dir.join(".dionotes").join("workspace.ron");
|
||||||
|
fs::write(&marker, "kaputt").unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(Workspace::open(dir), Err(OpenError::Damaged)));
|
||||||
|
// Der Marker wurde nicht stillschweigend ersetzt.
|
||||||
|
assert_eq!(fs::read_to_string(&marker).unwrap(), "kaputt");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn newer_schema_is_refused_with_the_version() {
|
||||||
|
let dir = scratch("too-new");
|
||||||
|
Workspace::create(dir.clone()).expect("anlegbar");
|
||||||
|
fs::write(dir.join(".dionotes").join("workspace.ron"), "(schema_version: 99)").unwrap();
|
||||||
|
|
||||||
|
match Workspace::open(dir) {
|
||||||
|
Err(OpenError::TooNew { found }) => assert_eq!(found, 99),
|
||||||
|
other => panic!("erwartet TooNew, war {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_folder_is_recognised() {
|
||||||
|
let dir = scratch("emptiness");
|
||||||
|
assert!(dir_is_empty(&dir).unwrap());
|
||||||
|
fs::write(dir.join("etwas.txt"), "x").unwrap();
|
||||||
|
assert!(!dir_is_empty(&dir).unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user