Skip to main content

scratch_test_model/scopes/
block_wrapper.rs

1use crate::{
2    Id,
3    aux::{JsonBlocks, JsonCtx, WithJsonContextExt},
4    blocks::BlockKind,
5    error::BlockError,
6};
7
8/// This wraps a Scratch [block](https://en.scratch-wiki.info/wiki/Scratch_File_Format#Blocks)
9///
10/// It stores the unique [`Id`] of the block Scratch has assigned to it,
11/// the [`Id`] of the `next` block (especially important for statements)
12/// and the `parent` block (`parent` is not actively used in the application).
13///
14/// **parent** is not the defined to be the previous block
15/// (next and parent don't form a double-linked-list)
16/// but it can be the next-outer-block in the hierarchy.
17/// [See here for details](https://en.scratch-wiki.info/wiki/Scratch_File_Format#Blocks)
18#[derive(Debug, derive_getters::Getters, PartialEq)]
19pub struct BlockWrapper {
20    /// The unique id of this block, alphanumeric and special characters are used
21    id: Id,
22    /// The [`BlockKind`] contains specific information for this kind of block
23    inner: BlockKind,
24    /// An optional id to the next block following this
25    next: Option<Id>,
26    /// `parent` is not actively used in the application
27    ///
28    /// **parent** is not the defined to be the previous block
29    /// (next and parent don't form a double-linked-list)
30    /// but it can be the next-outer-block in the hierarchy.
31    /// [See here for details](https://en.scratch-wiki.info/wiki/Scratch_File_Format#Blocks)
32    parent: Option<Id>,
33    /// not needed for the application, but it is present in the file so it's parsed
34    shadow: bool,
35    /// `topLevel`/`x`/`y` is the position of blocks, if they are top-level
36    ///
37    /// If and (as it seems) only if the property `topLevel` is set to `true`,
38    /// the two properties `x` and `y` are also present.
39    ///
40    /// They then form the position of the block on a grid.
41    top_level_pos: Option<Position>,
42}
43
44#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, derive_getters::Getters)]
45pub struct Position {
46    x: i64,
47    y: i64,
48}
49
50impl BlockWrapper {
51    pub(crate) fn from_json<'a>(
52        all_blocks: JsonBlocks<'a>,
53        id: Id,
54        obj: &'a serde_json::Value,
55    ) -> Result<Self, JsonCtx<'a, BlockError<'a>>> {
56        let next = read_maybe_string(obj, "next").with_ctx(obj)?.map(Id::from);
57        let parent = read_maybe_string(obj, "parent")
58            .with_ctx(obj)?
59            .map(Id::from);
60        let shadow = read_bool(obj, "shadow").with_ctx(obj)?;
61
62        let top_level_pos = {
63            let top_level = read_bool(obj, "topLevel").with_ctx(obj)?;
64            if top_level {
65                let x = read_i64(obj, "x").with_ctx(obj)?;
66                let y = read_i64(obj, "y").with_ctx(obj)?;
67                Some(Position { x, y })
68            } else {
69                None
70            }
71        };
72
73        let inner =
74            BlockKind::from_json(all_blocks, &id).map_err(|ctx| ctx.map_err(BlockError::Kind))?;
75
76        Ok(Self {
77            id,
78            inner,
79            next,
80            parent,
81            shadow,
82            top_level_pos,
83        })
84    }
85}
86
87fn read_maybe_string<'a>(
88    obj: &'a serde_json::Value,
89    key: &'static str,
90) -> Result<Option<&'a str>, BlockError<'a>> {
91    Ok(obj
92        .get(key)
93        .ok_or(BlockError::MissingMandatoryAttr(key))?
94        .as_str())
95}
96
97fn read_bool<'a>(obj: &serde_json::Value, key: &'static str) -> Result<bool, BlockError<'a>> {
98    obj.get(key)
99        .ok_or(BlockError::MissingMandatoryAttr(key))?
100        .as_bool()
101        .ok_or(BlockError::AttrType {
102            attr_name: key,
103            expected_type: "bool",
104        })
105}
106
107fn read_i64<'a>(obj: &'a serde_json::Value, key: &'static str) -> Result<i64, BlockError<'a>> {
108    obj.get(key)
109        .ok_or(BlockError::MissingMandatoryAttr(key))?
110        .as_i64()
111        .ok_or(BlockError::AttrType {
112            attr_name: key,
113            expected_type: "i64",
114        })
115}