From 90bd6c8afbe909d9cbd5eba38769179020999d7d Mon Sep 17 00:00:00 2001 From: Modar Nasser Date: Tue, 18 Jun 2024 22:13:23 +0200 Subject: [PATCH] Add API reference documentation in doc folder Add custom script that generates markdown wiki from yaml documentation --- doc/BgImage.yml | 21 ++ doc/DataTypes.yml | 209 ++++++++++++++ doc/Entity.yml | 113 ++++++++ doc/Enum.yml | 70 +++++ doc/Fields.yml | 26 ++ doc/FieldsContainer.yml | 97 +++++++ doc/Layer.yml | 107 +++++++ doc/Level.yml | 67 +++++ doc/Project.yml | 122 ++++++++ doc/TagsContainer.yml | 13 + doc/Tileset.yml | 65 +++++ doc/World.yml | 86 ++++++ tools/generate_wiki.py | 609 ++++++++++++++++++++++++++++++++++++++++ 13 files changed, 1605 insertions(+) create mode 100644 doc/BgImage.yml create mode 100644 doc/DataTypes.yml create mode 100644 doc/Entity.yml create mode 100644 doc/Enum.yml create mode 100644 doc/Fields.yml create mode 100644 doc/FieldsContainer.yml create mode 100644 doc/Layer.yml create mode 100644 doc/Level.yml create mode 100644 doc/Project.yml create mode 100644 doc/TagsContainer.yml create mode 100644 doc/Tileset.yml create mode 100644 doc/World.yml create mode 100644 tools/generate_wiki.py diff --git a/doc/BgImage.yml b/doc/BgImage.yml new file mode 100644 index 0000000..6516b43 --- /dev/null +++ b/doc/BgImage.yml @@ -0,0 +1,21 @@ +title: BgImage +filename: LDtkLoader/Level.hpp +structs: + - name: BgImage + desc: Contains information about the Level background image. + fields: + path: + decl: ldtk::FilePath ldtk::Level::BgImage::path + desc: The relative path to the background image file. + + crop: + decl: ldtk::IntRect ldtk::Level::BgImage::crop + desc: The cropped sub rectangle of the displayed image. + + pos: + decl: ldtk::IntPoint ldtk::Level::BgImage::pos + desc: The position of the top left corner of the background image in the level. + + scale: + decl: ldtk::FloatPoint ldtk::Level::BgImage::scale + desc: The scale of the background image. \ No newline at end of file diff --git a/doc/DataTypes.yml b/doc/DataTypes.yml new file mode 100644 index 0000000..97ff28b --- /dev/null +++ b/doc/DataTypes.yml @@ -0,0 +1,209 @@ +title: DataTypes +filename: LDtkLoader/DataTypes.hpp +classes: + - name: FilePath + desc: A string that contains file path information. + ctor: + - decl: ldtk::FilePath::FilePath() + desc: Default constructor. Constructs an empty FilePath. + - decl: ldtk::FilePath::FilePath(const std::string&) + desc: Construct a FilePath from a valid provided string. + methods: + directory: + - decl: ldtk::FilePath::directory() const -> std::string + desc: Returns the path to the directory containing the file. + + filename: + - decl: ldtk::FilePath::filename() const -> std::string + desc: Returns the filename (including the extension) of the FilePath. + + extension: + - decl: ldtk::FilePath::extension() const -> std::string + desc: Returns only the extension of the file (e.g. png, json, txt ...). + +structs: + - name: Point + desc: | + Represents a two dimentional coordinate. + + `T` can be one of the following types: `float`, `int` or `unsigned int`. + + Aliases: + * `ldtk::FloatPoint` = `ldtk::Point` + * `ldtk::IntPoint` = `ldtk::Point` + * `ldtk::UIntPoint` = `ldtk::Point` + fields: + x: + decl: T ldtk::Point::x + desc: X coordinate of the Point. + y: + decl: T ldtk::Point::y + desc: Y coordinate of the Point. + + - name: Rect + desc: | + Represents an axis aligned rectangle. + + `T` can be one of the following types: `float` or `int`. + + Aliases: + * `ldtk::FloatRect` = `ldtk::Rect` + * `ldtk::IntRect` = `ldtk::Rect` + fields: + x: + decl: T ldtk::Rect::x + desc: X coordinate of the top left corner of the Rect. + y: + decl: T ldtk::Rect::y + desc: Y coordinate of the top left corner of the Rect. + width: + decl: T ldtk::Rect::width + desc: Width of the Rect. + height: + decl: T ldtk::Rect::height + desc: Height of the Rect. + + - name: Color + desc: Represents a color value in the 32 bits RGBA format. + fields: + r: + decl: std::uint8_t ldtk::Color::r + desc: Red component of the Color. + g: + decl: std::uint8_t ldtk::Color::g + desc: Green component of the Color. + b: + decl: std::uint8_t ldtk::Color::n + desc: Blue component of the Color. + a: + decl: std::uint8_t ldtk::Color::a + desc: Alpha component of the Color (opacity). + + - name: EntityRef + desc: Represents a reference to an Entity. + methods: + operator->: + - decl: ldtk::EntityRef::operator->() const -> const ldtk::Entity* + desc: | + Returns a **pointer** to the Entity referenced by the EntityRef. + + This operator allows to access the Entity reference like this: + + ```c++ + entity_ref->getName(); // returns the name of the referenced Entity + ``` + + - name: NineSliceBorders + desc: Represents the dimensions of the 9-slice tile render. + fields: + top: + decl: int ldtk::NineSliceBorders::top + desc: "" + right: + decl: int ldtk::NineSliceBorders::right + desc: "" + bottom: + decl: int ldtk::NineSliceBorders::bottom + desc: "" + left: + decl: int ldtk::NineSliceBorders::left + desc: "" + + - name: IntGridValue + desc: Represents the grid values that can be painted on an IntGrid layer. + fields: + value: + decl: const int ldtk::IntGridValue::value + desc: "" + name: + decl: const std::string ldtk::IntGridValue::name + desc: "" + color: + decl: const ldtk::Color ldtk::IntGridValue::color + desc: "" + + - name: Vertex + desc: | + Represents a vertex composed of a position and a texture coordinate. + + The graphic representation of a Tile is composed of 4 vertices. + fields: + pos: + decl: const ldtk::FloatPoint ldtk::Vertex::pos + desc: Coordinate of the vertex in pixels, relative to the Level. + tex: + decl: const ldtk::IntPoint ldtk::Vertex::tex + desc: Texture coordinate of the vertex, in pixels. + +enums: + - name: WorldLayout + desc: Contains enum values representing all the possible types of World layouts. + values: + Free: 0 + GridVania: 1 + LinearHorizontal: 2 + LinearVertical: 3 + + - name: LayerType + desc: Contains enum values representing all the possible types of Layers. + values: + IntGrid: 0 + Entities: 1 + Tiles: 2 + AutoLayer: 3 + + - name: FieldType + desc: | + Contains enum values representing all the possible types of a Field. + + These enum values can be used to get a field from an Entity. For example: + + ```c++ + const auto& field_value = entity.getField(); + // The type of field_value is ldtk::ArrayField + ``` + values: + Int: 0 + Float: 1 + Bool: 2 + String: 3 + Color: 4 + Point: 5 + Enum: 6 + FilePath: 7 + EntityRef: 8 + ArrayInt: 9 + ArrayFloat: 10 + ArrayBool: 11 + ArrayString: 12 + ArrayColor: 13 + ArrayPoint: 14 + ArrayEnum: 15 + ArrayFilePath: 16 + ArrayEntityRef: 17 + + - name: Dir + desc: | + Contains enum values representing all the possible Directions of Level neighbours. + + It is used by `ldtk::Level::getNeighbours(const ldtk::Dir&)` and returned by + `ldtk::getNeighbourDirection(const Level&)`. + values: + None: 0 + North: 1 + NorthEast: 2 + East: 3 + SouthEast: 4 + South: 5 + SouthWest: 6 + West: 7 + NorthWest: 8 + +types: + - name: FileLoader + decl: using ldtk::FileLoader = std::function(const std::string&)> + desc: | + FileLoader is the type of a function that takes the path of a file as parameter and returns a + unique pointer to a standard stream buffer for that file. + + This allows to load a Project from a custom source stream (e.g. from a virtual filesystem). diff --git a/doc/Entity.yml b/doc/Entity.yml new file mode 100644 index 0000000..81f6120 --- /dev/null +++ b/doc/Entity.yml @@ -0,0 +1,113 @@ +title: Entity +filename: LDtkLoader/Entity.hpp +classes: + - name: Entity + desc: | + Represents an Entity instance. + + > [!TIP] + > To get the correct sprite data of the Entity as it is shown in the editor, + > you should use `hasSprite` to check if the Entity has a sprite, + > and then `getTexturePath` and `getTextureRect` to get the texture data. + + fields: + layer: + decl: const Layer* const layer + desc: Pointer to the Layer object that contains the Entity. + + iid: + decl: const std::string ldtk::Entity::iid + desc: Unique instance ID of the Entity. + + methods: + getName: + - decl: ldtk::Entity::getName() const -> const std::string& + desc: Returns the name of the Entity. + + getSize: + - decl: ldtk::Entity::getSize() const -> const ldtk::IntPoint& + desc: Returns the size in pixels of the Entity. + + getColor: + - decl: ldtk::Entity::getColor() const -> const ldtk::Color& + desc: Returns the color of the Entity. + + getPosition: + - decl: ldtk::Entity::getPosition() const -> const ldtk::IntPoint& + desc: | + Returns the position in pixels of the Entity relative to the parent Layer. + + If the layer has an offset different than (0, 0), the layer's total offset should be + added to get the position in pixels relative to the parent Level : + + ```c++ + auto entity_level_pos = entity.getPosition() + entity.layer->getOffset(); + ``` + + getGridPosition: + - decl: ldtk::Entity::getGridPosition() const -> const ldtk::IntPoint& + desc: Returns the position in grid coordinates of the Entity. + + getGridPosition: + - decl: ldtk::Entity::getGridPosition() const -> const ldtk::IntPoint& + desc: Returns the computed position in pixels of the Entity relative to the World. + + getPivot: + - decl: ldtk::Entity::getPivot() const -> const ldtk::FloatPoint& + desc: Returns the pivot of the Entity, a point from (0.f, 0.f) to (1.f, 1.f). + + hasSprite: + - decl: ldtk::Entity::hasSprite() const -> bool + desc: Returns true if the Entity has a sprite associated to it, returns false otherwise. + + getTexturePath: + - decl: ldtk::Entity::getTexturePath() const -> const std::string& + desc: Returns the path to the texture of the sprite. Returns an empty string if the Entity has no sprite. + + getTextureRect: + - decl: ldtk::Entity::getTextureRect() const -> const ldtk::IntRect& + desc: Returns the texture rectangle of this Entity's sprite. + + hasNineSlice: + - decl: ldtk::Entity::hasNineSlice() const -> bool + desc: Returns true if the Entity's sprite has a 9-slices scaling, returns false otherwise. + + getNineSliceBorders: + - decl: ldtk::Entity::getNineSliceBorders() const -> const ldtk::NineSliceBorders& + desc: Returns the Entity's 9-slices borders. See NineSliceBorders. + + hasTag: + - decl: ldtk::Entity::hasTag(const std::string&) const -> bool + desc: Returns `true` if the Entity has the given tag, returns `false` otherwise. + + allTags: + - decl: ldtk::Entity::allTags() const -> const std::vector& + desc: Returns a vector containing all tags of the Entity. + + getField: + - decl: | + template + ldtk::Entity::getField(const std::string& name) const -> const ldtk::Field& + desc: | + Returns the field matching the given name and type. Returned field can be null. + + `T` can either be a `ldtk::FieldType` value, or one of the following types: `int`, `float`, + `bool`, `std::string`, `ldtk::Color` , `ldtk::Point`, `ldtk::Enum`, `ldtk::FilePath`. + + For example, if your entity has a field of type color named "hair_color", you can either do : + + ```c++ + const auto& field = my_entity.getField("hair_color") + if (!field.is_null()) { + // do something with field.value() + } + ``` + + or + + ```c++ + const auto& field = my_entity.getField("hair_color") + if (!field.is_null()) { + // do something with field.value() + } + ``` diff --git a/doc/Enum.yml b/doc/Enum.yml new file mode 100644 index 0000000..0657167 --- /dev/null +++ b/doc/Enum.yml @@ -0,0 +1,70 @@ +title: Enum +filename: LDtkLoader/Enum.hpp +structs: + - name: Enum + parent: TagsContainer + desc: "" + fields: + name: + decl: const std::string ldtk::Enum::name + desc: Name of the Enum. + + uid: + decl: const int ldtk::Enum::uid + desc: Unique identifier of the Enum. + + methods: + operator[]: + - decl: | + ldtk::Enum::operator[](const std::string&) const -> const ldtk::EnumValue& + desc: | + Returns the EnumValue matching the provided name. + + If no EnumValue is found, an `invalid_argument` exception is thrown. + + hasIcons: + - decl: ldtk::Enum::hasIcons() const -> bool + desc: Returns true if the Enum has icons, returns false otherwise. + + getIconsTileset: + - decl: ldtk::Enum::getIconsTileset() const -> const ldtk::Tileset& + desc: Returns the Tileset used by this Enum's icons. + + - name: EnumValue + desc: "" + fields: + name: + decl: const std::string ldtk::EnumValue::name + desc: Name of the EnumValue. + + color: + decl: const ldtk::Color ldtk::EnumValue::color + desc: Color of the EnumValue. + + type: + decl: const ldtk::Enum& ldtk::EnumValue::type + desc: | + Enum object owning this EnumValue. + + Allows to access the name of the Enum, for example : + + ```c++ + const auto& enum_value = project.getEnum("Items")["SilverSword"]; + + std::cout << enum_value.type.name; + // output: Items + ``` + + methods: + hasIcon: + - decl: ldtk::EnumValue::hasIcon() const -> bool + desc: | + Returns true if the EnumValue has an icon, returns false otherwise. + + getIconTileset: + - decl: ldtk::EnumValue::getIconTileset() const -> const ldtk::Tileset& + desc: Returns the Tileset of the icon. + + getIconTextureRect: + - decl: ldtk::EnumValue::getIconTextureRect() const -> const ldtk::IntRect& + desc: Returns the texture rectangle of this EnumValue's icon. diff --git a/doc/Fields.yml b/doc/Fields.yml new file mode 100644 index 0000000..4c18ad9 --- /dev/null +++ b/doc/Fields.yml @@ -0,0 +1,26 @@ +title: Fields +filename: LDtkLoader/Field.hpp +structs: + - name: Field + desc: | + A Field holds a value of one of these types: `int`, `float`, `bool`, `std::string`, + `ldtk::Color`, `ldtk::IntPoint`, `ldtk::Enum` or `ldtk::FilePath`. + + Fields that are optional can be null. They hold the value `ldtk::null` in that case. + methods: + is_null: + - decl: constexpr ldtk::Field::is_null() const -> bool + desc: Returns `true` if the Field is optional and does not hold a value, returns `false` otherwise. + + value: + - decl: constexpr value() const -> const T& + desc: Returns the value of the field. + + value_or: + - decl: constexpr value_or(T&& value) const -> const T& + desc: If the Field holds a value, returns it; if the Field is null, returns the value provided as parameter. + + - name: ArrayField + desc: | + `ldtk::ArrayField` inherits from `std::vector>`, which means it is a vector + that contains `ldtk::Field` objects. Regular `std::vector` methods can be used. diff --git a/doc/FieldsContainer.yml b/doc/FieldsContainer.yml new file mode 100644 index 0000000..a31d95d --- /dev/null +++ b/doc/FieldsContainer.yml @@ -0,0 +1,97 @@ +title: FieldsContainer +filename: LDtkLoader/containers/FieldsContainer.hpp +classes: + - name: FieldsContainer + desc: | + Base class for objects that can have fields. + + Fields are pairs of (name, value) where the value can be of one of the following types: `int`, + `float`, `bool`, `std::string`, `ldtk::Color` , `ldtk::IntPoint`, `ldtk::Enum` or `ldtk::FilePath`. + + methods: + getField: + - decl: | + template + ldtk::FieldsContainer::getField(const std::string& name) const -> const ldtk::getFieldType& + desc: | + Returns the field matching the given name and type. Returned field can be null. + + `T` must be one of the values of the FieldType enum. + + This overload allows to get either single value fields or array fields: + + ```c++ + // get a single value field + const auto& field = entity.getField("hair_color"); + + if (!field.is_null()) { + // get the field value + const auto& hair_color = field.value(); + } + ``` + + ```c++ + // get an array field + const auto& array_field = level.getField("spawns"); + + // iterate on the array field + for (const auto& field : array_field) { + if (!field.is_null()) { + // get the field value + const auto& point = field.value(); + } + } + ``` + + - decl: | + template + ldtk::FieldsContainer::getField(const std::string& name) const -> const ldtk::Field& + desc: | + Returns the field matching the given name and type. Returned field can be null. + + `T` must be one of the following types : `int`, `float`, `bool`, `std::string`, `ldtk::Color`, + `ldtk::IntPoint`, `ldtk::Enum`, `ldtk::FilePath`. + + For example, if your FieldsContainer has a field of type Color named "color", you can write : + + ```c++ + const FieldsContainer& object = ...; // get the FieldsContainer + + // get the field + const auto& field = object.getField("color"); + + if (!field.is_null()) { + // get the field value + const auto& color = field.value(); + } + ``` + + getArrayField: + - decl: | + template + ldtk::FieldsContainer::getArrayField(const std::string& name) const -> const ldtk::ArrayField& + desc: | + Returns the array field matching the given name and type. + + `ldtk::ArrayField` is equivalent to `std::vector>` and can be iterated over like + a normal vector. Fields can be null. + + `T` must be one of the following types : `int`, `float`, `bool`, `std::string`, `ldtk::Color`, + `ldtk::IntPoint`, `ldtk::Enum`, `ldtk::FilePath`. + + For example, if your FieldsContainer has a field of type ArrayPoint named "spawns", you can write : + + ```c++ + const FieldsContainer& object = ...; // get the FieldsContainer + + // get the field + const auto& array_field = object.getArrayField("spawns"); + + // iterate on the array field + for (const auto& field : array_field) { + if (!field.is_null()) { + // get the field value + const auto& point = field.value(); + } + } + ``` diff --git a/doc/Layer.yml b/doc/Layer.yml new file mode 100644 index 0000000..8c9913e --- /dev/null +++ b/doc/Layer.yml @@ -0,0 +1,107 @@ +title: Layer +filename: LDtkLoader/Layer.hpp +classes: + - name: Layer + desc: A Layer is a part of a Level and can contain Tiles or Entities + fields: + level: + decl: const ldtk::Level* ldtk::Layer::level + desc: Pointer to the Level object that contains the Layer. + + iid: + decl: const std::string ldtk::Layer::iid + desc: Unique instance ID of the Layer. + + methods: + getType: + - decl: ldtk::Layer::getType() const -> const ldtk::LayerType& + desc: Returns the type of the Layer (IntGrid, Entities, Tiles or AutoLayer). + + getName: + - decl: ldtk::Layer::getName() const -> const std::string& + desc: Returns the name of the Layer. + + isVisible: + - decl: ldtk::Layer::isVisible() const -> bool + desc: Returns `true` if the Layer is visible in the editor, `false` otherwise. + + getCellSize: + - decl: ldtk::Layer::getCellSize() const -> int + desc: Returns the size of grid cells (cell_size x cell_size). + + getGridSize: + - decl: ldtk::Layer::getGridSize() const -> const ldtk::IntPoint& + desc: Returns the size of the grid in cells (the width and height of the level in cells number, not in pixel). + + getOffset: + - decl: ldtk::Layer::getOffset() const -> const ldtk::IntPoint& + desc: Returns Layer total offset in pixels. + + getOpacity: + - decl: ldtk::Layer::getOpacity() const -> float + desc: Returns Layer opacity. + + hasTileset: + - decl: ldtk::Layer::hasTileset() const -> bool + desc: Returns `true` if the Layer has a Tileset, `false` otherwise. + + getTileset: + - decl: ldtk::Layer::getTileset() const -> const ldtk::Tileset& + desc: | + Returns the Tileset used by the Layer. + + Throws an `invalid_argument` exception if Layer does not have a Tileset. + + allTiles: + - decl: ldtk::Layer::allTiles() const -> const std::vector& + desc: Returns a vector containing all the Tiles in the Layer, ordered correctly. + + getTile: + - decl: ldtk::Layer::getTile(int x, int y) const -> const Tile& + desc: | + Returns the Tile located at (x, y) grid position. + + Returns `ldtk::Tile::None` if there is none. + + getIntGridValue: + - decl: ldtk::Layer::getIntGridValue(int x, int y) const -> const IntGridValue& + desc: | + Returns the IntGrid value located at (x, y) grid position. + + Returns `ldtk::IntGridValue::None` if there is none. + + hasEntity: + - decl: ldtk::Layer::hasEntity(const std::string& name) const -> bool + desc: Returns `true` if Layer has an Entity with the provided name, returns `false` otherwise. + + allEntities: + - decl: ldtk::Layer::allEntities() const -> const std::vector& + desc: Returns a vector containing all Entities of the Layer. + + getEntitiesByName: + - decl: | + ldtk::Layer::getEntitiesByName(const std::string& name) const -> const std::vector>& + desc: | + Returns a vector containing reference wrappers of Entities named ``. + + Because the returned vector contains reference wrappers, using `auto` won't work like expected, you have to explicitly specify the type when iterating : + + ```c++ + for (const ldtk::Entity& door : layer.getEntitiesByName("Door")) { + // ... + } + ``` + + getEntitiesByTag: + - decl: | + ldtk::Layer::getEntitiesByTag(const std::string& tag) const -> const std::vector>& + desc: | + Returns a vector containing reference wrappers of Entities tagged with the provided tag name. + + Because the returned vector contains reference wrappers, using `auto` won't work like expected, you have to explicitly specify the type when iterating : + + ```c++ + for (const ldtk::Entity& actionable : layer.getEntitiesByTag("actionable")) { + // ... + } + ``` diff --git a/doc/Level.yml b/doc/Level.yml new file mode 100644 index 0000000..f84ebd4 --- /dev/null +++ b/doc/Level.yml @@ -0,0 +1,67 @@ +title: Level +filename: LDtkLoader/Level.hpp +classes: + - name: Level + desc: | + A Level represents a single tilemap in the World. + It contains one or multiple Layers. + parent: FieldsContainer + fields: + world: + decl: const ldtk::World* ldtk::Level::world + desc: Pointer to the World object that contains the Level. + + name: + decl: const std::string ldtk::Level::name + desc: Name of the Level. + + iid: + decl: const std::string ldtk::Level::iid + desc: Unique instance ID of the Level. + + uid: + decl: const int ldtk::Level::uid + desc: Unique identifier of the Level. + + size: + decl: const ldtk::IntPoint ldtk::Level::size + desc: Size of the Level in pixels. + + position: + decl: const ldtk::IntPoint ldtk::Level::position + desc: Position in pixels of the Level in the World. + + bg_color: + decl: const ldtk::Color ldtk::Level::bg_color + desc: Background color of the Level. + + methods: + allLayers: + - decl: ldtk::Level::allLayers() const -> const std::vector& + desc: Returns the vector containing all Layers of the Level. + + getLayer: + - decl: ldtk::Level::getLayer(const std::string& layer_name) const -> const ldtk::Layer& + desc: | + Returns the Layer matching the given name. + + If no Layer is found, an `invalid_argument` exception is thrown. + + hasBgImage: + - decl: ldtk::Level::hasBgImage() const -> bool + desc: Returns true if the Level has a background image, returns false otherwise. + + getBgImage: + - decl: ldtk::Level::getBgImage() const -> const ldtk::BgImage& + desc: Returns the background image data of the Level. See BgImage. + + getNeighbours: + - decl: ldtk::Level::getNeighbours(const Dir&) const -> const std::vector>& + desc: Returns a vector containing all the neighbour Levels placed at the given direction. + + getNeighbourDirection: + - decl: ldtk::Level::getNeighbourDirection(const ldtk::Level& level) const -> ldtk::Dir + desc: | + Get the direction of a neighbour Level. If the given Level is not a neighbour, returns `ldtk::Dir::None`. + + See Dir. diff --git a/doc/Project.yml b/doc/Project.yml new file mode 100644 index 0000000..9f2c6eb --- /dev/null +++ b/doc/Project.yml @@ -0,0 +1,122 @@ +title: Project +filename: LDtkLoader/Project.hpp +classes: + - name: Project + desc: | + A Project represents the whole LDtk project file and can contain one or multiple worlds. + ctor: + - decl: ldtk::Project::Project() + desc: Default constructor. + methods: + loadFromFile: + - decl: ldtk::Project::LoadFromFile(const std::string& filepath) + desc: | + Load a LDtk project from a given file path. + + After this method is executed, everything will be loaded in the Project object. + + Throws an exception in case of failure. + - decl: | + ldtk::Project::loadFromFile(const std::string& filepath, const FileLoader& file_loader) + desc: | + This overload allows loading a LDtk project using a custom stream. + + This allows for example, to load a file from a virtual filesystem. + + See FileLoader. + + loadFromMemory: + - decl: | + ldtk::Project::loadFromMemory(const std::vector& bytes) + ldtk::Project::loadFromMemory(const unsigned char* data, size_t size) + desc: | + Load a LDtk project from a string in memory. + + getFilePath: + - decl: ldtk::Project::getFilePath() const -> const ldtk::FilePath& + desc: | + Returns the path of the file from which the Project was loaded. + + getDefaultPivot: + - decl: ldtk::Project::getDefaultPivot() const -> const ldtk::FloatPoint& + desc: | + Returns the default pivot, a point from (0.f, 0.f) to (1.f, 1.f). + + getDefaultCellSize: + - decl: ldtk::Project::getDefaultCellSize() const -> int + desc: | + Returns the default size of a cell in the Project's world grid. + + Cell dimension is `(size, size)`. + + getBgColor: + - decl: ldtk::Project::getBgColor() const -> const ldtk::Color& + desc: Returns the default background color of the Project. + + allTilesets: + - decl: ldtk::Project::allTilesets() const -> const std::vector& + desc: Returns a vector containing all the Tilesets of the Project. + + getTileset: + - decl: | + ldtk::Project::getTileset(int id) const -> const ldtk::Tileset& + ldtk::Project::getTileset(const std::string& name) const -> const ldtk::Tileset& + desc: | + Returns the Tileset matching the given `id` or `name`. + + If no Tileset is found, an `invalid_argument` exception is thrown. + + allWorlds: + - decl: ldtk::Project::allWorlds() const -> const std::vector& + desc: Returns a vector containing all the Worlds of the Project. + + getWorld: + - decl: ldtk::Project::getWorld() const -> const ldtk::Project& + desc: | + For Projects with only one world, returns the World. + + If the project has multiple worlds, an `invalid_argument` exception is thrown. + + - decl: ldtk::Project::getWorld(const std::string& name) const -> const ldtk::Project& + desc: | + For projects with multi-worlds enabled, returns the World matching the given `name`. + + If no World is found, an `invalid_argument` exception is thrown. + + getEnum: + - decl: | + ldtk::Project::getEnum(int id) const -> const ldtk::Enum& + ldtk::Project::getEnum(const std::string& name) const -> const ldtk::Enum& + desc: | + Returns the Enum matching the given `id` or `name`. + + If no Enum is found, an `invalid_argument` exception is thrown. + + getLayerDef: + - decl: | + ldtk::Project::getLayerDef(int id) const -> const ldtk::LayerDef& + ldtk::Project::getLayerDef(const std::string& name) const -> const ldtk::LayerDef& + desc: | + Returns the LayerDef matching the given `id` or `name`. + + If no LayerDef is found, an `invalid_argument` exception is thrown. + + getEntityDef: + - decl: | + ldtk::Project::getEntityDef(int id) const -> const ldtk::EntityDef& + ldtk::Project::getEntityDef(const std::string& name) const -> const ldtk::EntityDef& + desc: | + Returns the EntityDef matching the given `id` or `name`. + + If no EntityDef is found, an `invalid_argument` exception is thrown. + + allTocEntities: + - decl: ldtk::Project::allTocEntities() const -> const std::vector& + desc: | + Returns a vector containing the EntityRefs that are located in the ToC. + + getTocEntitiesByName: + - decl: | + ldtk::Project::getTocEntitiesByName(const std::string& name) const -> const std::vector& + desc: | + Returns a vector containing the EntityRefs with the given name, that are located in the ToC. diff --git a/doc/TagsContainer.yml b/doc/TagsContainer.yml new file mode 100644 index 0000000..982bab1 --- /dev/null +++ b/doc/TagsContainer.yml @@ -0,0 +1,13 @@ +title: TagsContainer +filename: LDtkLoader/containers/TagsContainer.hpp +classes: + - name: TagsContainer + desc: Base class for objects that can be tagged. Tags are strings. + methods: + hasTag: + - decl: ldtk::TagsContainer::hasTag(const std::string&) const -> bool + desc: Returns `true` if the TagsContainer contains the given tag, returns `false` otherwise. + + allTags: + - decl: ldtk::TagsContainer::allTags() const -> const std::vector& + desc: Returns a vector containing all the tags in the TagsContainer. diff --git a/doc/Tileset.yml b/doc/Tileset.yml new file mode 100644 index 0000000..360baed --- /dev/null +++ b/doc/Tileset.yml @@ -0,0 +1,65 @@ +title: Tileset +filename: LDtkLoader/Tileset.hpp +classes: + - name: Tileset + desc: A Tileset contains information about a texture used to draw tiles. + parent: TagsContainer + fields: + name: + decl: const std::string ldtk::Tileset::name + desc: Name of the Tileset. + + uid: + decl: const int ldtk::Tileset::uid + desc: Unique identifier of the Tileset. + + path: + decl: const std::string ldtk::Tileset::path + desc: Relative path to the image file. + + texture_size: + decl: const ldtk::IntPoint ldtk::Tileset::texture_size + desc: Size in pixels of the Texture. + + tile_size: + decl: const int ldtk::Tileset::tile_size + desc: Size in pixels of a tile in the Tileset. Tiles are always square shaped. + + spacing: + decl: const int ldtk::Tileset::spacing + desc: Spacing in pixels between tiles in the Tileset. + + padding: + decl: const int ldtk::Tileset::padding + desc: Padding in pixels to the first row and column of tiles in the Tileset. + + methods: + getTileTexturePos: + - decl: ldtk::Tileset::getTileTexturePos(int tile_id) const -> ldtk::IntPoint + desc: Returns the texture coordinates of the tile ID. + + getTileCustomData: + - decl: | + ldtk::Tileset::getTileCustomData(int tile_id) const -> const std::string& + desc: Returns the custom data of the tile ID. + + getTileEnumTags: + - decl: | + ldtk::Tileset::getTileEnumTags(int tile_id) const -> const std::vector>& + desc: Returns a vector containing the EnumTags associated with the tile ID. + + hasEnumTags: + - decl: ldtk::Tileset::hasEnumTags() const -> bool + desc: Returns true if the Tileset has enum tags associated to tiles, false otherwise. + + getEnumTagsEnum: + - decl: ldtk::Tileset::getEnumTagsEnum() const -> const Enum& + desc: Returns the Enum type used by this Tileset's EnumTags. + + getTilesByEnumTag: + - decl: | + ldtk::Tileset::getTilesByEnumTag(const ldtk::EnumValue& enumvalue) const -> const std::vector& + desc: | + Get a vector of all tiles ID tagged with a given EnumValue. + + Throws an `invalid_argument` exception if the EnumValue argument does not have the right Enum type. \ No newline at end of file diff --git a/doc/World.yml b/doc/World.yml new file mode 100644 index 0000000..7eede23 --- /dev/null +++ b/doc/World.yml @@ -0,0 +1,86 @@ +title: World +filename: LDtkLoader/World.hpp +classes: + - name: World + desc: A World represents one world in the LDtk project and can contain one or multiple levels. + fields: + iid: + decl: const std::string ldtk::World::iid + desc: Unique instance ID of the World. + + methods: + getName: + - decl: ldtk::World::getName() const -> const std::string& + desc: Returns the name of the World. + + getDefaultPivot: + - decl: ldtk::World::getDefaultPivot() const -> const ldtk::FloatPoint& + desc: Returns the default pivot, a point from (0.f, 0.f) to (1.f, 1.f). + + getDefaultCellSize: + - decl: ldtk::World::getDefaultCellSize() const -> int + desc: Returns the default size of a cell in the World grid. Cell dimension is be `size`x`size`. + + getBgColor: + - decl: ldtk::World::getBgColor() const -> const ldtk::Color& + desc: Returns the default background color of the World. + + getLayout: + - decl: ldtk::World::getLayout() const -> const ldtk::WorldLayout& + desc: | + Returns the layout of the world (Free, GridVania, LinearHorizontal or LinearVertical). + + See WorldLayout enum. + + allTilesets: + - decl: ldtk::World::allTilesets() const -> const std::vector& + desc: Returns a vector containing all the Tilesets of the World. + + getTileset: + - decl: | + ldtk::World::getTileset(int id) const -> const ldtk::Tileset& + ldtk::World::getTileset(const std::string& name) const -> const ldtk::Tileset& + desc: | + Returns the Tileset matching the given `id` or `name`. + + If no Tileset is found, an `invalid_argument` exception is thrown. + + allLevels: + - decl: ldtk::World::allLevels() const -> const std::vector& + desc: Returns a vector containing all the Levels of the World. + + getLevel: + - decl: | + ldtk::World::getLevel(int id) const -> const ldtk::Level& + ldtk::World::getLevel(const std::string& name) const -> const ldtk::Level& + desc: | + Returns the Level matching the given `ìd` or `name`. + + If no Level is found, an `invalid_argument` exception is thrown. + + getEnum: + - decl: | + ldtk::World::getEnum(int id) const -> const ldtk::Enum& + ldtk::World::getEnum(const std::string& name) const -> const ldtk::Enum& + desc: | + Returns the Enum matching the given `id` or `name`. + + If no Enum is found, an `invalid_argument` exception is thrown. + + getLayerDef: + - decl: | + ldtk::World::getLayerDef(int id) const -> const ldtk::LayerDef& + ldtk::World::getLayerDef(const std::string& name) const -> const ldtk::LayerDef& + desc: | + Returns the LayerDef matching the given `id` or `name`. + + If no LayerDef is found, an `invalid_argument` exception is thrown. + + getEntityDef: + - decl: | + ldtk::World::getEntityDef(int id) const -> const ldtk::EntityDef& + ldtk::World::getEntityDef(const std::string& name) const -> const ldtk::EntityDef& + desc: | + Returns the EntityDef matching the given `id` or `name`. + + If no EntityDef is found, an `invalid_argument` exception is thrown. diff --git a/tools/generate_wiki.py b/tools/generate_wiki.py new file mode 100644 index 0000000..ff9a7c3 --- /dev/null +++ b/tools/generate_wiki.py @@ -0,0 +1,609 @@ +# Created by Modar Nasser on 08/06/2024 + +from dataclasses import dataclass +from os import listdir, makedirs +from os import path +from typing import Any, Optional, Union + +import yaml + +############################################################################### +# YAML parsing # +############################################################################### + +class YamlObj: + def __init__(self, *args, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, yaml2obj(v)) + def __iter__(self): + for k, v in self.__dict__.items(): + yield k, v + + +def yaml2obj(val) -> YamlObj: + if isinstance(val, dict): + return YamlObj(**val) + elif isinstance(val, list): + return [yaml2obj(e) for e in val] + elif isinstance(val, str): + return val.strip() + else: + return val + + +############################################################################### +# YAML deserialization # +############################################################################### + +@dataclass +class DocumentedType: + name: str + desc: str + page: 'Page' + + +@dataclass +class Member: + decl: str + desc: str + cls: 'Class' + + +@dataclass +class Field(Member): + pass + + +@dataclass +class Func(Member): + pass + + +@dataclass +class Class(DocumentedType): + ctor: list[Func] + fields: dict[str, Field] + methods: dict[str, list[Func]] + parent: Optional['Class'] + + @property + def methods_count(self): + return len(self.methods) + (len(self.parent.methods) if self.parent else 0) + + +@dataclass +class Struct(Class): + pass + + +@dataclass +class Enum(DocumentedType): + values: dict[int, str] + + +@dataclass +class Type(DocumentedType): + decl: str + + +@dataclass +class Page: + title: str + filename: str + classes: list[Class] + structs: list[Struct] + enums: list[Enum] + types: list[Type] + + @property + def documented_types_count(self): + return len(self.classes) + len(self.structs) + len(self.enums) + len(self.types) + + +def deserialize_yaml_obj(data: YamlObj) -> Page: + page = Page( + title=data.title, + filename=data.filename, + classes=[], + structs=[], + enums=[], + types=[] + ) + + def _deserialize_class_or_struct(obj: YamlObj, typename: type) -> Union[Class, Struct]: + c = typename( + name=obj.name, + desc=obj.desc, + page=page, + ctor=[], + fields={}, + methods={}, + parent=None + ) + + if hasattr(obj, 'parent'): + c.parent = obj.parent + + if hasattr(obj, 'ctor'): + for ctor in obj.ctor: + c.ctor.append(Func(decl=ctor.decl, desc=ctor.desc, cls=c)) + + if hasattr(obj, 'methods'): + for name, funcs in obj.methods: + c.methods[name] = [] + for func in funcs: + c.methods[name].append(Func(decl=func.decl, desc=func.desc, cls=c)) + + if hasattr(obj, 'fields'): + for name, field in obj.fields: + c.fields[name] = Field(decl=field.decl, desc=field.desc, cls=c) + + return c + + def _deserialize_enum(obj: YamlObj) -> Enum: + return Enum( + name=obj.name, + desc=obj.desc, + page=page, + values={val: name for name, val in obj.values} + ) + + def _deserialize_type(obj: YamlObj) -> Type: + return Type( + name=obj.name, + desc=obj.desc, + page=page, + decl=obj.decl + ) + + if hasattr(data, 'classes'): + for cls in data.classes: + page.classes.append(_deserialize_class_or_struct(cls, Class)) + + if hasattr(data, 'structs'): + for struct in data.structs: + page.structs.append(_deserialize_class_or_struct(struct, Struct)) + + if hasattr(data, 'enums'): + for enum in data.enums: + page.enums.append(_deserialize_enum(enum)) + + if hasattr(data, 'types'): + for type_ in data.types: + page.types.append(_deserialize_type(type_)) + + return page + + +############################################################################### +# Markdown linking # +############################################################################### + +def markdown_anchor(name: str, page: str = ''): + sanitized_name = name.translate({ord(c): None for c in '[]:<>`\\'}).replace(' ', '-') + return f'{page}#{sanitized_name}' + + +def mardown_link(text, anchor): + if anchor[0] == '#': + return f'[{text}]({anchor})' + else: + return f'[[{text}|{anchor}]]' + +@dataclass +class Ref: + obj: DocumentedType + page: str + + @property + def _prefix(self): + prefix = '' + if isinstance(self.obj, Struct): + prefix = 'struct : ldtk::' + elif isinstance(self.obj, Class): + prefix = 'class : ldtk::' + return prefix + + @property + def local_anchor(self) -> str: + return markdown_anchor(self._prefix + self.obj.name) + + @property + def external_anchor(self) -> str: + return markdown_anchor(self._prefix + self.obj.name, self.page.title) + + def link_from(self, referer: DocumentedType, replace: tuple[str, str] = ('', '')) -> str: + text = self.obj.name.replace(replace[0], replace[1]) + if referer.page == self.page: + return mardown_link(text, self.local_anchor) + else: + return mardown_link(text, self.external_anchor) + + +############################################################################### +# Markdown generation utilities # +############################################################################### + +class GenCtx: + def __init__(self, pages: list[Page], refs: list[Ref]): + self.pages = pages + self.refs = refs + self.page: Optional[Page] = None + self.current_type: Optional[DocumentedType] = None + + classes_table: dict[str, Class] = {} + for p in pages: + for c in p.classes + p.structs: + classes_table[c.name] = c + + for p in pages: + for c in p.classes + p.structs: + if c.parent: + c.parent = classes_table[c.parent] + + def link_refs(self, desc: str) -> str: + for ref in self.refs: + if ref.obj.name != self.current_type.name: + for prefix, suffix in [('', ' '), (' ', ' '), (' ', '.'), (' ', ','), (' ', 's')]: + fixed_name = prefix + ref.obj.name + suffix + fixed_link = prefix + ref.link_from(self.current_type) + suffix + desc = desc.replace(fixed_name, fixed_link) + + if '' in ref.obj.name: + name = ref.obj.name.replace('', '') + fixed_name = prefix + name + suffix + fixed_link = prefix + ref.link_from(self.current_type, replace=('', '')) + suffix + desc = desc.replace(fixed_name, fixed_link) + + return desc + + def link_ref(self, desc: str, referer: DocumentedType) -> str: + for ref in self.refs: + if ref.obj.name != referer.name: + desc = desc.replace(ref.obj.name, ref.link_from(referer)) + + return desc + + +class MarkdownGenerator: + def __init__(self, ctx: GenCtx): + self.ctx = ctx + self.lines = [] + + def _generate_goto_button(self, anchor: str): + self.lines += [ + f'

🔝

' + , f'' + ] + + def _generate_member(self, member: Member, inherited: bool = False): + decl = member.decl + desc = member.desc + + if inherited: + decl = member.decl.replace(member.cls.name, self.ctx.current_type.name) + desc = member.desc.replace(member.cls.name, self.ctx.current_type.name) + + decl = decl.split('\n') + decls = [] + i = 0 + while i < len(decl): + d = decl[i] + if d.count('template') > 0: + decls.append(d + '\n' + decl[i + 1]) + i += 2 + else: + decls.append(d) + i += 1 + + for decl in decls: + self.lines += [ + f'```c++' + , f'{decl.strip()}' + , f'```' + ] + + if decl.strip()[-1] == ';': + print(f'bad decl in {self.ctx.current_type.name}: {decl}') + + if desc: + desc = self.ctx.link_refs(desc) + # desc = desc.replace('\n', '\n\n') + self.lines += [ + f'{desc}' + , '' + ] + + if desc[-1] not in ['.', '`']: + print(f'bad desc in {self.ctx.current_type.name}: {desc}') + + def _generate_method(self, name: str, funcs: list[Func], inherited: bool = False): + sanitanized_name = name.replace('<', '\<').replace('>', '\>') + self.lines += [ + f'### {sanitanized_name}' + , f'' + ] + + if inherited: + self.lines += [ + f'> Inherited from {self.ctx.link_ref(self.ctx.current_type.parent.name, self.ctx.current_type)}' + , f'' + ] + + for func in funcs: + self._generate_member(func, inherited) + if func is not funcs[-1]: + self.lines += [ + '---' + , '' + ] + + def _generate_field(self, name: str, field: Field): + self.lines += [ + f'### {name}' + , f'' + ] + + self._generate_member(field) + + def _generate_index(self, members: list[str], indent_size = 0, anchor_prefix = ''): + indent = ' ' * indent_size + for name in members: + anchor = markdown_anchor(anchor_prefix + name) + self.lines += [f'{indent}- {mardown_link(name, anchor)}'] + self.lines += [f''] + + def _generate_class_or_struct(self, cls: Class): + self.ctx.current_type = cls + + typename = 'Class' + if isinstance(cls, Struct): + typename = 'Struct' + + self.lines += [ + f'# {typename} : `ldtk::{cls.name}`' + , f'' + ] + + if cls.desc: + self.lines += [ + self.ctx.link_refs(cls.desc), + '' + ] + + if [len(cls.ctor) > 0, len(cls.fields) > 0, cls.methods_count > 0].count(True) > 1: + if len(cls.ctor) > 0: + self.lines += [ + f'* {mardown_link("Constructor", markdown_anchor("Constructor"))}' + ] + + if len(cls.fields) > 0: + self.lines += [ + f'* {mardown_link("Fields", markdown_anchor("Fields"))}' + ] + + if len(cls.methods) > 0: + self.lines += [ + f'* {mardown_link("Methods", markdown_anchor("Methods"))}' + ] + + self.lines += [''] + + if cls.ctor: + self.lines += [ + f'## Constructor' + , f'' + ] + for ctor in cls.ctor: + self.lines += [ + f'```c++' + , ctor.decl + , f'```' + , ctor.desc + , f'' + ] + + if cls.fields: + self.lines += [ + f'## Fields' + , f'' + ] + + if len(cls.fields) > 3: + self._generate_index(['`' + name + '`' for name in cls.fields.keys()]) + + gen_goto_button_after_entry = len(cls.fields) > 4 + + for name, field in cls.fields.items(): + self._generate_field(name, field) + if gen_goto_button_after_entry: + self._generate_goto_button(markdown_anchor(f'{typename.lower()}--ldtk' + cls.name)) + + if not gen_goto_button_after_entry: + if cls.page.documented_types_count > 1: + self._generate_goto_button(markdown_anchor('')) + else: + self._generate_goto_button(markdown_anchor(f'{typename.lower()}--ldtk' + cls.name)) + + if cls.methods: + self.lines += [ + f'## Methods' + , f'' + ] + + if cls.methods_count > 3: + methods_list = ['`' + name + '`' for name in cls.methods.keys()] + if cls.parent: + methods_list += ['`' + name + '`' for name in cls.parent.methods.keys()] + self._generate_index(methods_list) + + gen_goto_button_after_entry = cls.methods_count > 4 + + for name, funcs in cls.methods.items(): + self._generate_method(name, funcs) + if gen_goto_button_after_entry: + self._generate_goto_button(markdown_anchor(f'{typename.lower()}--ldtk' + cls.name)) + + if cls.parent: + for name, funcs in cls.parent.methods.items(): + self._generate_method(name, funcs, inherited=True) + if gen_goto_button_after_entry: + self._generate_goto_button(markdown_anchor(f'{typename.lower()}--ldtk' + cls.name)) + + if not gen_goto_button_after_entry: + if cls.page.documented_types_count > 1: + self._generate_goto_button(markdown_anchor('')) + else: + self._generate_goto_button(markdown_anchor(f'{typename.lower()}--ldtk' + cls.name)) + + self.ctx.current_type = None + + def _generate_enum(self, enum: Enum): + self.ctx.current_type = enum + + self.lines += [ + f'# Enum : `ldtk::{enum.name}`' + , f'' + ] + + if enum.desc: + self.lines += [ + self.ctx.link_refs(enum.desc), + '' + ] + + for val, val_name in enum.values.items(): + self.lines += [ + f'{val}. `{enum.name}::{val_name}`', + ] + + self.lines += [''] + + self._generate_goto_button(markdown_anchor('')) + + def _generate_type(self, type_: Type): + self.ctx.current_type = type_ + + self.lines += [ + f'# Type : `ldtk::{type_.name}`' + , f'' + ] + + self.lines += [ + '```c++', + type_.decl, + '```', + ] + + if type_.desc: + self.lines += [ + self.ctx.link_refs(type_.desc), + ] + + self.lines += [''] + + self._generate_goto_button(markdown_anchor('')) + + def generate_page(self, page: Page): + self.ctx.page = page + + self.lines = [ + f'### In file `{page.filename}`' + , f'' + ] + + if len(page.classes) + len(page.structs) + len(page.enums) + len(page.types) > 1: + if page.classes: + self.lines += [ + f'* Classes', + ] + self._generate_index(['`ldtk::' + cls.name + '`' for cls in page.classes], 4, 'class : ') + + if page.structs: + self.lines += [ + f'* Structs', + ] + self._generate_index(['`ldtk::' + struct.name + '`' for struct in page.structs], 4, 'struct : ') + + if page.enums: + self.lines += [ + f'* Enums', + ] + self._generate_index(['`ldtk::' + enum.name + '`' for enum in page.enums], 4, 'enum : ') + + if page.types: + self.lines += [ + f'* Types', + ] + self._generate_index(['`ldtk::' + type_.name + '`' for type_ in page.types], 4, 'type : ') + + + for cls in page.classes: + self._generate_class_or_struct(cls) + + for struct in page.structs: + self._generate_class_or_struct(struct) + + for enum in page.enums: + self._generate_enum(enum) + + for type_ in page.types: + self._generate_type(type_) + + self.ctx.page = None + + @property + def generated_string(self) -> str: + return '\n'.join(self.lines) + + +if __name__ == '__main__': + DOC_DIR = 'doc' + GEN_DIR = 'wiki' + + refs : list[Ref] = [] + pages : list[Page] = [] + + directory = DOC_DIR + + for filename in listdir(directory): + if not path.isfile(path.join(directory, filename)): + continue + + if not filename.endswith('.yml'): + continue + + file_path = path.join(directory, filename) + + with open(file_path) as file: + yaml_obj = yaml2obj(yaml.safe_load(file)) + + page = deserialize_yaml_obj(yaml_obj) + + print(f'Processed {file_path}') + + # fill the references dict + for cls in page.classes: + refs.append(Ref(cls, page)) + + for struct in page.structs: + refs.append(Ref(struct, page)) + + for enum in page.enums: + refs.append(Ref(enum, page)) + + for type_ in page.types: + refs.append(Ref(type_, page)) + + pages.append(page) + + ctx = GenCtx(pages, refs) + + print(f'{len(refs)} refs found: {", ".join(sorted([ref.obj.name for ref in refs]))}') + + makedirs(GEN_DIR, exist_ok=True) + for page in pages: + with open(path.join(GEN_DIR, page.title + '.md'), 'w+') as md_file: + generator = MarkdownGenerator(ctx) + generator.generate_page(page) + md_file.write(generator.generated_string)