link-cli 0.2.0

A CLI tool for links manipulation
Documentation
use anyhow::Result;
use link_cli::sequences::{
    AddressToRawNumberConverter, BalancedVariantConverter, CachingConverterDecorator,
    CharToUnicodeSymbolConverter, RawNumberToAddressConverter, RightSequenceWalker,
    StringToUnicodeSequenceConverter, TargetMatcher, UnicodeSequenceToStringConverter,
    UnicodeSymbolToCharConverter,
};
use link_cli::{external_reference, HybridReference, LinkStorage, PinnedTypes};
use std::cell::Cell;
use tempfile::NamedTempFile;

fn with_links(test: impl FnOnce(&mut LinkStorage) -> Result<()>) -> Result<()> {
    let temp_file = NamedTempFile::new()?;
    let db_path = temp_file.path().to_str().unwrap();
    let mut links = LinkStorage::new(db_path, false)?;
    test(&mut links)
}

fn allocate_unicode_types(links: &mut LinkStorage) -> Result<(u32, u32)> {
    let mut pinned_types = PinnedTypes::new(links);
    let _type_type = pinned_types.next_type()?;
    let unicode_symbol_type = pinned_types.next_type()?;
    let unicode_sequence_type = pinned_types.next_type()?;
    Ok((unicode_symbol_type, unicode_sequence_type))
}

#[test]
fn raw_number_converters_match_hybrid_external_reference_encoding() {
    let address_to_number = AddressToRawNumberConverter::new();
    let number_to_address = RawNumberToAddressConverter::new();

    assert_eq!(u32::MAX, external_reference(1));
    assert_eq!(u32::MAX, address_to_number.convert(1));
    assert_eq!(1, number_to_address.convert(u32::MAX));

    let zero = HybridReference::external(0);
    assert!(zero.is_external());
    assert_eq!(Some(0), zero.absolute_value());
}

#[test]
fn target_and_char_symbol_converters_create_and_decode_symbols() -> Result<()> {
    with_links(|links| {
        let (unicode_symbol_type, _) = allocate_unicode_types(links)?;
        let symbol_matcher = TargetMatcher::new(unicode_symbol_type);
        let char_to_symbol = CharToUnicodeSymbolConverter::new(
            AddressToRawNumberConverter::new(),
            unicode_symbol_type,
        );
        let symbol_to_char =
            UnicodeSymbolToCharConverter::new(RawNumberToAddressConverter::new(), symbol_matcher);

        let symbol = char_to_symbol.convert(links, 'A' as u16);

        assert!(symbol_matcher.is_matched(links, symbol));
        assert_eq!('A' as u16, symbol_to_char.convert(links, symbol)?);
        Ok(())
    })
}

#[test]
fn balanced_variant_and_right_sequence_walker_preserve_symbol_order() -> Result<()> {
    with_links(|links| {
        let (unicode_symbol_type, _) = allocate_unicode_types(links)?;
        let symbol_matcher = TargetMatcher::new(unicode_symbol_type);
        let char_to_symbol = CharToUnicodeSymbolConverter::new(
            AddressToRawNumberConverter::new(),
            unicode_symbol_type,
        );
        let symbol_to_char =
            UnicodeSymbolToCharConverter::new(RawNumberToAddressConverter::new(), symbol_matcher);
        let symbols = "ABCDE"
            .encode_utf16()
            .map(|code_unit| char_to_symbol.convert(links, code_unit))
            .collect::<Vec<_>>();

        let root = BalancedVariantConverter::new().convert(links, &symbols);
        let walked = RightSequenceWalker::new(symbol_matcher).walk(links, root);
        let code_units = walked
            .into_iter()
            .map(|symbol| symbol_to_char.convert(links, symbol))
            .collect::<Result<Vec<_>>>()?;

        assert_eq!("ABCDE", String::from_utf16(&code_units)?);
        Ok(())
    })
}

#[test]
fn string_and_unicode_sequence_converters_round_trip_utf16_text() -> Result<()> {
    with_links(|links| {
        let (unicode_symbol_type, unicode_sequence_type) = allocate_unicode_types(links)?;
        let symbol_matcher = TargetMatcher::new(unicode_symbol_type);
        let sequence_matcher = TargetMatcher::new(unicode_sequence_type);
        let char_to_symbol = CharToUnicodeSymbolConverter::new(
            AddressToRawNumberConverter::new(),
            unicode_symbol_type,
        );
        let symbol_to_char =
            UnicodeSymbolToCharConverter::new(RawNumberToAddressConverter::new(), symbol_matcher);
        let string_to_sequence = StringToUnicodeSequenceConverter::new(
            char_to_symbol,
            BalancedVariantConverter::new(),
            unicode_sequence_type,
        );
        let sequence_to_string = UnicodeSequenceToStringConverter::new(
            sequence_matcher,
            RightSequenceWalker::new(symbol_matcher),
            symbol_to_char,
            unicode_sequence_type,
        );
        let input = "A😀B世界";

        let sequence = string_to_sequence.convert(links, input);

        assert!(sequence_matcher.is_matched(links, sequence));
        assert_eq!(input, sequence_to_string.convert(links, sequence)?);
        Ok(())
    })
}

#[test]
fn caching_converter_decorator_reuses_cached_values() -> Result<()> {
    let calls = Cell::new(0);
    let mut cache = CachingConverterDecorator::<String, usize>::new();

    let first = cache.convert_with("Unicode".to_string(), |input| {
        calls.set(calls.get() + 1);
        Ok::<_, anyhow::Error>(input.encode_utf16().count())
    })?;
    let second = cache.convert_with("Unicode".to_string(), |input| {
        calls.set(calls.get() + 1);
        Ok::<_, anyhow::Error>(input.len())
    })?;

    assert_eq!(7, first);
    assert_eq!(first, second);
    assert_eq!(1, calls.get());
    assert_eq!(1, cache.len());
    Ok(())
}