Expand description
litter
makes your literals mutable with smart pointers into your source code.
These can be used for snapshot testing, or as a basic way of inlining state into
scripts. This is only intended for use in code thatâs being run through Cargo,
as it relies on CARGO_
environment variables to locate the source code when
mutations need to be written back. The implementation uses the
#[track_caller]
attribute (no macros).
To Do
- implement naive unsyncronized writing
- implement better writing
- support external data(?)
- Future work?! Almost nothing described above has actually been implemented yet!
- Fallible alternatives instead of panicking, including for the case of writing values when the source files donât exist (they can still be saved in-memory).
- External data, not just inline.
- While the value has only been read, we can hold on to a Weak Arc of its state and let it be freed. Once itâs been written to, we need to keep it in memory forever, so we leak a reference.
Basic Use
The litter::Litter
type wraps a literal value with information about its
location in your source code, allowing it to be mutated with changes reflected
in the original script file. Literal types supported are integers (1
, 2
,
2_usize
, -1i16
), floats (1.5
, 2e6f64
), booleans (true
, false
),
static strings ("hello"
, r##"world##"
), static byte strings
(b"one two \x12"
, br"hell\x00"
). These are described by the
litter::Literal
trait.
.edit()
ing a Literal
or a Litter
produces a LitterHandle
. It implements
Deref
and DerefMut
, exposing the inner value, as well as various other
traits. If the inner value is modified, it will be written back to the file when
the Litter
is dropped.
Hereâs a basic example, of a string thatâs modified each time the script runs:
use litter::LiteralExt;
fn main() {
let mut p = "and I say hello!".edit();
*p += " hello!";
}
let mut p = "and I say hello! hello!".edit();
*p += " hello!";
let mut p = "and I say hello! hello! hello!".edit();
*p += " hello!";
Composition
The Litter
constructors work using #[track_caller]
, not macros, so itâs
possible to wrap them to create your own functions that modifying literals, with
one important caveat: the literal in question must be the first literal that
occurs as an argument in the function call. So f(x, "literal")
works, but
f(2, "literal")
would not. For this reason we usually prefer to take the
literal as the first argument to the function.
For example, we can use this to implement snapshot testing in the style of
expect_test
.
#[test]
fn test_the_ultimate_question() {
assert_eq_u64(42, 6 * 9);
}
#[track_caller] // <- in order to look for literal at this function's call site instead
fn assert_eq_u64(expected: u64, actual: u64) {
let expected = litter::new(expected);
if expected != actual {
if std::env::get("UPDATE_EXPECT").unwrap_or("0") != "0" {
*expected = actual; // <- updated in memory, written to source at end of scope
} else {
panic!("\
Expected {expected:?} but actual value was {actual:?}.\n\
\n\
To update the expected value, run this again with UPDATE_EXPECT=1.\
");
}
}
}
Running this test will initially fail with our panic message, but running it
again with UPDATE_EXPECT=1 cargo test
will pass and update our source code to
reflect the correct value:
#[test]
fn test_the_ultimate_question() {
assert_eq_u64(54, 6 * 9);
}
Although you donât need to write this particular function yourself: a generic
version is included at litter::assert_eq(literal, actual)
.
Serialization
If you enable the json
, yaml
, postcard
, or toml
features, LiteralExt
for strings and bytes gains corresponding .edit_json()
, .edit_yaml()
,
.edit_toml()
, or .edit_postcard()
methods that can be used to be used to
inline non-primitive values that implement serde::{ Serialize, Deserialize }
,
as well as Debug
, Clone
, and Default
. (If your type doesnât implement
Default
, you may consider wrapping it in an Option<...>
.)
As a special case for convenience, if the literal string is empty but
deserialization fails, the typeâs Default::default()
value will be returned.
Any other (de)serialization errors will cause the program to panic.
fn main() {
// empty string interpreted as default, like "[]"
let json_vec = "".edit_json::<Vec<usize>>();
json_vec.push(json_vec.len());
// empty byte string interpreted as default, like b"\x00"
let postcard_vec = b"".edit_postcard::<Vec<usize>>();
postcard_vec.push(postcard_vec.len());
}
let json_vec = "[0]".edit_json::<Vec<usize>>();
json_vec.push(json_vec.len());
let postcard_vec = b"\x01\x00".edit_postcard::<Vec<usize>>();
postcard_vec.postcard_vec(postcard_vec.len());
let json_vec = "[0, 1]".edit_json::<Vec<usize>>();
json_vec.push(json_vec.len());
let postcard_vec = b"\x02\x00\x01".edit_postcard::<Vec<usize>>();
postcard_vec.push(postcard_vec.len());
If no type is specified or can be inferred, the .edit_json()
, .edit_yaml()
,
and .edit_toml()
methods default to dynamic values (serde_json::Value
,
serde_yaml::Value
, and toml::Value
). However .edit_postcard()
always
requires a known type (itâs non-self-describing and canât be handled
dynamically).
For text formats, encoding to a string literal will be pretty/verbose, while
encoding to a byte string literal will be use a compact representation. (This
obviously isnât relevant to binary-only formats like postcard
and rkyv
.)
Performance and Reliability
This is (clearly) intended for convenience, not performance. It should be fast
enough for use in test snapshotting or dumping some state for a script, but you
certainly wouldnât want to use it for a high-throughput web server. Access to
each literal is controlled by an RwLock
which may block if used concurrently.
The filesystem is only accessed when a mutated value needs to be written back, so if a value is never then modified the filesystem wonât be accessed, and in that case the program can work fine on a different system without the source files available.
Logic errors can occur if multiple copies of your program are running concurrently and both try to modify the same file.
License
litter
is Copyright Jeremy Banks, released under the familiar choice of
MIT OR Apache-2.0
.
litter
copies heavily from the
the expect-test
library, which is also under
MIT OR Apache-2.0
and is Copyright the rust-analyzer developers, including
Aleksey Kladov and Dylan MacKenzie.
Crate Feature Flags
Serialization Formats
-
json
â Support (de)serializing string/byte literals as JSON usingserde
via the.edit_json()
methods. -
postcard
â Support (de)serializing byte literals as Postcard usingserde
via the.edit_postcard()
methods. -
toml
â Support (de)serializing string/byte literals as TOML usingserde
via the.edit_toml()
methods. -
yaml
â Support (de)serializing string/byte literals as YAML usingserde
via the.edit_yaml()
methods.
Core Capabilities
-
write
(enabled by default) â Support for writing literal mutations back to their source file. -
mut
(enabled by default) â Support for âmutatingâ literals. -
panic
(enabled by default) â Include features that may panic on error. (Disabling does not prevent all panics (yet?).)
Testing Tools
assertions
(enabled by default) â Include assertion functions for snapshot testing.
Forbidding Features
These flags are obviously ânon-additiveâ (contra the normal recommendation),
so they should typically only be activated by the root binary, not libraries.
These donât disable the corresponding features directly; youâll still need to use default-features = false
.
Rather, these flags will raise a compile errors if the corresponding features are enabled,
to help catch cases where they may inadvertently be reenabled by other crates.
These flags are ignored in --all-features
builds.
-
forbid-panic
â Forbids the use of thepanic
feature. -
forbid-write
â Forbids the use of thewrite
feature. -
forbid-mut
â Forbids the use of themut
feature. -
forbid-serde
â Forbids the use of features that requireserde
.
Re-exports
pub use ::toke;
Structs
Enums
Traits
A type representing a literal value in the source code that can be edited
by litter
. Using this type with a value that is not actually a literal in
your source code may result in logic errors or panics.
Functions
assertions