<commit_before>use orbtk::{
    prelude::*,
    // only instantiate the `default` theme
    widgets::themes::theme_orbtk::{
	register_default_fonts, THEME_DEFAULT, THEME_DEFAULT_COLORS_DARK, THEME_DEFAULT_FONTS,
    },
};

// German localization file.
static POPUP_DE_DE: &str = include_str!("assets/popup/popup_de_DE.ron");

static DARK_EXT: &str = include_str!("assets/popup/default_dark.ron");

static ID_BUTTON: &str = "Toggle Popup";
static ID_GRID: &str = "PopupGrid";
static ID_GRID_PLACEMENT: &str = "Selection GRID";
static ID_MAIN_VIEW: &str = "MainView";
static ID_POPUP: &str = "Popup";
static ID_POPUP_STACK: &str = "PopupStack";
static ID_PLACEMENT_COMBO_BOX: &str = "Placement Selecton Box";
static ID_PLACEMENT_OFFSET: &str = "Placement Offset";
static ID_PLACEMENT_OFFSET_LABEL: &str = "Offset";
static ID_PLACEMENT_COMBO_BOX_LABEL: &str = "Placement Label";
static ID_TARGET: &str = "Target";

/// Valid `actions` that are handled as state changes in the `MainView` widget.
#[derive(Copy, Clone)]
enum MainViewAction {
    /// Toggle the visibility of the popup
    TogglePopup,

    /// Update the offset of the popup relative to the target
    UpdateOffset,

    /// Update the placement of the popup relative to the target
    UpdatePlacement,
}

/// Valid `structures` that are handled inside the state of the `MainView` widget.
#[derive(AsAny, Default)]
struct MainViewState {
    _action: Option<MainViewAction>,
}

/// Method definitions, that react on any given state change inside the `MainView` widget.
impl MainViewState {
    fn _action(&mut self, action: impl Into<Option<MainViewAction>>) {
	self._action = action.into();
    }

    fn toggle_popup(&mut self, ctx: &mut Context<'_>) {
	let open = ctx.child(ID_POPUP).clone::<bool>("open");

	    if open {
	    Popup::open_set(&mut ctx.child(ID_POPUP), false);
	    Popup::visibility_set(&mut ctx.child(ID_POPUP), Visibility::Collapsed);
		Button::text_set(&mut ctx.child(ID_BUTTON), "Click me to show the popup");
	    } else {
	    Popup::visibility_set(&mut ctx.child(ID_POPUP), Visibility::Visible);
	    Popup::open_set(&mut ctx.child(ID_POPUP), true);
		Button::text_set(&mut ctx.child(ID_BUTTON), "Click me to hide the popup");
	    }
	if cfg!(debug) {
	    println!("popup_v2: toggled {:?}.",
		     ctx.child(ID_POPUP).get::<String>("name"));
	    println!("Current placement: {:?}",
		     ctx.child(ID_POPUP).get::<Placement>("placement")
	    );
	    println!("Current visibility: {:?}",
		     ctx.child(ID_POPUP).get::<Visibility>("visibility"));
	}
    }

    fn update_placement(&mut self, ctx: &mut Context<'_>) {
	let selected_index: i32 = ctx.child(ID_PLACEMENT_COMBO_BOX).clone("selected_index");
	let placement: Placement = ctx.child(ID_POPUP).clone_or_default("placement");
	    match selected_index {
	    0 => Popup::placement_set(&mut ctx.child(ID_POPUP),
				      placement.bottom()),
	    1 => Popup::placement_set(&mut ctx.child(ID_POPUP),
				      placement.bottom_left()),
	    2 => Popup::placement_set(&mut ctx.child(ID_POPUP),
				      placement.bottom_right()),
	    3 => Popup::placement_set(&mut ctx.child(ID_POPUP),
				      placement.left()),
	    4 => Popup::placement_set(&mut ctx.child(ID_POPUP),
				      placement.right()),
	    5 => Popup::placement_set(&mut ctx.child(ID_POPUP),
				      placement.top()),
	    6 => Popup::placement_set(&mut ctx.child(ID_POPUP),
				      placement.top_left()),
	    7 => Popup::placement_set(&mut ctx.child(ID_POPUP),
				      placement.top_right()),
		_ => panic!(),
	    }
	if cfg!(debug) {
	    println!(
		"popup_v2: Placement {:?} (index: {:?}).",
		ctx.child(ID_POPUP).get::<Placement>("placement"),
		selected_index
	    );
	}
    }

    fn update_offset(&mut self, ctx: &mut Context<'_>) {
	let offset: f64 = ctx.child(ID_PLACEMENT_OFFSET).clone("val");
	Popup::offset_set(&mut ctx.child(ID_POPUP), offset);

	//if cfg!(debug) {
	    println!(
		"popup_v2: Offset {:?}.",
		ctx.child(ID_POPUP).get::<f64>("offset")
	    );
	//}
    }
}

/// Associated methods handled inside the `MainViewState`
impl State for MainViewState {
    fn init(&mut self, _registry: &mut Registry, ctx: &mut Context) {
	// initialize the popup properties
	ctx.child(ID_POPUP).set("open", false);
	ctx.child(ID_POPUP).set("visibility", Visibility::Collapsed);
    }

    fn messages(
	&mut self,
	mut messages: MessageReader,
	_registry: &mut Registry,
	ctx: &mut Context,
    ) {
	for message in messages.read::<MainViewAction>() {
	    match message {
		MainViewAction::TogglePopup => {
		    MainViewState::toggle_popup(self, ctx);
		}
		MainViewAction::UpdatePlacement => {
		    MainViewState::update_placement(self, ctx);
		}
		MainViewAction::UpdateOffset => {
		    MainViewState::update_offset(self, ctx);
		}
	    }
	}
    }
}

fn theme() -> Theme {
    register_default_fonts(Theme::from_config(
	ThemeConfig::from(DARK_EXT)
	    .extend(ThemeConfig::from(THEME_DEFAULT))
	    .extend(ThemeConfig::from(THEME_DEFAULT_COLORS_DARK))
	    .extend(ThemeConfig::from(THEME_DEFAULT_FONTS)),
    ))
}

// Used to define ComboBox list members
type List = Vec<String>;

// constructs the MainView
widget!(
    MainView<MainViewState> {
    /// Active seleced index of combo box.
    selected_index: i32,

    /// Acitve selected position of popup.
    placements: List
    }
);

/// The template implementation of the main view
/// GUI elements are styled using the "style" attribute referencing to a ron based css
impl Template for MainView {
    fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
	let placements = vec![
	    // TODO: make it sort independent
	    "Bottom".to_string(),
	    "BottomLeft".to_string(),
	    "BottomRight".to_string(),
	    "Left".to_string(),
	    "Right".to_string(),
	    "Top".to_string(),
	    "TopLeft".to_string(),
	    "TopRight".to_string(),
	];
	let count_placements = placements.len();

	let target_container = Container::new()
	    .id(ID_TARGET)
	    .attach(Grid::row(2))
	    .attach(Grid::column(1))
	    .style("container_form")
	    .child(
		Stack::new()
		    .orientation("vertical")
		    .spacing(16.0)
		    .child(
			TextBlock::new()
			    .style("target_text_block")
			    .text("Target")
			    .build(ctx),
		    )
		    .child(
			TextBlock::new()
			    .style("target_text_block")
			    .text("Target content")
			    .build(ctx),
		    )
		    .build(ctx),
	    )
	    .build(ctx);

	let popup = Popup::new()
	    .id(ID_POPUP)
	    .name(ID_POPUP)
	    .style("popup_form")
	    // alternative: popup is target of given coordinates (Point)
	    //.target(Point::new(0.0, 0.0))
	    .height(180.0)
	    .open(false)
	    .offset(1.0)
	    .width(180.0)
	    .target(target_container)
	    .child(
		Container::new()
		    .child(
			Stack::new()
			    .id(ID_POPUP_STACK)
			    .orientation("vertical")
			    .spacing(16.0)
			    .child(
				TextBlock::new()
				    .style("popup_text_block")
				    .h_align("center")
				    .v_align("center")
				    .text("Popup Header")
				    .build(ctx),
			    )
			    .child(
				TextBlock::new()
				    .style("popup_text_block")
				    .h_align("center")
				    .v_align("center")
				    .text("Popup content")
				    .build(ctx),
			    )
			    .build(ctx),
		    )
		    .build(ctx),
	    )
	    .build(ctx);

	let _ = ctx.append_child_to_overlay(popup);

	// Widget:  MainView
	self.id(ID_MAIN_VIEW)
	    .name(ID_MAIN_VIEW)
	    .margin(20.0)
	    .placements(placements) // vector with placement strings
	    .child(
		Grid::new()
		    .id(ID_GRID)
		    .rows("80, 200, 200, *")
		    .columns("200, 200, 250")
		    .child(
			Button::new()
			    .id(ID_BUTTON)
			    .attach(Grid::row(0))
			    .attach(Grid::column(1))
			    .text("Click me to show the popup")
			    .on_click(move |state, _entity| -> bool {
				state.send_message(MainViewAction::TogglePopup, id);
				true
			    })
			    .build(ctx),
		    )
		    .child(
			Grid::new()
			    .id(ID_GRID_PLACEMENT)
			    .attach(Grid::row(0))
			    .attach(Grid::column(2))
			    .rows("auto, 4, auto")
			    .columns("120, 8, auto")
			    //.style("container_form")
			    .name(ID_GRID_PLACEMENT)
			    .child(
				TextBlock::new()
				    .id(ID_PLACEMENT_COMBO_BOX_LABEL)
				    .attach(Grid::row(0))
				    .attach(Grid::column(0))
				    .h_align("end")
				    .text("Placement:")
				    .v_align("center")
				    .build(ctx),
			    )
			    .child(
				ComboBox::new()
				    .id(ID_PLACEMENT_COMBO_BOX)
				    .attach(Grid::row(0))
				    .attach(Grid::column(2))
				    .count(count_placements)
				    //.style("combo_box")
				    .items_builder(move |ibc, index| {
					let text = MainView::placements_ref(&ibc.get_widget(id))
					    [index]
					    .clone();
					TextBlock::new().v_align("center").text(text).build(ibc)
				    })
				    .on_changed("selected_index", move |states, _entity| {
					states.send_message(MainViewAction::UpdatePlacement, id);
				    })
				    .selected_index(id)
				    .build(ctx),
			    )
			    .child(
				TextBlock::new()
				    .id(ID_PLACEMENT_OFFSET_LABEL)
				    .attach(Grid::row(2))
				    .attach(Grid::column(0))
				    .h_align("end")
				    .text("Offset:")
				    .v_align("center")
				    .build(ctx),
			    )
			    .child(
				NumericBox::new()
				    .id(ID_PLACEMENT_OFFSET)
				    .attach(Grid::row(2))
				    .attach(Grid::column(2))
				    .style("popup_numeric_box")
				    .max(50)
				    .step(1)
				    .val(5)
				    //.water_mark("in pixel")
				    .on_changed("val", move |states, _entity| {
					states.send_message(MainViewAction::UpdateOffset, id);
				    })
				    .build(ctx),
			    )
			    .build(ctx),
		    )
		    .child(target_container)
		    .build(ctx),
	    )
    }
}

fn main() {
    // if no dictionary is set for the default language
    // the content of the text property will drawn.
    let localization = RonLocalization::create()
	.language("de_DE")
	//.language("en_US")
	.dictionary("de_DE", POPUP_DE_DE)
	.build();

    Application::new()
	.localization(localization)
	.theme(theme())
	.window(|ctx| {
	    Window::new()
		.title("OrbTk - Popup example")
		.position((100.0, 100.0))
		.size(680, 690.0)
		.resizeable(true)
		.child(MainView::new().build(ctx))
		.build(ctx)
	})
	.run();
}
<commit_msg>orbtk: examples popup_v2<commit_after>use orbtk::{
    prelude::*,
    // only instantiate the `default` theme
    widgets::themes::theme_orbtk::{
	register_default_fonts, THEME_DEFAULT, THEME_DEFAULT_COLORS_DARK, THEME_DEFAULT_FONTS,
    },
};

// German localization file.
static POPUP_DE_DE: &str = include_str!("assets/popup/popup_de_DE.ron");

static DARK_EXT: &str = include_str!("assets/popup/default_dark.ron");

static ID_BUTTON: &str = "Toggle Popup";
static ID_GRID: &str = "PopupGrid";
static ID_GRID_PLACEMENT: &str = "Selection GRID";
static ID_MAIN_VIEW: &str = "MainView";
static ID_POPUP: &str = "Popup";
static ID_POPUP_STACK: &str = "PopupStack";
static ID_PLACEMENT_COMBO_BOX: &str = "Placement Selecton Box";
static ID_PLACEMENT_OFFSET: &str = "Placement Offset";
static ID_PLACEMENT_OFFSET_LABEL: &str = "Offset";
static ID_PLACEMENT_COMBO_BOX_LABEL: &str = "Placement Label";
static ID_TARGET: &str = "Target";

/// Valid `actions` that are handled as state changes in the `MainView` widget.
#[derive(Copy, Clone)]
enum MainViewAction {
    /// Toggle the visibility of the popup
    TogglePopup,

    /// Update the offset of the popup relative to the target
    UpdateOffset,

    /// Update the placement of the popup relative to the target
    UpdatePlacement,
}

/// Valid `structures` that are handled inside the state of the `MainView` widget.
#[derive(AsAny, Default)]
struct MainViewState {
    _action: Option<MainViewAction>,
}

/// Method definitions, that react on any given state change inside the `MainView` widget.
impl MainViewState {
    fn _action(&mut self, action: impl Into<Option<MainViewAction>>) {
	self._action = action.into();
    }

    fn toggle_popup(&mut self, ctx: &mut Context<'_>) {
	let open = ctx.child(ID_POPUP).clone::<bool>("open");

	    if open {
	    Popup::open_set(&mut ctx.child(ID_POPUP), false);
	    Popup::visibility_set(&mut ctx.child(ID_POPUP), Visibility::Collapsed);
		Button::text_set(&mut ctx.child(ID_BUTTON), "Click me to show the popup");
	    } else {
	    Popup::visibility_set(&mut ctx.child(ID_POPUP), Visibility::Visible);
	    Popup::open_set(&mut ctx.child(ID_POPUP), true);
		Button::text_set(&mut ctx.child(ID_BUTTON), "Click me to hide the popup");
	    }
    }

    fn update_placement(&mut self, ctx: &mut Context<'_>) {
	let selected_index: i32 = ctx.child(ID_PLACEMENT_COMBO_BOX).clone("selected_index");
	let placement: Placement = ctx.child(ID_POPUP).clone_or_default("placement");
	    match selected_index {
	    0 => Popup::placement_set(&mut ctx.child(ID_POPUP), placement.bottom()),
	    1 => Popup::placement_set(&mut ctx.child(ID_POPUP), placement.bottom_left()),
	    2 => Popup::placement_set(&mut ctx.child(ID_POPUP), placement.bottom_right()),
	    3 => Popup::placement_set(&mut ctx.child(ID_POPUP), placement.left()),
	    4 => Popup::placement_set(&mut ctx.child(ID_POPUP), placement.right()),
	    5 => Popup::placement_set(&mut ctx.child(ID_POPUP), placement.top()),
	    6 => Popup::placement_set(&mut ctx.child(ID_POPUP), placement.top_left()),
	    7 => Popup::placement_set(&mut ctx.child(ID_POPUP), placement.top_right()),
		_ => panic!(),
	    }
	if cfg!(debug) {
	    println!(
		"popup_v2: Placement {:?} (index: {:?}).",
		ctx.child(ID_POPUP).get::<Placement>("placement"),
		selected_index
	    );
	}
    }

    fn update_offset(&mut self, ctx: &mut Context<'_>) {
	let offset: f64 = ctx.child(ID_PLACEMENT_OFFSET).clone("val");
	Popup::offset_set(&mut ctx.child(ID_POPUP), offset);

	if cfg!(debug) {
	    println!(
		"popup_v2: Offset {:?}.",
		ctx.child(ID_POPUP).get::<f64>("offset")
	    );
	}
    }
}

/// Associated methods handled inside the `MainViewState`
impl State for MainViewState {
    fn init(&mut self, _registry: &mut Registry, ctx: &mut Context) {
	// initialize the popup properties
	ctx.child(ID_POPUP).set("open", false);
	ctx.child(ID_POPUP).set("visibility", Visibility::Collapsed);
    }

    fn messages(
	&mut self,
	mut messages: MessageReader,
	_registry: &mut Registry,
	ctx: &mut Context,
    ) {
	for message in messages.read::<MainViewAction>() {
	    match message {
		MainViewAction::TogglePopup => {
		    MainViewState::toggle_popup(self, ctx);
		}
		MainViewAction::UpdatePlacement => {
		    MainViewState::update_placement(self, ctx);
		}
		MainViewAction::UpdateOffset => {
		    MainViewState::update_offset(self, ctx);
		}
	    }
	}
    }
}

fn theme() -> Theme {
    register_default_fonts(Theme::from_config(
	ThemeConfig::from(DARK_EXT)
	    .extend(ThemeConfig::from(THEME_DEFAULT))
	    .extend(ThemeConfig::from(THEME_DEFAULT_COLORS_DARK))
	    .extend(ThemeConfig::from(THEME_DEFAULT_FONTS)),
    ))
}

// Used to define ComboBox list members
type List = Vec<String>;

// constructs the MainView
widget!(
    MainView<MainViewState> {
    /// Active seleced index of combo box.
    selected_index: i32,

    /// Acitve selected position of popup.
    placements: List
    }
);

/// The template implementation of the main view
/// GUI elements are styled using the "style" attribute referencing to a ron based css
impl Template for MainView {
    fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
	let placements = vec![
	    // TODO: make it sort independent
	    "Bottom".to_string(),
	    "BottomLeft".to_string(),
	    "BottomRight".to_string(),
	    "Left".to_string(),
	    "Right".to_string(),
	    "Top".to_string(),
	    "TopLeft".to_string(),
	    "TopRight".to_string(),
	];
	let count_placements = placements.len();

	let target_container = Container::new()
	    .id(ID_TARGET)
	    .attach(Grid::row(2))
	    .attach(Grid::column(1))
	    .style("container_form")
	    .child(
		Stack::new()
		    .orientation("vertical")
		    .spacing(16.0)
		    .child(
			TextBlock::new()
			    .style("target_text_block")
			    .text("Target")
			    .build(ctx),
		    )
		    .child(
			TextBlock::new()
			    .style("target_text_block")
			    .text("Target content")
			    .build(ctx),
		    )
		    .build(ctx),
	    )
	    .build(ctx);

	let popup = Popup::new()
	    .id(ID_POPUP)
	    .name(ID_POPUP)
	    .style("popup_form")
	    // alternative: popup is target of given coordinates (Point)
	    //.target(Point::new(0.0, 0.0))
	    .height(180.0)
	    .open(false)
	    .offset(1.0)
	    .width(180.0)
	    .target(target_container)
	    .child(
		Container::new()
		    .child(
			Stack::new()
			    .id(ID_POPUP_STACK)
			    .orientation("vertical")
			    .spacing(16.0)
			    .child(
				TextBlock::new()
				    .style("popup_text_block")
				    .h_align("center")
				    .v_align("center")
				    .text("Popup Header")
				    .build(ctx),
			    )
			    .child(
				TextBlock::new()
				    .style("popup_text_block")
				    .h_align("center")
				    .v_align("center")
				    .text("Popup content")
				    .build(ctx),
			    )
			    .build(ctx),
		    )
		    .build(ctx),
	    )
	    .build(ctx);

	let _ = ctx.append_child_to_overlay(popup);

	// Widget:  MainView
	self.id(ID_MAIN_VIEW)
	    .name(ID_MAIN_VIEW)
	    .margin(20.0)
	    .placements(placements) // vector with placement strings
	    .child(
		Grid::new()
		    .id(ID_GRID)
		    .rows("80, 200, 200, *")
		    .columns("200, 200, 250")
		    .child(
			Button::new()
			    .id(ID_BUTTON)
			    .attach(Grid::row(0))
			    .attach(Grid::column(1))
			    .text("Click me to show the popup")
			    .on_click(move |state, _entity| -> bool {
				state.send_message(MainViewAction::TogglePopup, id);
				true
			    })
			    .build(ctx),
		    )
		    .child(
			Grid::new()
			    .id(ID_GRID_PLACEMENT)
			    .attach(Grid::row(0))
			    .attach(Grid::column(2))
			    .rows("auto, 4, auto")
			    .columns("120, 8, auto")
			    //.style("container_form")
			    .name(ID_GRID_PLACEMENT)
			    .child(
				TextBlock::new()
				    .id(ID_PLACEMENT_COMBO_BOX_LABEL)
				    .attach(Grid::row(0))
				    .attach(Grid::column(0))
				    .h_align("end")
				    .text("Placement:")
				    .v_align("center")
				    .build(ctx),
			    )
			    .child(
				ComboBox::new()
				    .id(ID_PLACEMENT_COMBO_BOX)
				    .attach(Grid::row(0))
				    .attach(Grid::column(2))
				    .count(count_placements)
				    //.style("combo_box")
				    .items_builder(move |ibc, index| {
					let text = MainView::placements_ref(&ibc.get_widget(id))
					    [index]
					    .clone();
					TextBlock::new().v_align("center").text(text).build(ibc)
				    })
				    .on_changed("selected_index", move |states, _entity| {
					states.send_message(MainViewAction::UpdatePlacement, id);
				    })
				    .selected_index(id)
				    .build(ctx),
			    )
			    .child(
				TextBlock::new()
				    .id(ID_PLACEMENT_OFFSET_LABEL)
				    .attach(Grid::row(2))
				    .attach(Grid::column(0))
				    .h_align("end")
				    .text("Offset:")
				    .v_align("center")
				    .build(ctx),
			    )
			    .child(
				NumericBox::new()
				    .id(ID_PLACEMENT_OFFSET)
				    .attach(Grid::row(2))
				    .attach(Grid::column(2))
				    .style("popup_numeric_box")
				    .max(50)
				    .step(1)
				    .val(5)
				    //.water_mark("in pixel")
				    .on_changed("val", move |states, _entity| {
					states.send_message(MainViewAction::UpdateOffset, id);
				    })
				    .build(ctx),
			    )
			    .build(ctx),
		    )
		    .child(target_container)
		    .build(ctx),
	    )
    }
}

fn main() {
    // if no dictionary is set for the default language
    // the content of the text property will drawn.
    let localization = RonLocalization::create()
	.language("de_DE")
	//.language("en_US")
	.dictionary("de_DE", POPUP_DE_DE)
	.build();

    Application::new()
	.localization(localization)
	.theme(theme())
	.window(|ctx| {
	    Window::new()
		.title("OrbTk - Popup example")
		.position((100.0, 100.0))
		.size(680, 690.0)
		.resizeable(true)
		.child(MainView::new().build(ctx))
		.build(ctx)
	})
	.run();
}
<|endoftext|>


<commit_before>use crc32c::*;
use log::{debug, error, warn};
use std::{
    cell::RefCell,
    collections::HashMap,
    convert::TryInto,
    fs::{File, OpenOptions},
    io::{Cursor, Read, Seek, SeekFrom, Write},
    path::Path,
    sync::Arc,
};

use crate::blockchain::{block::*, index::*};

const MAX_CACHE_SIZE: u64 = 100;

#[derive(Clone, Debug, PartialEq)]
pub struct ReindexOpts {
    pub auto_trim: bool,
}

pub struct BlockStore {
    indexer: Arc<Indexer>,

    height: u64,
    blocks: HashMap<u64, Arc<SignedBlock>>,
    genesis_block: Option<Arc<SignedBlock>>,

    file: RefCell<File>,
    byte_pos_tail: u64,
}

impl BlockStore {
    pub fn new(blocklog_file: &Path, indexer: Arc<Indexer>) -> BlockStore {
        let (file, tail) = {
            let f = OpenOptions::new()
                .create(true)
                .read(true)
                .append(true)
                .open(blocklog_file)
                .unwrap();
            let m = f.metadata().unwrap();
            (f, m.len())
        };

        let mut store = BlockStore {
            indexer,

            height: 0,
            blocks: HashMap::new(),
            genesis_block: None,

            file: RefCell::new(file),
            byte_pos_tail: tail,
        };

        store.init_state();
        store
    }

    #[inline(always)]
    pub fn get_chain_height(&self) -> u64 {
        self.height
    }

    pub fn get(&self, height: u64) -> Option<Arc<SignedBlock>> {
        if height > self.height {
            return None;
        } else if height == 0 {
            if let Some(ref block) = self.genesis_block {
                return Some(Arc::clone(block));
            }
        }
        if let Some(block) = self.blocks.get(&height) {
            Some(Arc::clone(block))
        } else {
            Some(Arc::new(self.read_from_disk(height)?))
        }
    }

    pub fn is_empty(&mut self) -> bool {
        let meta = self.file.borrow_mut().metadata().unwrap();
        meta.len() == 0
    }

    pub fn insert(&mut self, batch: &mut WriteBatch, block: SignedBlock) {
        assert_eq!(self.height + 1, block.height(), "invalid block height");
        let byte_pos = self.byte_pos_tail;
        self.write_to_disk(&block);

        // Update internal cache
        let height = block.height();
        self.height = height;
        batch.set_block_byte_pos(height, byte_pos);
        batch.set_chain_height(height);

        let opt = self.blocks.insert(height, Arc::new(block));
        debug_assert!(opt.is_none(), "block already in the chain");

        if self.blocks.len() > MAX_CACHE_SIZE as usize {
            let b = self.blocks.remove(&(height - MAX_CACHE_SIZE));
            debug_assert!(b.is_some(), "nothing removed from cache");
        }
    }

    pub fn insert_genesis(&mut self, batch: &mut WriteBatch, block: SignedBlock) {
        assert_eq!(block.height(), 0, "expected to be 0");
        assert!(
            self.genesis_block.is_none(),
            "expected genesis block to not exist"
        );
        assert!(self.is_empty(), "block log must be empty");
        self.write_to_disk(&block);
        self.genesis_block = Some(Arc::new(block));
        batch.set_block_byte_pos(0, 0);
    }

    pub fn reindex_blocks<F>(&mut self, opts: ReindexOpts, index_fn: F)
    where
        F: Fn(&mut WriteBatch, &SignedBlock),
    {
        let mut batch = WriteBatch::new(Arc::clone(&self.indexer));
        let mut last_known_good_height = 0;
        let mut pos = 0;
        loop {
            match self.raw_read_from_disk(pos) {
                Ok(block) => {
                    let height = block.height();
                    let new_pos = {
                        let mut f = self.file.borrow_mut();
                        f.seek(SeekFrom::Current(0)).unwrap()
                    };
                    if !(last_known_good_height == 0 || height == last_known_good_height + 1) {
                        error!("Invalid height ({}) detected at byte pos {}", height, pos);
                        if opts.auto_trim {
                            warn!("Truncating block log");
                            let f = self.file.borrow();
                            f.set_len(pos).unwrap();
                            self.byte_pos_tail = pos;
                        } else {
                            panic!("corruption detected, auto trim is disabled");
                        }
                        break;
                    }

                    batch.set_block_byte_pos(height, pos);
                    batch.set_chain_height(height);
                    index_fn(&mut batch, &block);
                    debug!("Reindexed block {} at pos {}", height, pos);

                    pos = new_pos;
                    last_known_good_height = height;
                }
                Err(e) => match e {
                    ReadError::Eof => break,
                    ReadError::CorruptBlock => {
                        error!(
                            "(last known good height: {}, block end byte pos: {})",
                            last_known_good_height, pos
                        );
                        if opts.auto_trim {
                            warn!("Truncating block log");
                            let f = self.file.borrow();
                            f.set_len(pos).unwrap();
                            self.byte_pos_tail = pos;
                            break;
                        } else {
                            panic!("corrupt block detected, auto trim is disabled");
                        }
                    }
                },
            }
        }

        batch.commit();
        self.indexer.set_index_status(IndexStatus::Complete);
        self.init_state();
    }

    fn read_from_disk(&self, height: u64) -> Option<SignedBlock> {
        if height > self.height {
            return None;
        }

        let pos = self.indexer.get_block_byte_pos(height)?;
        self.raw_read_from_disk(pos).ok()
    }

    fn raw_read_from_disk(&self, pos: u64) -> Result<SignedBlock, ReadError> {
        let mut f = self.file.borrow_mut();
        f.seek(SeekFrom::Start(pos)).unwrap();

        let (block_len, crc) = {
            let mut meta = [0u8; 8];
            f.read_exact(&mut meta).map_err(|_| ReadError::Eof)?;
            let (len_buf, crc_buf) = meta.split_at(4);
            let len = u32::from_be_bytes(len_buf.try_into().unwrap()) as usize;
            let crc = u32::from_be_bytes(crc_buf.try_into().unwrap());
            (len, crc)
        };

        let block_vec = {
            let mut buf = Vec::with_capacity(block_len);
            unsafe {
                buf.set_len(block_len);
            }
            f.read_exact(&mut buf)
                .map_err(|_| ReadError::CorruptBlock)?;
            assert_eq!(crc, crc32c(&buf));
            buf
        };

        let mut cursor = Cursor::<&[u8]>::new(&block_vec);
        SignedBlock::deserialize_with_tx(&mut cursor).ok_or(ReadError::CorruptBlock)
    }

    fn write_to_disk(&mut self, block: &SignedBlock) {
        let vec = &mut Vec::with_capacity(1_048_576);
        block.serialize_with_tx(vec);
        let len = vec.len() as u32;
        let crc = crc32c(vec);

        let mut f = self.file.borrow_mut();
        {
            let mut buf = [0u8; 8];
            buf[0] = (len >> 24) as u8;
            buf[1] = (len >> 16) as u8;
            buf[2] = (len >> 8) as u8;
            buf[3] = len as u8;

            buf[4] = (crc >> 24) as u8;
            buf[5] = (crc >> 16) as u8;
            buf[6] = (crc >> 8) as u8;
            buf[7] = crc as u8;

            f.write_all(&buf).unwrap();
        }

        f.write_all(vec).unwrap();
        f.flush().unwrap();

        self.byte_pos_tail += u64::from(len) + 8;
    }

    fn init_state(&mut self) {
        self.height = self.indexer.get_chain_height();
        self.genesis_block = self.get(0);
        if !self.is_empty() {
            // Init block cache
            self.blocks.clear();
            let max = self.height;
            let min = max.saturating_sub(MAX_CACHE_SIZE);
            for height in min..=max {
                let block = self
                    .read_from_disk(height)
                    .unwrap_or_else(|| panic!("Failed to read block {} from disk", height));
                self.blocks.insert(height, Arc::new(block));
            }
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
enum ReadError {
    Eof,
    CorruptBlock,
}
<commit_msg>Check the index status before initializing the block cache<commit_after>use crc32c::*;
use log::{debug, error, warn};
use std::{
    cell::RefCell,
    collections::HashMap,
    convert::TryInto,
    fs::{File, OpenOptions},
    io::{Cursor, Read, Seek, SeekFrom, Write},
    path::Path,
    sync::Arc,
};

use crate::blockchain::{block::*, index::*};

const MAX_CACHE_SIZE: u64 = 100;

#[derive(Clone, Debug, PartialEq)]
pub struct ReindexOpts {
    pub auto_trim: bool,
}

pub struct BlockStore {
    indexer: Arc<Indexer>,

    height: u64,
    blocks: HashMap<u64, Arc<SignedBlock>>,
    genesis_block: Option<Arc<SignedBlock>>,

    file: RefCell<File>,
    byte_pos_tail: u64,
}

impl BlockStore {
    pub fn new(blocklog_file: &Path, indexer: Arc<Indexer>) -> BlockStore {
        let (file, tail) = {
            let f = OpenOptions::new()
                .create(true)
                .read(true)
                .append(true)
                .open(blocklog_file)
                .unwrap();
            let m = f.metadata().unwrap();
            (f, m.len())
        };

        let mut store = BlockStore {
            indexer,

            height: 0,
            blocks: HashMap::new(),
            genesis_block: None,

            file: RefCell::new(file),
            byte_pos_tail: tail,
        };

        store.init_state();
        store
    }

    #[inline(always)]
    pub fn get_chain_height(&self) -> u64 {
        self.height
    }

    pub fn get(&self, height: u64) -> Option<Arc<SignedBlock>> {
        if height > self.height {
            return None;
        } else if height == 0 {
            if let Some(ref block) = self.genesis_block {
                return Some(Arc::clone(block));
            }
        }
        if let Some(block) = self.blocks.get(&height) {
            Some(Arc::clone(block))
        } else {
            Some(Arc::new(self.read_from_disk(height)?))
        }
    }

    pub fn is_empty(&mut self) -> bool {
        let meta = self.file.borrow_mut().metadata().unwrap();
        meta.len() == 0
    }

    pub fn insert(&mut self, batch: &mut WriteBatch, block: SignedBlock) {
        assert_eq!(self.height + 1, block.height(), "invalid block height");
        let byte_pos = self.byte_pos_tail;
        self.write_to_disk(&block);

        // Update internal cache
        let height = block.height();
        self.height = height;
        batch.set_block_byte_pos(height, byte_pos);
        batch.set_chain_height(height);

        let opt = self.blocks.insert(height, Arc::new(block));
        debug_assert!(opt.is_none(), "block already in the chain");

        if self.blocks.len() > MAX_CACHE_SIZE as usize {
            let b = self.blocks.remove(&(height - MAX_CACHE_SIZE));
            debug_assert!(b.is_some(), "nothing removed from cache");
        }
    }

    pub fn insert_genesis(&mut self, batch: &mut WriteBatch, block: SignedBlock) {
        assert_eq!(block.height(), 0, "expected to be 0");
        assert!(
            self.genesis_block.is_none(),
            "expected genesis block to not exist"
        );
        assert!(self.is_empty(), "block log must be empty");
        self.write_to_disk(&block);
        self.genesis_block = Some(Arc::new(block));
        batch.set_block_byte_pos(0, 0);
    }

    pub fn reindex_blocks<F>(&mut self, opts: ReindexOpts, index_fn: F)
    where
        F: Fn(&mut WriteBatch, &SignedBlock),
    {
        let mut batch = WriteBatch::new(Arc::clone(&self.indexer));
        let mut last_known_good_height = 0;
        let mut pos = 0;
        loop {
            match self.raw_read_from_disk(pos) {
                Ok(block) => {
                    let height = block.height();
                    let new_pos = {
                        let mut f = self.file.borrow_mut();
                        f.seek(SeekFrom::Current(0)).unwrap()
                    };
                    if !(last_known_good_height == 0 || height == last_known_good_height + 1) {
                        error!("Invalid height ({}) detected at byte pos {}", height, pos);
                        if opts.auto_trim {
                            warn!("Truncating block log");
                            let f = self.file.borrow();
                            f.set_len(pos).unwrap();
                            self.byte_pos_tail = pos;
                        } else {
                            panic!("corruption detected, auto trim is disabled");
                        }
                        break;
                    }

                    batch.set_block_byte_pos(height, pos);
                    batch.set_chain_height(height);
                    index_fn(&mut batch, &block);
                    debug!("Reindexed block {} at pos {}", height, pos);

                    pos = new_pos;
                    last_known_good_height = height;
                }
                Err(e) => match e {
                    ReadError::Eof => break,
                    ReadError::CorruptBlock => {
                        error!(
                            "(last known good height: {}, block end byte pos: {})",
                            last_known_good_height, pos
                        );
                        if opts.auto_trim {
                            warn!("Truncating block log");
                            let f = self.file.borrow();
                            f.set_len(pos).unwrap();
                            self.byte_pos_tail = pos;
                            break;
                        } else {
                            panic!("corrupt block detected, auto trim is disabled");
                        }
                    }
                },
            }
        }

        batch.commit();
        self.indexer.set_index_status(IndexStatus::Complete);
        self.init_state();
    }

    fn read_from_disk(&self, height: u64) -> Option<SignedBlock> {
        if height > self.height {
            return None;
        }

        let pos = self.indexer.get_block_byte_pos(height)?;
        self.raw_read_from_disk(pos).ok()
    }

    fn raw_read_from_disk(&self, pos: u64) -> Result<SignedBlock, ReadError> {
        let mut f = self.file.borrow_mut();
        f.seek(SeekFrom::Start(pos)).unwrap();

        let (block_len, crc) = {
            let mut meta = [0u8; 8];
            f.read_exact(&mut meta).map_err(|_| ReadError::Eof)?;
            let (len_buf, crc_buf) = meta.split_at(4);
            let len = u32::from_be_bytes(len_buf.try_into().unwrap()) as usize;
            let crc = u32::from_be_bytes(crc_buf.try_into().unwrap());
            (len, crc)
        };

        let block_vec = {
            let mut buf = Vec::with_capacity(block_len);
            unsafe {
                buf.set_len(block_len);
            }
            f.read_exact(&mut buf)
                .map_err(|_| ReadError::CorruptBlock)?;
            assert_eq!(crc, crc32c(&buf));
            buf
        };

        let mut cursor = Cursor::<&[u8]>::new(&block_vec);
        SignedBlock::deserialize_with_tx(&mut cursor).ok_or(ReadError::CorruptBlock)
    }

    fn write_to_disk(&mut self, block: &SignedBlock) {
        let vec = &mut Vec::with_capacity(1_048_576);
        block.serialize_with_tx(vec);
        let len = vec.len() as u32;
        let crc = crc32c(vec);

        let mut f = self.file.borrow_mut();
        {
            let mut buf = [0u8; 8];
            buf[0] = (len >> 24) as u8;
            buf[1] = (len >> 16) as u8;
            buf[2] = (len >> 8) as u8;
            buf[3] = len as u8;

            buf[4] = (crc >> 24) as u8;
            buf[5] = (crc >> 16) as u8;
            buf[6] = (crc >> 8) as u8;
            buf[7] = crc as u8;

            f.write_all(&buf).unwrap();
        }

        f.write_all(vec).unwrap();
        f.flush().unwrap();

        self.byte_pos_tail += u64::from(len) + 8;
    }

    fn init_state(&mut self) {
        self.height = self.indexer.get_chain_height();
        self.genesis_block = self.get(0);
        if !self.is_empty() && self.indexer.index_status() == IndexStatus::Complete {
            // Init block cache
            self.blocks.clear();
            let max = self.height;
            let min = max.saturating_sub(MAX_CACHE_SIZE);
            for height in min..=max {
                let block = self
                    .read_from_disk(height)
                    .unwrap_or_else(|| panic!("Failed to read block {} from disk", height));
                self.blocks.insert(height, Arc::new(block));
            }
        }
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
enum ReadError {
    Eof,
    CorruptBlock,
}
<|endoftext|>


<commit_before>//! FIXME: write short doc here

use std::{
    ffi::OsStr,
    ops,
    path::{Path, PathBuf},
    process::Command,
};

use anyhow::{Context, Result};
use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId};
use paths::{AbsPath, AbsPathBuf};
use ra_arena::{Arena, Idx};
use ra_db::Edition;
use rustc_hash::FxHashMap;

/// `CargoWorkspace` represents the logical structure of, well, a Cargo
/// workspace. It pretty closely mirrors `cargo metadata` output.
///
/// Note that internally, rust analyzer uses a different structure:
/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates,
/// while this knows about `Packages` & `Targets`: purely cargo-related
/// concepts.
///
/// We use absolute paths here, `cargo metadata` guarantees to always produce
/// abs paths.
#[derive(Debug, Clone)]
pub struct CargoWorkspace {
    packages: Arena<PackageData>,
    targets: Arena<TargetData>,
    workspace_root: AbsPathBuf,
}

impl ops::Index<Package> for CargoWorkspace {
    type Output = PackageData;
    fn index(&self, index: Package) -> &PackageData {
        &self.packages[index]
    }
}

impl ops::Index<Target> for CargoWorkspace {
    type Output = TargetData;
    fn index(&self, index: Target) -> &TargetData {
        &self.targets[index]
    }
}

#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct CargoConfig {
    /// Do not activate the `default` feature.
    pub no_default_features: bool,

    /// Activate all available features
    pub all_features: bool,

    /// List of features to activate.
    /// This will be ignored if `cargo_all_features` is true.
    pub features: Vec<String>,

    /// Runs cargo check on launch to figure out the correct values of OUT_DIR
    pub load_out_dirs_from_check: bool,

    /// rustc target
    pub target: Option<String>,
}

pub type Package = Idx<PackageData>;

pub type Target = Idx<TargetData>;

#[derive(Debug, Clone)]
pub struct PackageData {
    pub version: String,
    pub name: String,
    pub manifest: AbsPathBuf,
    pub targets: Vec<Target>,
    pub is_member: bool,
    pub dependencies: Vec<PackageDependency>,
    pub edition: Edition,
    pub features: Vec<String>,
    pub cfgs: Vec<String>,
    pub out_dir: Option<AbsPathBuf>,
    pub proc_macro_dylib_path: Option<AbsPathBuf>,
}

#[derive(Debug, Clone)]
pub struct PackageDependency {
    pub pkg: Package,
    pub name: String,
}

#[derive(Debug, Clone)]
pub struct TargetData {
    pub package: Package,
    pub name: String,
    pub root: AbsPathBuf,
    pub kind: TargetKind,
    pub is_proc_macro: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetKind {
    Bin,
    /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...).
    Lib,
    Example,
    Test,
    Bench,
    Other,
}

impl TargetKind {
    fn new(kinds: &[String]) -> TargetKind {
        for kind in kinds {
            return match kind.as_str() {
                "bin" => TargetKind::Bin,
                "test" => TargetKind::Test,
                "bench" => TargetKind::Bench,
                "example" => TargetKind::Example,
                "proc-macro" => TargetKind::Lib,
                _ if kind.contains("lib") => TargetKind::Lib,
                _ => continue,
            };
        }
        TargetKind::Other
    }
}

impl PackageData {
    pub fn root(&self) -> &AbsPath {
        self.manifest.parent().unwrap()
    }
}

impl CargoWorkspace {
    pub fn from_cargo_metadata(
        cargo_toml: &Path,
        cargo_features: &CargoConfig,
    ) -> Result<CargoWorkspace> {
        let mut meta = MetadataCommand::new();
        meta.cargo_path(ra_toolchain::cargo());
        meta.manifest_path(cargo_toml);
        if cargo_features.all_features {
            meta.features(CargoOpt::AllFeatures);
        } else if cargo_features.no_default_features {
            // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
            // https://github.com/oli-obk/cargo_metadata/issues/79
            meta.features(CargoOpt::NoDefaultFeatures);
        } else if !cargo_features.features.is_empty() {
            meta.features(CargoOpt::SomeFeatures(cargo_features.features.clone()));
        }
        if let Some(parent) = cargo_toml.parent() {
            meta.current_dir(parent);
        }
        if let Some(target) = cargo_features.target.as_ref() {
            meta.other_options(vec![String::from("--filter-platform"), target.clone()]);
        }
        let meta = meta.exec().with_context(|| {
            format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display())
        })?;

        let mut out_dir_by_id = FxHashMap::default();
        let mut cfgs = FxHashMap::default();
        let mut proc_macro_dylib_paths = FxHashMap::default();
        if cargo_features.load_out_dirs_from_check {
            let resources = load_extern_resources(cargo_toml, cargo_features)?;
            out_dir_by_id = resources.out_dirs;
            cfgs = resources.cfgs;
            proc_macro_dylib_paths = resources.proc_dylib_paths;
        }

        let mut pkg_by_id = FxHashMap::default();
        let mut packages = Arena::default();
        let mut targets = Arena::default();

        let ws_members = &meta.workspace_members;

        for meta_pkg in meta.packages {
            let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } =
                meta_pkg;
            let is_member = ws_members.contains(&id);
            let edition = edition
                .parse::<Edition>()
                .with_context(|| format!("Failed to parse edition {}", edition))?;
            let pkg = packages.alloc(PackageData {
                name,
                version: version.to_string(),
                manifest: AbsPathBuf::assert(manifest_path),
                targets: Vec::new(),
                is_member,
                edition,
                dependencies: Vec::new(),
                features: Vec::new(),
                cfgs: cfgs.get(&id).cloned().unwrap_or_default(),
                out_dir: out_dir_by_id.get(&id).cloned(),
                proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(),
            });
            let pkg_data = &mut packages[pkg];
            pkg_by_id.insert(id, pkg);
            for meta_tgt in meta_pkg.targets {
                let is_proc_macro = meta_tgt.kind.as_slice() == ["proc-macro"];
                let tgt = targets.alloc(TargetData {
                    package: pkg,
                    name: meta_tgt.name,
                    root: AbsPathBuf::assert(meta_tgt.src_path.clone()),
                    kind: TargetKind::new(meta_tgt.kind.as_slice()),
                    is_proc_macro,
                });
                pkg_data.targets.push(tgt);
            }
        }
        let resolve = meta.resolve.expect("metadata executed with deps");
        for node in resolve.nodes {
            let source = match pkg_by_id.get(&node.id) {
                Some(&src) => src,
                // FIXME: replace this and a similar branch below with `.unwrap`, once
                // https://github.com/rust-lang/cargo/issues/7841
                // is fixed and hits stable (around 1.43-is probably?).
                None => {
                    log::error!("Node id do not match in cargo metadata, ignoring {}", node.id);
                    continue;
                }
            };
            for dep_node in node.deps {
                let pkg = match pkg_by_id.get(&dep_node.pkg) {
                    Some(&pkg) => pkg,
                    None => {
                        log::error!(
                            "Dep node id do not match in cargo metadata, ignoring {}",
                            dep_node.pkg
                        );
                        continue;
                    }
                };
                let dep = PackageDependency { name: dep_node.name, pkg };
                packages[source].dependencies.push(dep);
            }
            packages[source].features.extend(node.features);
        }

        let workspace_root = AbsPathBuf::assert(meta.workspace_root);
        Ok(CargoWorkspace { packages, targets, workspace_root: workspace_root })
    }

    pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + ExactSizeIterator + 'a {
        self.packages.iter().map(|(id, _pkg)| id)
    }

    pub fn target_by_root(&self, root: &AbsPath) -> Option<Target> {
        self.packages()
            .filter_map(|pkg| self[pkg].targets.iter().find(|&&it| &self[it].root == root))
            .next()
            .copied()
    }

    pub fn workspace_root(&self) -> &AbsPath {
        &self.workspace_root
    }

    pub fn package_flag(&self, package: &PackageData) -> String {
        if self.is_unique(&*package.name) {
            package.name.clone()
        } else {
            format!("{}:{}", package.name, package.version)
        }
    }

    fn is_unique(&self, name: &str) -> bool {
        self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
    }
}

#[derive(Debug, Clone, Default)]
pub struct ExternResources {
    out_dirs: FxHashMap<PackageId, AbsPathBuf>,
    proc_dylib_paths: FxHashMap<PackageId, AbsPathBuf>,
    cfgs: FxHashMap<PackageId, Vec<String>>,
}

pub fn load_extern_resources(
    cargo_toml: &Path,
    cargo_features: &CargoConfig,
) -> Result<ExternResources> {
    let mut cmd = Command::new(ra_toolchain::cargo());
    cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml);
    if cargo_features.all_features {
        cmd.arg("--all-features");
    } else if cargo_features.no_default_features {
        // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
        // https://github.com/oli-obk/cargo_metadata/issues/79
        cmd.arg("--no-default-features");
    } else {
        cmd.args(&cargo_features.features);
    }

    let output = cmd.output()?;

    let mut res = ExternResources::default();

    for message in cargo_metadata::Message::parse_stream(output.stdout.as_slice()) {
        if let Ok(message) = message {
            match message {
                Message::BuildScriptExecuted(BuildScript { package_id, out_dir, cfgs, .. }) => {
                    // cargo_metadata crate returns default (empty) path for
                    // older cargos, which is not absolute, so work around that.
                    if out_dir != PathBuf::default() {
                        let out_dir = AbsPathBuf::assert(out_dir);
                        res.out_dirs.insert(package_id.clone(), out_dir);
                        res.cfgs.insert(package_id, cfgs);
                    }
                }
                Message::CompilerArtifact(message) => {
                    if message.target.kind.contains(&"proc-macro".to_string()) {
                        let package_id = message.package_id;
                        // Skip rmeta file
                        if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name))
                        {
                            let filename = AbsPathBuf::assert(filename.clone());
                            res.proc_dylib_paths.insert(package_id, filename);
                        }
                    }
                }
                Message::CompilerMessage(_) => (),
                Message::Unknown => (),
                Message::BuildFinished(_) => {}
                Message::TextLine(_) => {}
            }
        }
    }
    Ok(res)
}

// FIXME: File a better way to know if it is a dylib
fn is_dylib(path: &Path) -> bool {
    match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) {
        None => false,
        Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
    }
}
<commit_msg>AbsPath<commit_after>//! FIXME: write short doc here

use std::{
    ffi::OsStr,
    ops,
    path::{Path, PathBuf},
    process::Command,
};

use anyhow::{Context, Result};
use cargo_metadata::{BuildScript, CargoOpt, Message, MetadataCommand, PackageId};
use paths::{AbsPath, AbsPathBuf};
use ra_arena::{Arena, Idx};
use ra_db::Edition;
use rustc_hash::FxHashMap;

/// `CargoWorkspace` represents the logical structure of, well, a Cargo
/// workspace. It pretty closely mirrors `cargo metadata` output.
///
/// Note that internally, rust analyzer uses a different structure:
/// `CrateGraph`. `CrateGraph` is lower-level: it knows only about the crates,
/// while this knows about `Packages` & `Targets`: purely cargo-related
/// concepts.
///
/// We use absolute paths here, `cargo metadata` guarantees to always produce
/// abs paths.
#[derive(Debug, Clone)]
pub struct CargoWorkspace {
    packages: Arena<PackageData>,
    targets: Arena<TargetData>,
    workspace_root: AbsPathBuf,
}

impl ops::Index<Package> for CargoWorkspace {
    type Output = PackageData;
    fn index(&self, index: Package) -> &PackageData {
        &self.packages[index]
    }
}

impl ops::Index<Target> for CargoWorkspace {
    type Output = TargetData;
    fn index(&self, index: Target) -> &TargetData {
        &self.targets[index]
    }
}

#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct CargoConfig {
    /// Do not activate the `default` feature.
    pub no_default_features: bool,

    /// Activate all available features
    pub all_features: bool,

    /// List of features to activate.
    /// This will be ignored if `cargo_all_features` is true.
    pub features: Vec<String>,

    /// Runs cargo check on launch to figure out the correct values of OUT_DIR
    pub load_out_dirs_from_check: bool,

    /// rustc target
    pub target: Option<String>,
}

pub type Package = Idx<PackageData>;

pub type Target = Idx<TargetData>;

#[derive(Debug, Clone)]
pub struct PackageData {
    pub version: String,
    pub name: String,
    pub manifest: AbsPathBuf,
    pub targets: Vec<Target>,
    pub is_member: bool,
    pub dependencies: Vec<PackageDependency>,
    pub edition: Edition,
    pub features: Vec<String>,
    pub cfgs: Vec<String>,
    pub out_dir: Option<AbsPathBuf>,
    pub proc_macro_dylib_path: Option<AbsPathBuf>,
}

#[derive(Debug, Clone)]
pub struct PackageDependency {
    pub pkg: Package,
    pub name: String,
}

#[derive(Debug, Clone)]
pub struct TargetData {
    pub package: Package,
    pub name: String,
    pub root: AbsPathBuf,
    pub kind: TargetKind,
    pub is_proc_macro: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TargetKind {
    Bin,
    /// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...).
    Lib,
    Example,
    Test,
    Bench,
    Other,
}

impl TargetKind {
    fn new(kinds: &[String]) -> TargetKind {
        for kind in kinds {
            return match kind.as_str() {
                "bin" => TargetKind::Bin,
                "test" => TargetKind::Test,
                "bench" => TargetKind::Bench,
                "example" => TargetKind::Example,
                "proc-macro" => TargetKind::Lib,
                _ if kind.contains("lib") => TargetKind::Lib,
                _ => continue,
            };
        }
        TargetKind::Other
    }
}

impl PackageData {
    pub fn root(&self) -> &AbsPath {
        self.manifest.parent().unwrap()
    }
}

impl CargoWorkspace {
    pub fn from_cargo_metadata(
        cargo_toml: &AbsPath,
        cargo_features: &CargoConfig,
    ) -> Result<CargoWorkspace> {
        let mut meta = MetadataCommand::new();
        meta.cargo_path(ra_toolchain::cargo());
        meta.manifest_path(cargo_toml.to_path_buf());
        if cargo_features.all_features {
            meta.features(CargoOpt::AllFeatures);
        } else if cargo_features.no_default_features {
            // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
            // https://github.com/oli-obk/cargo_metadata/issues/79
            meta.features(CargoOpt::NoDefaultFeatures);
        } else if !cargo_features.features.is_empty() {
            meta.features(CargoOpt::SomeFeatures(cargo_features.features.clone()));
        }
        if let Some(parent) = cargo_toml.parent() {
            meta.current_dir(parent.to_path_buf());
        }
        if let Some(target) = cargo_features.target.as_ref() {
            meta.other_options(vec![String::from("--filter-platform"), target.clone()]);
        }
        let meta = meta.exec().with_context(|| {
            format!("Failed to run `cargo metadata --manifest-path {}`", cargo_toml.display())
        })?;

        let mut out_dir_by_id = FxHashMap::default();
        let mut cfgs = FxHashMap::default();
        let mut proc_macro_dylib_paths = FxHashMap::default();
        if cargo_features.load_out_dirs_from_check {
            let resources = load_extern_resources(cargo_toml, cargo_features)?;
            out_dir_by_id = resources.out_dirs;
            cfgs = resources.cfgs;
            proc_macro_dylib_paths = resources.proc_dylib_paths;
        }

        let mut pkg_by_id = FxHashMap::default();
        let mut packages = Arena::default();
        let mut targets = Arena::default();

        let ws_members = &meta.workspace_members;

        for meta_pkg in meta.packages {
            let cargo_metadata::Package { id, edition, name, manifest_path, version, .. } =
                meta_pkg;
            let is_member = ws_members.contains(&id);
            let edition = edition
                .parse::<Edition>()
                .with_context(|| format!("Failed to parse edition {}", edition))?;
            let pkg = packages.alloc(PackageData {
                name,
                version: version.to_string(),
                manifest: AbsPathBuf::assert(manifest_path),
                targets: Vec::new(),
                is_member,
                edition,
                dependencies: Vec::new(),
                features: Vec::new(),
                cfgs: cfgs.get(&id).cloned().unwrap_or_default(),
                out_dir: out_dir_by_id.get(&id).cloned(),
                proc_macro_dylib_path: proc_macro_dylib_paths.get(&id).cloned(),
            });
            let pkg_data = &mut packages[pkg];
            pkg_by_id.insert(id, pkg);
            for meta_tgt in meta_pkg.targets {
                let is_proc_macro = meta_tgt.kind.as_slice() == ["proc-macro"];
                let tgt = targets.alloc(TargetData {
                    package: pkg,
                    name: meta_tgt.name,
                    root: AbsPathBuf::assert(meta_tgt.src_path.clone()),
                    kind: TargetKind::new(meta_tgt.kind.as_slice()),
                    is_proc_macro,
                });
                pkg_data.targets.push(tgt);
            }
        }
        let resolve = meta.resolve.expect("metadata executed with deps");
        for node in resolve.nodes {
            let source = match pkg_by_id.get(&node.id) {
                Some(&src) => src,
                // FIXME: replace this and a similar branch below with `.unwrap`, once
                // https://github.com/rust-lang/cargo/issues/7841
                // is fixed and hits stable (around 1.43-is probably?).
                None => {
                    log::error!("Node id do not match in cargo metadata, ignoring {}", node.id);
                    continue;
                }
            };
            for dep_node in node.deps {
                let pkg = match pkg_by_id.get(&dep_node.pkg) {
                    Some(&pkg) => pkg,
                    None => {
                        log::error!(
                            "Dep node id do not match in cargo metadata, ignoring {}",
                            dep_node.pkg
                        );
                        continue;
                    }
                };
                let dep = PackageDependency { name: dep_node.name, pkg };
                packages[source].dependencies.push(dep);
            }
            packages[source].features.extend(node.features);
        }

        let workspace_root = AbsPathBuf::assert(meta.workspace_root);
        Ok(CargoWorkspace { packages, targets, workspace_root: workspace_root })
    }

    pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + ExactSizeIterator + 'a {
        self.packages.iter().map(|(id, _pkg)| id)
    }

    pub fn target_by_root(&self, root: &AbsPath) -> Option<Target> {
        self.packages()
            .filter_map(|pkg| self[pkg].targets.iter().find(|&&it| &self[it].root == root))
            .next()
            .copied()
    }

    pub fn workspace_root(&self) -> &AbsPath {
        &self.workspace_root
    }

    pub fn package_flag(&self, package: &PackageData) -> String {
        if self.is_unique(&*package.name) {
            package.name.clone()
        } else {
            format!("{}:{}", package.name, package.version)
        }
    }

    fn is_unique(&self, name: &str) -> bool {
        self.packages.iter().filter(|(_, v)| v.name == name).count() == 1
    }
}

#[derive(Debug, Clone, Default)]
pub struct ExternResources {
    out_dirs: FxHashMap<PackageId, AbsPathBuf>,
    proc_dylib_paths: FxHashMap<PackageId, AbsPathBuf>,
    cfgs: FxHashMap<PackageId, Vec<String>>,
}

pub fn load_extern_resources(
    cargo_toml: &Path,
    cargo_features: &CargoConfig,
) -> Result<ExternResources> {
    let mut cmd = Command::new(ra_toolchain::cargo());
    cmd.args(&["check", "--message-format=json", "--manifest-path"]).arg(cargo_toml);
    if cargo_features.all_features {
        cmd.arg("--all-features");
    } else if cargo_features.no_default_features {
        // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
        // https://github.com/oli-obk/cargo_metadata/issues/79
        cmd.arg("--no-default-features");
    } else {
        cmd.args(&cargo_features.features);
    }

    let output = cmd.output()?;

    let mut res = ExternResources::default();

    for message in cargo_metadata::Message::parse_stream(output.stdout.as_slice()) {
        if let Ok(message) = message {
            match message {
                Message::BuildScriptExecuted(BuildScript { package_id, out_dir, cfgs, .. }) => {
                    // cargo_metadata crate returns default (empty) path for
                    // older cargos, which is not absolute, so work around that.
                    if out_dir != PathBuf::default() {
                        let out_dir = AbsPathBuf::assert(out_dir);
                        res.out_dirs.insert(package_id.clone(), out_dir);
                        res.cfgs.insert(package_id, cfgs);
                    }
                }
                Message::CompilerArtifact(message) => {
                    if message.target.kind.contains(&"proc-macro".to_string()) {
                        let package_id = message.package_id;
                        // Skip rmeta file
                        if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name))
                        {
                            let filename = AbsPathBuf::assert(filename.clone());
                            res.proc_dylib_paths.insert(package_id, filename);
                        }
                    }
                }
                Message::CompilerMessage(_) => (),
                Message::Unknown => (),
                Message::BuildFinished(_) => {}
                Message::TextLine(_) => {}
            }
        }
    }
    Ok(res)
}

// FIXME: File a better way to know if it is a dylib
fn is_dylib(path: &Path) -> bool {
    match path.extension().and_then(OsStr::to_str).map(|it| it.to_string().to_lowercase()) {
        None => false,
        Some(ext) => matches!(ext.as_str(), "dll" | "dylib" | "so"),
    }
}
<|endoftext|>


<commit_before>#![deny(warnings)]

extern crate coreutils;
extern crate extra;

use std::cell::Cell;
use std::collections::HashMap;
use std::env;
use std::io::{self, Stdout, Stderr,  BufRead, Read, Write};

use coreutils::ArgParser;
use extra::io::{fail, WriteExt};

static OK: i32                      = 0;
static INVALID_FLAG: i32            = 1;
static REPLACE_CANNOT_BE_EMPTY: i32 = 2;
static WRITE_ERROR: i32             = 3;

static USAGE: &'static str = r#"usage: tr -c string1  string2
    tr -d string1
    tr -s string1 string2
"#;

const MAN_PAGE: &'static str = /* @MANSTART{tr} */ r#"NAME
    tr - translate characters

SYNOPSIS
    tr [ -cdst ] [ string1 [ string2 ] ]

DESCRIPTION
    Tr copies the standard input to the standard output with
    substitution or deletion of selected characters.  Input
    characters found in string1 are mapped into the correspond-
    ing characters of string2. When string2 is short it is pad-
    ded to the length of string1 by duplicating its last charac-
    ter.  Any combination of the options -cds may be used:

    --complement
    -c   complement the set of characters in string1 with
         respect to the universe of characters whose ASCII codes
         are 01 through 0377

    --delete
    -d   delete all input characters in string1

    --squeeze
    -s   squeeze all strings of repeated output characters that
         are in string2 to single characters

    --truncate
    -t   first truncate string1 to length of string2

    In either string the notation a-b means a range of charac-
    ters from a to b in increasing ASCII order.  The character
    `\' followed by 1, 2 or 3 octal digits stands for the char-
    acter whose ASCII code is given by those digits.  A `\' fol-
    lowed by any other character stands for that character.

    The following example creates a list of all the words in
    `file1' one per line in `file2', where a word is taken to be
    a maximal string of alphabetics.  The second string is
    quoted to protect `\' from the Shell.  012 is the ASCII code
    for newline.

        tr -cs A-Za-z '\012' <file1 >file2

SEE ALSO
    ed(1), ascii(7)

BUGS
    loads.
"#; /* @MANEND */

struct Translation {
    complement:  bool,
    delete:      bool,
    squeeze:     bool,
    truncate:    bool,
    search:      String,
    replace:     String,
    status:      Cell<i32>
}

impl Translation {

    fn print_opts(&self) {
        println!("flags\ncompliment {}\ndelete: {}\nsqueeze {}", self.complement, self.delete, self.squeeze);
        println!("search: {}", self.search);
        println!("replace: {}", self.replace);
    }

    fn truncate(&mut self, input: String, length: usize) -> String {
        // use an iterator just in case we have diacretes or other complex chars
        let mut new_string = "".to_string();
        {
            let mut char_walker = input.chars();
            for _ in 0 .. length {
                let last_char = char_walker.next().unwrap();
                new_string.push(last_char);
            }
        }
        return new_string;
    }

    fn append_or_truncate(&mut self) -> &mut Translation {
        // first decide
        let search_length = self.search.chars().count();
        let replace_length = self.replace.chars().count();

        if replace_length < search_length {
            //build adjust search or replace?
            if self.truncate {
                let old_value = self.search.clone();
                self.search = self.truncate(old_value, replace_length);
            } else {
                // fill replace with it's last char to match search in length
                let lastchar_as_string = self.replace.chars().last().unwrap();
                for _ in replace_length .. search_length {
                    self.replace.push(lastchar_as_string); // do something
                }
            }
        } else if replace_length > search_length {
            // truncate replaces length to search'
            let old_value = self.replace.clone();
            self.replace = self.truncate(old_value, search_length);
        }
        return self;
    }

    fn make_map(&mut self) -> HashMap<char, char> {
        // prereq is that search and replace are now the same length
        return self.search.chars().zip(self.replace.chars()).collect();
    }

    fn get_opts(&mut self, stdout: &mut Stdout, mut stderr: &mut Stderr) -> &mut Translation {
        let mut parser = ArgParser::new(2)
            .add_flag("c", "complement")
            .add_flag("d", "delete")
            .add_flag("s", "squeeze")
            .add_flag("t", "truncate")
            .add_flag("h", "help");
        parser.parse(env::args());
        if let Err(err) = parser.found_invalid() {
            let _ = stderr.write(err.as_bytes());
            self.status.set(INVALID_FLAG);
        } else {
            self.complement = parser.found("complement");
            self.delete = parser.found("delete");
            self.squeeze = parser.found("squeeze");
            self.truncate = parser.found("truncate");

            if parser.found("help") {
                let _ = stdout.write(MAN_PAGE.as_bytes());
            }
            let mut iter = parser.args.iter();
            let mut next = iter.next();
            if next.is_some() {
                self.search = next.unwrap().clone();
                next = iter.next();
                if next.is_some() {
                    self.replace = next.unwrap().clone();
                }
            } else {
                let _ = stderr.write("set of characters to replace is obligatory".as_bytes());
                let _ = stdout.write(MAN_PAGE.as_bytes());
            }
        }
        return self;
    }

    fn check_opts(&mut self) -> &mut Translation {
        if !self.delete && !self.squeeze && self.replace.len() == 0 {
            // big issue
            println!("replace string can not be empty when neither -s nor -d is specified.");
            println!("{}", MAN_PAGE);
            self.status.set(REPLACE_CANNOT_BE_EMPTY);
        }

        return self;
    }

    fn translate<R: Read, W: Write>(& mut self, input: R, output: W,) {
        // do the work
        let map = self.make_map();
        let reader = io::BufReader::new(input);
        let mut writer = io::BufWriter::new(output);

        for line in reader.lines() {
            let line = line.unwrap();
            let chars = line.chars();
            for kar in chars {
                let mut output = kar;
                if map.get(&kar).is_some() {
                    output = *map.get(&kar).unwrap();
                }
                if writer.write_char(output).is_err() {
                    self.status.set(WRITE_ERROR);
                    return;
                }
            }
            if writer.write_char('\n').is_err() {
                self.status.set(WRITE_ERROR);
                return;
            }
        }
    }
}

fn main() {
    let stdin = io::stdin();
    let mut stdout = io::stdout();
    let mut stderr = io::stderr();
    let mut tr = Translation { complement: false, delete: false, squeeze: false, truncate: false, search: String::new(), replace: String::new(), status: Cell::new(OK)};
    tr.get_opts(&mut stdout,&mut stderr);
    tr.check_opts();
// actually put them somewhere for retrieval by the other parts of the program instead of print
    tr.append_or_truncate();
    if tr.status.get() > 0 {
        tr.print_opts();
        fail(USAGE, &mut stderr);
    }

    // if complement is turned on recreate 'search' to contain the complement of search
    tr.translate(stdin, &mut stdout);
    // open std input
    // open std ouput
// read a char
// if not in search => pass through
// decide what to do
// switching over:
// case either 'delete' switched on or find matching char in 'replace'
// case 'squeeze' switched on
//
}

#[cfg(test)]
mod tests {
    use super::Translation;
    use super::OK;
    use std::cell::Cell;

    #[test]
    fn append_replace_when_it_is_short() {
        let mut tr = Translation {search: "abcde".to_string() , replace: "xyz".to_string(), complement: false, delete: false, squeeze: false, truncate: false, status: Cell::new(OK)};
        tr.append_or_truncate();
        assert_eq!("xyzzz", tr.replace);
    }
    #[test]
    fn append_replace_when_it_is_long() {
        let mut tr = Translation {search: "a".to_string() , replace: "xyz".to_string(), complement: false, delete: false, squeeze: false, truncate: false, status: Cell::new(OK)};
        tr.append_or_truncate();
        assert_eq!("x", tr.replace);
    }
    #[test]
    fn append_replace_when_it_is_exact_in_length() {
        let mut tr = Translation {search: "abc".to_string() , replace: "xyz".to_string(), complement: false, delete: false, squeeze: false, truncate: false, status: Cell::new(OK)};
        tr.append_or_truncate();
        assert_eq!("xyz", tr.replace);
    }
    #[test]
    fn make_and_check_map() {
        let mut tr = Translation {search: "abc".to_string() , replace: "xyz".to_string(), complement: false, delete: false, squeeze: false, truncate: false, status: Cell::new(OK)};
        // precondition is that append_or_truncate is called so search and replace have the same length
        let map = tr.make_map();
        assert_eq!(3, map.len());
        assert_eq!(3, map.keys().count());
        assert_eq!(3, map.values().count());
        assert_eq!(Some(&'x'), map.get(&'a'));
        assert_eq!(Some(&'y'), map.get(&'b'));
        assert_eq!(Some(&'z'), map.get(&'c'));
        assert_eq!(None, map.get(&'d'));
    }
    #[test]
    fn make_and_check_maptranslate() {
        let mut tr = Translation {search: "abc".to_string() , replace: "xyz".to_string(), complement: false, delete: false, squeeze: false, truncate: false, status: Cell::new(OK)};
        // precondition is that append_or_truncate is called so search and replace have the same length
        let map = tr.make_map();
        assert_eq!(3, map.len());
        assert_eq!(3, map.keys().count());
        assert_eq!(3, map.values().count());
        assert_eq!(Some(&'x'), map.get(&'a'));
        assert_eq!(Some(&'y'), map.get(&'b'));
        assert_eq!(Some(&'z'), map.get(&'c'));
        assert_eq!(None, map.get(&'d'));
    }
}
<commit_msg>Fix some tr bahaviour according to flags (#107)<commit_after>#![deny(warnings)]

extern crate coreutils;
extern crate extra;

use std::cell::Cell;
use std::collections::HashMap;
use std::env;
use std::io::{self, Stdout, Stderr,  BufRead, Read, Write};

use coreutils::ArgParser;
use extra::io::{fail, WriteExt};

static OK: i32                      = 0;
static INVALID_FLAG: i32            = 1;
static SEARCH_CANNOT_BE_EMPTY: i32  = 2;
static REPLACE_CANNOT_BE_EMPTY: i32 = 3;
static WRITE_ERROR: i32             = 4;
static DUMMY_RUN: i32               = 5;

static USAGE: &'static str = r#"usage: tr -c string1  string2
    tr -d string1
    tr -s string1 string2
"#;

const MAN_PAGE: &'static str = /* @MANSTART{tr} */ r#"NAME
    tr - translate characters

SYNOPSIS
    tr [ -cdst ] [ string1 [ string2 ] ]

DESCRIPTION
    Tr copies the standard input to the standard output with
    substitution or deletion of selected characters.  Input
    characters found in string1 are mapped into the correspond-
    ing characters of string2. When string2 is short it is pad-
    ded to the length of string1 by duplicating its last charac-
    ter.  Any combination of the options -cds may be used:

    --complement
    -c   complement the set of characters in string1 with
         respect to the universe of characters whose ASCII codes
         are 01 through 0377

    --delete
    -d   delete all input characters in string1

    --squeeze
    -s   squeeze all strings of repeated output characters that
         are in string2 to single characters

    --truncate
    -t   first truncate string1 to length of string2

    In either string the notation a-b means a range of charac-
    ters from a to b in increasing ASCII order.  The character
    `\' followed by 1, 2 or 3 octal digits stands for the char-
    acter whose ASCII code is given by those digits.  A `\' fol-
    lowed by any other character stands for that character.

    The following example creates a list of all the words in
    `file1' one per line in `file2', where a word is taken to be
    a maximal string of alphabetics.  The second string is
    quoted to protect `\' from the Shell.  012 is the ASCII code
    for newline.

        tr -cs A-Za-z '\012' <file1 >file2

SEE ALSO
    ed(1), ascii(7)

BUGS
    loads.
"#; /* @MANEND */

struct Translation {
    complement:  bool,
    delete:      bool,
    squeeze:     bool,
    truncate:    bool,
    search:      String,
    replace:     String,
    status:      Cell<i32>
}

impl Translation {

    fn print_opts(&self) {
        println!("flags\ncompliment:\t{}\ndelete:\t{}\nsqueeze:\t{}\ntruncate:\t{}", self.complement, self.delete, self.squeeze, self.truncate);
        println!("search: {}", self.search);
        println!("replace: {}", self.replace);
    }

    fn truncate(&mut self, input: String, length: usize) -> String {
        // use an iterator just in case we have diacretes or other complex chars
        let mut new_string = "".to_string();
        {
            let mut char_walker = input.chars();
            for _ in 0 .. length {
                let last_char = char_walker.next().unwrap();
                new_string.push(last_char);
            }
        }
        return new_string;
    }

    fn append_or_truncate(&mut self) -> &mut Translation {
        // first decide
        let search_length = self.search.chars().count();
        let replace_length = self.replace.chars().count();

        if replace_length > 0 && replace_length < search_length {
            //build adjust search or replace?
            if self.truncate {
                let old_value = self.search.clone();
                self.search = self.truncate(old_value, replace_length);
            } else {
                // fill replace with it's last char to match search in length
                let lastchar_as_string = self.replace.chars().last().unwrap();
                for _ in replace_length .. search_length {
                    self.replace.push(lastchar_as_string); // do something
                }
            }
        } else if replace_length > search_length {
            // truncate replaces length to search'
            let old_value = self.replace.clone();
            self.replace = self.truncate(old_value, search_length);
        }
        return self;
    }

    fn make_map(&mut self) -> HashMap<char, char> {
        // prereq is that search and replace are now the same length
        return self.search.chars().zip(self.replace.chars()).collect();
    }

    fn get_opts(&mut self, stdout: &mut Stdout, mut stderr: &mut Stderr) -> &mut Translation {
        let mut parser = ArgParser::new(2)
            .add_flag("c", "complement")
            .add_flag("d", "delete")
            .add_flag("s", "squeeze")
            .add_flag("t", "truncate")
            .add_flag("h", "help");
        parser.parse(env::args());
        if let Err(err) = parser.found_invalid() {
            let _ = stderr.write(err.as_bytes());
            self.status.set(INVALID_FLAG);
        } else {
            if parser.found(&'h') || parser.found("help") {
                self.status.set(DUMMY_RUN);
            }
            self.complement = parser.found(&'c') || parser.found("complement");
            self.delete = parser.found(&'d') || parser.found("delete");
            self.squeeze = parser.found(&'s') || parser.found("squeeze");
            self.truncate = parser.found(&'t') || parser.found("truncate");

            let mut iter = parser.args.iter();
            let mut next = iter.next();
            if next.is_some() {
                self.search = next.unwrap().clone();
                next = iter.next();
                if next.is_some() {
                    self.replace = next.unwrap().clone();
                }
            }
            if self.status.get() > OK {
                let _ = stdout.write(MAN_PAGE.as_bytes());
                self.print_opts();
            }
        }
        return self;
    }

    fn check_opts(&mut self) -> &mut Translation {
        if !self.delete && !self.squeeze && self.replace.len() == 0 {
            // big issue
            println!("replace string can not be empty when neither -s nor -d is specified.");
            self.status.set(REPLACE_CANNOT_BE_EMPTY);
        }
        if self.search.len() == 0 {
            println!("set of characters to replace is obligatory");
            self.status.set(SEARCH_CANNOT_BE_EMPTY);
        }
        if self.status.get() > OK {
            println!("{}", MAN_PAGE);
        }
        return self;
    }

    fn remove<R: Read, W: Write>(& mut self, input: R, output: W,) {
        // do the work
        let reader = io::BufReader::new(input);
        let mut writer = io::BufWriter::new(output);

        for line in reader.lines() {
            let line = line.unwrap();
            let chars = line.chars();
            // read a char
            for kar in chars {
                // if not in search => pass through
                if self.search.find(kar).is_none() {
                    if writer.write_char(kar).is_err() {
                        self.status.set(WRITE_ERROR);
                        return;
                    }
                }
            }

            if writer.write_char('\n').is_err() {
                self.status.set(WRITE_ERROR);
                return;
            }
        }
    }

    fn translate<R: Read, W: Write>(& mut self, input: R, output: W,) {
        // do the work
        let map = self.make_map();
        let reader = io::BufReader::new(input);
        let mut writer = io::BufWriter::new(output);

        for line in reader.lines() {
            let line = line.unwrap();
            let chars = line.chars();
            // read a char
            for kar in chars {
                // if not in search => pass through
                let mut output = kar;
                if map.get(&kar).is_some() {
                    output = *map.get(&kar).unwrap();
                }
                if writer.write_char(output).is_err() {
                    self.status.set(WRITE_ERROR);
                    return;
                }
            }
            if writer.write_char('\n').is_err() {
                self.status.set(WRITE_ERROR);
                return;
            }
        }
    }
}

fn main() {
    let stdin = io::stdin();
    let mut stdout = io::stdout();
    let mut stderr = io::stderr();
    let mut tr = Translation { complement: false, delete: false, squeeze: false, truncate: false, search: String::new(), replace: String::new(), status: Cell::new(OK)};
    tr.get_opts(&mut stdout,&mut stderr);
    if tr.status.get() == OK {
        tr.check_opts();
    }
    if tr.status.get() == OK {
        tr.append_or_truncate();
    }
    if tr.status.get() > OK {
        fail(USAGE, &mut stderr);
    }

    // if complement is turned on recreate 'search' to contain the complement of search
    if !tr.delete {
        tr.translate(stdin, &mut stdout);
    } else {
        tr.remove(stdin, &mut stdout);
    }
// decide what to do
// switching over:
// case 'squeeze' switched on
//
}

#[cfg(test)]
mod tests {
    use super::Translation;
    use super::OK;
    use std::cell::Cell;

    #[test]
    fn append_replace_when_it_is_short() {
        let mut tr = Translation {search: "abcde".to_string() , replace: "xyz".to_string(), complement: false, delete: false, squeeze: false, truncate: false, status: Cell::new(OK)};
        tr.append_or_truncate();
        assert_eq!("xyzzz", tr.replace);
    }
    #[test]
    fn append_replace_when_it_is_long() {
        let mut tr = Translation {search: "a".to_string() , replace: "xyz".to_string(), complement: false, delete: false, squeeze: false, truncate: false, status: Cell::new(OK)};
        tr.append_or_truncate();
        assert_eq!("x", tr.replace);
    }
    #[test]
    fn append_replace_when_it_is_exact_in_length() {
        let mut tr = Translation {search: "abc".to_string() , replace: "xyz".to_string(), complement: false, delete: false, squeeze: false, truncate: false, status: Cell::new(OK)};
        tr.append_or_truncate();
        assert_eq!("xyz", tr.replace);
    }
    #[test]
    fn make_and_check_map() {
        let mut tr = Translation {search: "abc".to_string() , replace: "xyz".to_string(), complement: false, delete: false, squeeze: false, truncate: false, status: Cell::new(OK)};
        // precondition is that append_or_truncate is called so search and replace have the same length
        let map = tr.make_map();
        assert_eq!(3, map.len());
        assert_eq!(3, map.keys().count());
        assert_eq!(3, map.values().count());
        assert_eq!(Some(&'x'), map.get(&'a'));
        assert_eq!(Some(&'y'), map.get(&'b'));
        assert_eq!(Some(&'z'), map.get(&'c'));
        assert_eq!(None, map.get(&'d'));
    }
    #[test]
    fn make_and_check_maptranslate() {
        let mut tr = Translation {search: "abc".to_string() , replace: "xyz".to_string(), complement: false, delete: false, squeeze: false, truncate: false, status: Cell::new(OK)};
        // precondition is that append_or_truncate is called so search and replace have the same length
        let map = tr.make_map();
        assert_eq!(3, map.len());
        assert_eq!(3, map.keys().count());
        assert_eq!(3, map.values().count());
        assert_eq!(Some(&'x'), map.get(&'a'));
        assert_eq!(Some(&'y'), map.get(&'b'));
        assert_eq!(Some(&'z'), map.get(&'c'));
        assert_eq!(None, map.get(&'d'));
    }
}
<|endoftext|>


<commit_before>use std::cell::Cell;

pub struct SpiderRobot {
    species: String,
    web_enabled: bool,
    leg_devices: [fd::FileDesc, 8],
    hardware_error_count: Cell<u32>,
}

impl SpiderRobot {
    // Increase the error count by 1
    pub fn add_hardware_error(&self) {
        let n = self.hardware_error_count.get();
        self.hardware_error_count.set(n+1);
    }

    pub fn has_hardware_errors(&self) {
        self.hardware_error_count.get() > 0
    }
}

fn main() {
    SpiderRobot { species, web_enabled, leg_devices, hardware_error_count }
}
<commit_msg>very smart comment about Cell<commit_after>use std::cell::Cell;

// I think I can think of Cell as getter/setter pair
// You can not get the ref value from Cell. It gives you copy of value
// you mutate this value somewhere in your program and set it back with setter.
pub struct SpiderRobot {
    species: String,
    web_enabled: bool,
    leg_devices: [fd::FileDesc, 8],
    hardware_error_count: Cell<u32>,
}

impl SpiderRobot {
    // Increase the error count by 1
    pub fn add_hardware_error(&self) {
        let n = self.hardware_error_count.get();
        self.hardware_error_count.set(n+1);
    }

    pub fn has_hardware_errors(&self) {
        self.hardware_error_count.get() > 0
    }
}

fn main() {
    SpiderRobot { species, web_enabled, leg_devices, hardware_error_count }
}
<|endoftext|>


<commit_before>extern crate mime_guess;

use std::convert::TryInto;
use task_maker_format::ioi;
use turingarena::content::*;
use turingarena::evaluation::record::*;
use turingarena::feedback::{table::*, *};
use turingarena::problem::material::*;
use turingarena::score::*;
use turingarena::submission::form::*;

fn subtasks_of(task: &ioi::Task) -> Vec<&ioi::SubtaskInfo> {
    let mut subtasks: Vec<_> = task.subtasks.values().collect();
    subtasks.sort_by(|a, b| a.id.cmp(&b.id));
    subtasks
}

fn testcases_of(subtask: &ioi::SubtaskInfo) -> Vec<&ioi::TestcaseInfo> {
    let mut testcases: Vec<_> = subtask.testcases.values().collect();
    testcases.sort_by(|a, b| a.id.cmp(&b.id));
    testcases
}

fn submission_form() -> Form {
    Form {
        fields: vec![Field {
            id: FieldId("solution".into()),
            title: vec![TextVariant {
                attributes: vec![],
                value: "Solution".into(),
            }],
            types: vec![FileType {
                id: FileTypeId("cpp".into()),
                title: vec![TextVariant {
                    attributes: vec![],
                    value: "C++".into(),
                }],
                extensions: vec![
                    FileTypeExtension(".cpp".into()),
                    FileTypeExtension(".cc".into()),
                ],
                primary_extension: FileTypeExtension(".cpp".into()),
            }],
        }],
    }
}

fn scorable_of(subtask: &ioi::SubtaskInfo) -> Scorable {
    Scorable {
        name: ScorableName(format!("subtask.{}", subtask.id)),
        title: vec![TextVariant {
            attributes: vec![],
            value: format!("Subtask {}", subtask.id),
        }],
        range: Range {
            // TODO: assuming IOI-like tasks have integer scores
            precision: 0,
            max: Score(subtask.max_score),
        },
    }
}

fn cols() -> Vec<Col> {
    vec![
        Col {
            title: vec![TextVariant {
                attributes: vec![],
                value: format!("Case"),
            }],
            content: ColContent::RowNumber(RowNumberColContent {}),
        },
        Col {
            title: vec![TextVariant {
                attributes: vec![],
                value: format!("Score"),
            }],
            content: ColContent::Score(ScoreColContent {
                range: Range {
                    // FIXME: assuming per-test-case score has fixed precision
                    precision: 2,
                    max: Score(1.),
                },
            }),
        },
    ]
}

fn caption() -> Text {
    vec![TextVariant {
        attributes: vec![],
        value: format!("Test case results"),
    }]
}

fn row_group_of(subtask: &ioi::SubtaskInfo) -> RowGroup {
    RowGroup {
        title: vec![TextVariant {
            attributes: vec![],
            value: format!("Subtask {}", subtask.id),
        }],
        rows: testcases_of(subtask).into_iter().map(row_of).collect(),
    }
}

fn row_of(testcase: &ioi::TestcaseInfo) -> Row {
    Row {
        content: RowContent::Data,
        cells: vec![
            Cell {
                content: CellContent::RowNumber(RowNumberCellContent {
                    number: testcase.id.try_into().expect("Testcase ID too large"),
                }),
            },
            Cell {
                content: CellContent::Score(ScoreCellContent {
                    range: Range {
                        precision: 2,
                        max: Score(1.),
                    },
                    r#ref: Key(format!("testcase.{}.score", testcase.id)),
                }),
            },
        ],
    }
}

fn files_in_dir(dir_path: &std::path::PathBuf) -> impl Iterator<Item = std::path::PathBuf> {
    std::fs::read_dir(dir_path)
        .expect("unable to read_dir")
        .map(|entry| entry.expect("unable to read_dir").path())
}

fn attachment_at_path(file_path: std::path::PathBuf) -> Attachment {
    Attachment {
        title: vec![TextVariant {
            attributes: vec![],
            value: file_path
                .file_name()
                .unwrap()
                .to_string_lossy()
                .into_owned(),
        }],
        file: vec![FileVariant {
            attributes: vec![],
            name: Some(FileName(
                file_path
                    .file_name()
                    .unwrap()
                    .to_string_lossy()
                    .into_owned(),
            )),
            r#type: mime_guess::from_path(&file_path)
                .first_raw()
                .map(|t| MediaType(t.to_owned())),
            content: FileContent(std::fs::read(&file_path.to_string_lossy().as_ref()).unwrap()),
        }],
    }
}

fn statement_of(booklet: &ioi::Booklet) -> FileVariant {
    FileVariant {
        attributes: vec![VariantAttribute {
            key: "language".to_owned(),
            value: booklet.config().language.to_owned(),
        }],
        name: Some(FileName(
            booklet
                .dest()
                .file_name()
                .unwrap()
                .to_string_lossy()
                .into_owned(),
        )),
        r#type: Some(MediaType("application/pdf".to_owned())),
        content: FileContent(std::fs::read(booklet.dest()).expect("Unable to read statement file")),
    }
}

pub fn gen_material(task: &ioi::Task) -> Material {
    Material {
        title: vec![TextVariant {
            attributes: vec![],
            value: task.name.clone().into(),
        }],
        statement: task.booklets.iter().map(statement_of).collect(),
        attachments: files_in_dir(&task.path.join("att"))
            .map(attachment_at_path)
            .collect(),
        submission_form: submission_form(),
        scorables: { subtasks_of(task).into_iter().map(scorable_of).collect() },
        feedback: vec![Section::Table(TableSection {
            caption: caption(),
            cols: cols(),
            row_groups: subtasks_of(task).into_iter().map(row_group_of).collect(),
        })],
    }
}
<commit_msg>task-maker: use task title, not name, as title<commit_after>extern crate mime_guess;

use std::convert::TryInto;
use task_maker_format::ioi;
use turingarena::content::*;
use turingarena::evaluation::record::*;
use turingarena::feedback::{table::*, *};
use turingarena::problem::material::*;
use turingarena::score::*;
use turingarena::submission::form::*;

fn subtasks_of(task: &ioi::Task) -> Vec<&ioi::SubtaskInfo> {
    let mut subtasks: Vec<_> = task.subtasks.values().collect();
    subtasks.sort_by(|a, b| a.id.cmp(&b.id));
    subtasks
}

fn testcases_of(subtask: &ioi::SubtaskInfo) -> Vec<&ioi::TestcaseInfo> {
    let mut testcases: Vec<_> = subtask.testcases.values().collect();
    testcases.sort_by(|a, b| a.id.cmp(&b.id));
    testcases
}

fn submission_form() -> Form {
    Form {
        fields: vec![Field {
            id: FieldId("solution".into()),
            title: vec![TextVariant {
                attributes: vec![],
                value: "Solution".into(),
            }],
            types: vec![FileType {
                id: FileTypeId("cpp".into()),
                title: vec![TextVariant {
                    attributes: vec![],
                    value: "C++".into(),
                }],
                extensions: vec![
                    FileTypeExtension(".cpp".into()),
                    FileTypeExtension(".cc".into()),
                ],
                primary_extension: FileTypeExtension(".cpp".into()),
            }],
        }],
    }
}

fn scorable_of(subtask: &ioi::SubtaskInfo) -> Scorable {
    Scorable {
        name: ScorableName(format!("subtask.{}", subtask.id)),
        title: vec![TextVariant {
            attributes: vec![],
            value: format!("Subtask {}", subtask.id),
        }],
        range: Range {
            // TODO: assuming IOI-like tasks have integer scores
            precision: 0,
            max: Score(subtask.max_score),
        },
    }
}

fn cols() -> Vec<Col> {
    vec![
        Col {
            title: vec![TextVariant {
                attributes: vec![],
                value: format!("Case"),
            }],
            content: ColContent::RowNumber(RowNumberColContent {}),
        },
        Col {
            title: vec![TextVariant {
                attributes: vec![],
                value: format!("Score"),
            }],
            content: ColContent::Score(ScoreColContent {
                range: Range {
                    // FIXME: assuming per-test-case score has fixed precision
                    precision: 2,
                    max: Score(1.),
                },
            }),
        },
    ]
}

fn caption() -> Text {
    vec![TextVariant {
        attributes: vec![],
        value: format!("Test case results"),
    }]
}

fn row_group_of(subtask: &ioi::SubtaskInfo) -> RowGroup {
    RowGroup {
        title: vec![TextVariant {
            attributes: vec![],
            value: format!("Subtask {}", subtask.id),
        }],
        rows: testcases_of(subtask).into_iter().map(row_of).collect(),
    }
}

fn row_of(testcase: &ioi::TestcaseInfo) -> Row {
    Row {
        content: RowContent::Data,
        cells: vec![
            Cell {
                content: CellContent::RowNumber(RowNumberCellContent {
                    number: testcase.id.try_into().expect("Testcase ID too large"),
                }),
            },
            Cell {
                content: CellContent::Score(ScoreCellContent {
                    range: Range {
                        precision: 2,
                        max: Score(1.),
                    },
                    r#ref: Key(format!("testcase.{}.score", testcase.id)),
                }),
            },
        ],
    }
}

fn files_in_dir(dir_path: &std::path::PathBuf) -> impl Iterator<Item = std::path::PathBuf> {
    std::fs::read_dir(dir_path)
        .expect("unable to read_dir")
        .map(|entry| entry.expect("unable to read_dir").path())
}

fn attachment_at_path(file_path: std::path::PathBuf) -> Attachment {
    Attachment {
        title: vec![TextVariant {
            attributes: vec![],
            value: file_path
                .file_name()
                .unwrap()
                .to_string_lossy()
                .into_owned(),
        }],
        file: vec![FileVariant {
            attributes: vec![],
            name: Some(FileName(
                file_path
                    .file_name()
                    .unwrap()
                    .to_string_lossy()
                    .into_owned(),
            )),
            r#type: mime_guess::from_path(&file_path)
                .first_raw()
                .map(|t| MediaType(t.to_owned())),
            content: FileContent(std::fs::read(&file_path.to_string_lossy().as_ref()).unwrap()),
        }],
    }
}

fn statement_of(booklet: &ioi::Booklet) -> FileVariant {
    FileVariant {
        attributes: vec![VariantAttribute {
            key: "language".to_owned(),
            value: booklet.config().language.to_owned(),
        }],
        name: Some(FileName(
            booklet
                .dest()
                .file_name()
                .unwrap()
                .to_string_lossy()
                .into_owned(),
        )),
        r#type: Some(MediaType("application/pdf".to_owned())),
        content: FileContent(std::fs::read(booklet.dest()).expect("Unable to read statement file")),
    }
}

pub fn gen_material(task: &ioi::Task) -> Material {
    Material {
        title: vec![TextVariant {
            attributes: vec![],
            value: task.title.clone().into(),
        }],
        statement: task.booklets.iter().map(statement_of).collect(),
        attachments: files_in_dir(&task.path.join("att"))
            .map(attachment_at_path)
            .collect(),
        submission_form: submission_form(),
        scorables: { subtasks_of(task).into_iter().map(scorable_of).collect() },
        feedback: vec![Section::Table(TableSection {
            caption: caption(),
            cols: cols(),
            row_groups: subtasks_of(task).into_iter().map(row_group_of).collect(),
        })],
    }
}
<|endoftext|>