remove inlining
This commit is contained in:
parent
3de82cbacc
commit
f08f0fdc16
|
@ -9,7 +9,6 @@ pub struct Colour {
|
|||
pub struct InvalidRgb;
|
||||
|
||||
impl Colour {
|
||||
#[inline]
|
||||
pub(crate) fn from(string: &str) -> Result<Colour, InvalidRgb> {
|
||||
let values: Vec<&str> = string.trim_matches(',').split(',').collect();
|
||||
|
||||
|
@ -26,7 +25,6 @@ impl Colour {
|
|||
}
|
||||
|
||||
impl ToString for Colour {
|
||||
#[inline]
|
||||
fn to_string(&self) -> String {
|
||||
format!("{},{},{}", self.red, self.green, self.blue)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ impl From<String> for Dialogue {
|
|||
}
|
||||
|
||||
impl ToString for Dialogue {
|
||||
#[inline]
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
"DLG {}\n{}{}",
|
||||
|
|
|
@ -24,7 +24,6 @@ impl FromStr for Ending {
|
|||
}
|
||||
|
||||
impl fmt::Display for Ending {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f,"END {}\n{}", self.id, self.dialogue)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ pub enum Transition {
|
|||
}
|
||||
|
||||
impl From<&str> for Transition {
|
||||
#[inline]
|
||||
fn from(str: &str) -> Transition {
|
||||
match str {
|
||||
"fade_w" => Transition::FadeToWhite,
|
||||
|
|
27
src/game.rs
27
src/game.rs
|
@ -35,7 +35,6 @@ impl RoomFormat {
|
|||
pub enum RoomType {Room, Set}
|
||||
|
||||
impl ToString for RoomType {
|
||||
#[inline]
|
||||
fn to_string(&self) -> String {
|
||||
match &self {
|
||||
RoomType::Set => "SET",
|
||||
|
@ -54,7 +53,6 @@ pub struct Version {
|
|||
pub struct InvalidVersion;
|
||||
|
||||
impl Version {
|
||||
#[inline]
|
||||
fn from(str: &str) -> Result<Version, InvalidVersion> {
|
||||
let parts: Vec<&str> = str.split(".").collect();
|
||||
if parts.len() == 2 {
|
||||
|
@ -102,7 +100,6 @@ pub struct GameHasNoAvatar;
|
|||
// todo no tiles? no rooms? no palettes? turn this into an enum?
|
||||
|
||||
impl Game {
|
||||
#[inline]
|
||||
pub fn from(string: String) -> Result<Game, NotFound> {
|
||||
let line_endings_crlf = string.contains("\r\n");
|
||||
let mut string = string;
|
||||
|
@ -243,7 +240,6 @@ impl Game {
|
|||
}
|
||||
|
||||
/// todo refactor this into "get T by ID", taking a Vec<T> and an ID name?
|
||||
#[inline]
|
||||
pub fn get_sprite_by_id(&self, id: String) -> Result<&Sprite, NotFound> {
|
||||
let index = self.sprites.iter().position(
|
||||
|sprite| sprite.id == id
|
||||
|
@ -525,7 +521,6 @@ impl Game {
|
|||
}
|
||||
|
||||
impl ToString for Game {
|
||||
#[inline]
|
||||
fn to_string(&self) -> String {
|
||||
let mut segments: Vec<String> = Vec::new();
|
||||
|
||||
|
@ -585,43 +580,33 @@ impl ToString for Game {
|
|||
|
||||
impl Game {
|
||||
// todo dedupe
|
||||
|
||||
#[inline]
|
||||
pub fn palette_ids(&self) -> Vec<String> {
|
||||
self.palettes.iter().map(|palette| palette.id.clone()).collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tile_ids(&self) -> Vec<String> {
|
||||
self.tiles.iter().map(|tile| tile.id.clone()).collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sprite_ids(&self) -> Vec<String> {
|
||||
self.sprites.iter().map(|sprite| sprite.id.clone()).collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn room_ids(&self) -> Vec<String> {
|
||||
self.rooms.iter().map(|room| room.id.clone()).collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn item_ids(&self) -> Vec<String> {
|
||||
self.items.iter().map(|item| item.id.clone()).collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dialogue_ids(&self) -> Vec<String> {
|
||||
self.dialogues.iter().map(|dialogue| dialogue.id.clone()).collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ending_ids(&self) -> Vec<String> {
|
||||
self.endings.iter().map(|ending| ending.id.clone()).collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn variable_ids(&self) -> Vec<String> {
|
||||
self.variables.iter().map(|variable| variable.id.clone()).collect()
|
||||
}
|
||||
|
@ -635,7 +620,6 @@ impl Game {
|
|||
/// first available tile ID.
|
||||
/// e.g. if current tile IDs are [0, 2, 3] the result will be `1`
|
||||
/// if current tile IDs are [0, 1, 2] the result will be `3`
|
||||
#[inline]
|
||||
pub fn new_tile_id(&self) -> String {
|
||||
let mut ids = self.tile_ids();
|
||||
// don't allow 0 - this is a reserved ID for an implicit background tile
|
||||
|
@ -679,7 +663,6 @@ impl Game {
|
|||
}
|
||||
|
||||
/// adds a palette safely and returns the ID
|
||||
#[inline]
|
||||
pub fn add_palette(&mut self, mut palette: Palette) -> String {
|
||||
let new_id = try_id(&self.palette_ids(), &palette.id);
|
||||
if new_id != palette.id {
|
||||
|
@ -690,7 +673,6 @@ impl Game {
|
|||
}
|
||||
|
||||
/// adds a tile safely and returns the ID
|
||||
#[inline]
|
||||
pub fn add_tile(&mut self, mut tile: Tile) -> String {
|
||||
if tile.id == "0".to_string() || self.tile_ids().contains(&tile.id) {
|
||||
let new_id = self.new_tile_id();
|
||||
|
@ -704,7 +686,6 @@ impl Game {
|
|||
}
|
||||
|
||||
/// adds a sprite safely and returns the ID
|
||||
#[inline]
|
||||
pub fn add_sprite(&mut self, mut sprite: Sprite) -> String {
|
||||
let new_id = try_id(&self.sprite_ids(), &sprite.id);
|
||||
if new_id != sprite.id {
|
||||
|
@ -715,7 +696,6 @@ impl Game {
|
|||
}
|
||||
|
||||
/// adds an item safely and returns the ID
|
||||
#[inline]
|
||||
pub fn add_item(&mut self, mut item: Item) -> String {
|
||||
let new_id = try_id(&self.item_ids(), &item.id);
|
||||
if new_id != item.id {
|
||||
|
@ -726,7 +706,6 @@ impl Game {
|
|||
}
|
||||
|
||||
/// adds a dialogue safely and returns the ID
|
||||
#[inline]
|
||||
pub fn add_dialogue(&mut self, mut dialogue: Dialogue) -> String {
|
||||
let new_id = try_id(&self.dialogue_ids(), &dialogue.id);
|
||||
if new_id != dialogue.id {
|
||||
|
@ -798,7 +777,6 @@ impl Game {
|
|||
self.tiles = unique_tiles;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn version_line(&self) -> String {
|
||||
if self.version.is_some() {
|
||||
format!(
|
||||
|
@ -810,7 +788,6 @@ impl Game {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn room_format_line(&self) -> String {
|
||||
if self.room_format.is_some() {
|
||||
format!("\n\n! ROOM_FORMAT {}", self.room_format.unwrap().to_string())
|
||||
|
@ -819,7 +796,6 @@ impl Game {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn font_line(&self) -> String {
|
||||
if self.font == Font::AsciiSmall {
|
||||
"".to_string()
|
||||
|
@ -832,7 +808,6 @@ impl Game {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn text_direction_line(&self) -> &str {
|
||||
if self.text_direction == TextDirection::RightToLeft {
|
||||
"\n\nTEXT_DIRECTION RTL"
|
||||
|
@ -842,13 +817,11 @@ impl Game {
|
|||
}
|
||||
|
||||
/// older bitsy games do not specify a version, but we can infer 1.0
|
||||
#[inline]
|
||||
pub fn version(&self) -> Version {
|
||||
self.version.unwrap_or(Version { major: 1, minor: 0 })
|
||||
}
|
||||
|
||||
/// older bitsy games do not specify a room format, but we can infer 0
|
||||
#[inline]
|
||||
pub fn room_format(&self) -> RoomFormat {
|
||||
self.room_format.unwrap_or(RoomFormat::Contiguous)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ pub struct Image {
|
|||
}
|
||||
|
||||
impl From<String> for Image {
|
||||
#[inline]
|
||||
fn from(string: String) -> Image {
|
||||
let string = string.replace("NaN", "0");
|
||||
let string = string.trim();
|
||||
|
@ -24,7 +23,6 @@ impl From<String> for Image {
|
|||
}
|
||||
|
||||
impl ToString for Image {
|
||||
#[inline]
|
||||
fn to_string(&self) -> String {
|
||||
let mut string = String::new();
|
||||
|
||||
|
@ -42,7 +40,6 @@ impl ToString for Image {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn animation_frames_from_string(string: String) -> Vec<Image> {
|
||||
let frames: Vec<&str> = string.split(">").collect();
|
||||
|
||||
|
|
|
@ -11,24 +11,20 @@ pub struct Item {
|
|||
}
|
||||
|
||||
impl Item {
|
||||
#[inline]
|
||||
fn name_line(&self) -> String {
|
||||
optional_data_line("NAME", self.name.as_ref())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dialogue_line(&self) -> String {
|
||||
optional_data_line("DLG", self.dialogue_id.as_ref())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn colour_line(&self) -> String {
|
||||
optional_data_line("COL", self.colour_id.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Item {
|
||||
#[inline]
|
||||
fn from(string: String) -> Item {
|
||||
let mut lines: Vec<&str> = string.lines().collect();
|
||||
|
||||
|
@ -67,7 +63,6 @@ impl From<String> for Item {
|
|||
}
|
||||
|
||||
impl ToString for Item {
|
||||
#[inline]
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
"ITM {}\n{}{}{}{}",
|
||||
|
|
|
@ -59,7 +59,6 @@ pub trait AnimationFrames {
|
|||
}
|
||||
|
||||
impl AnimationFrames for Vec<Image> {
|
||||
#[inline]
|
||||
fn to_string(&self) -> String {
|
||||
let mut string = String::new();
|
||||
let last_frame = self.len() - 1;
|
||||
|
@ -77,7 +76,6 @@ impl AnimationFrames for Vec<Image> {
|
|||
}
|
||||
|
||||
/// this doesn't work inside ToBase36 for some reason
|
||||
#[inline]
|
||||
fn to_base36(int: u64) -> String {
|
||||
format!("{}", radix_36(int))
|
||||
}
|
||||
|
@ -87,14 +85,12 @@ pub trait ToBase36 {
|
|||
}
|
||||
|
||||
impl ToBase36 for u64 {
|
||||
#[inline]
|
||||
fn to_base36(&self) -> String {
|
||||
to_base36(*self)
|
||||
}
|
||||
}
|
||||
|
||||
/// e.g. `\nNAME DLG_0`
|
||||
#[inline]
|
||||
fn optional_data_line<T: Display>(label: &str, item: Option<T>) -> String {
|
||||
if item.is_some() {
|
||||
format!("\n{} {}", label, item.unwrap())
|
||||
|
@ -103,7 +99,6 @@ fn optional_data_line<T: Display>(label: &str, item: Option<T>) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn transform_line_endings(input: String, mode: TransformMode) -> String {
|
||||
let mut input = Cursor::new(input);
|
||||
let mut output = Cursor::new(Vec::new());
|
||||
|
@ -112,7 +107,6 @@ fn transform_line_endings(input: String, mode: TransformMode) -> String {
|
|||
String::from_utf8(output.into_inner()).unwrap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn segments_from_string(string: String) -> Vec<String> {
|
||||
// this is pretty weird but a dialogue can just have an empty line followed by a name
|
||||
// however, on entering two empty lines, dialogue will be wrapped in triple quotation marks
|
||||
|
@ -160,7 +154,6 @@ fn is_id_available(ids: &Vec<String>, id: &String) -> bool {
|
|||
}
|
||||
|
||||
/// e.g. pass all tile IDs into this to get a new non-conflicting tile ID
|
||||
#[inline]
|
||||
fn new_unique_id(ids: Vec<String>) -> String {
|
||||
let mut new_id: u64 = 0;
|
||||
|
||||
|
@ -176,7 +169,6 @@ pub trait Quote {
|
|||
}
|
||||
|
||||
impl Quote for String {
|
||||
#[inline]
|
||||
fn quote(&self) -> String {
|
||||
format!("\"\"\"\n{}\n\"\"\"", self).to_string()
|
||||
}
|
||||
|
@ -187,7 +179,6 @@ pub trait Unquote {
|
|||
}
|
||||
|
||||
impl Unquote for String {
|
||||
#[inline]
|
||||
fn unquote(&self) -> String {
|
||||
self.trim_matches('\"').trim_matches('\n').to_string()
|
||||
}
|
||||
|
|
11
src/mock.rs
11
src/mock.rs
|
@ -4,7 +4,6 @@ use crate::game::{RoomType, RoomFormat};
|
|||
pub mod image {
|
||||
use crate::Image;
|
||||
|
||||
#[inline]
|
||||
pub fn chequers_1() -> Image {
|
||||
Image {
|
||||
pixels: vec![
|
||||
|
@ -15,7 +14,6 @@ pub mod image {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn chequers_2() -> Image {
|
||||
Image {
|
||||
pixels: vec![
|
||||
|
@ -25,8 +23,7 @@ pub mod image {
|
|||
],
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
pub fn animation_frames() -> Vec<Image> {
|
||||
vec![
|
||||
Image {
|
||||
|
@ -81,7 +78,6 @@ pub mod image {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn avatar() -> Sprite {
|
||||
Sprite {
|
||||
id: "0".to_string(),
|
||||
|
@ -110,7 +106,6 @@ pub fn avatar() -> Sprite {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tile_default() -> Tile {
|
||||
Tile {
|
||||
id: "a".to_string(),
|
||||
|
@ -127,7 +122,6 @@ pub fn tile_default() -> Tile {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tile_background() -> Tile {
|
||||
Tile {
|
||||
id: "0".to_string(),
|
||||
|
@ -138,7 +132,6 @@ pub fn tile_background() -> Tile {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sprite() -> Sprite {
|
||||
Sprite {
|
||||
id: "a".to_string(),
|
||||
|
@ -210,7 +203,6 @@ pub fn item() -> Item {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn room() -> Room {
|
||||
Room {
|
||||
id: "a".to_string(),
|
||||
|
@ -514,7 +506,6 @@ pub fn room() -> Room {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn game_default() -> Game {
|
||||
Game {
|
||||
name: "Write your game's title here".to_string(),
|
||||
|
|
|
@ -8,7 +8,6 @@ pub struct Palette {
|
|||
}
|
||||
|
||||
impl From<String> for Palette {
|
||||
#[inline]
|
||||
fn from(string: String) -> Palette {
|
||||
let lines: Vec<&str> = string.lines().collect();
|
||||
|
||||
|
@ -31,7 +30,6 @@ impl From<String> for Palette {
|
|||
}
|
||||
|
||||
impl ToString for Palette {
|
||||
#[inline]
|
||||
fn to_string(&self) -> String {
|
||||
let name = if self.name.as_ref().is_some() {
|
||||
format!("NAME {}\n", self.name.as_ref().unwrap())
|
||||
|
|
|
@ -31,7 +31,6 @@ impl FromStr for Position {
|
|||
}
|
||||
|
||||
impl fmt::Display for Position {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{},{}", self.x, self.y)
|
||||
}
|
||||
|
|
|
@ -20,12 +20,10 @@ pub struct Room {
|
|||
}
|
||||
|
||||
impl Room {
|
||||
#[inline]
|
||||
fn name_line(&self) -> String {
|
||||
optional_data_line("NAME", self.name.as_ref())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn wall_line(&self) -> String {
|
||||
if self.walls.len() > 0 {
|
||||
optional_data_line("WAL", Some(self.walls.join(",")))
|
||||
|
@ -34,7 +32,6 @@ impl Room {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn palette_line(&self) -> String {
|
||||
if self.palette_id.is_some() {
|
||||
optional_data_line("PAL", Some(self.palette_id.as_ref().unwrap()))
|
||||
|
@ -45,7 +42,6 @@ impl Room {
|
|||
}
|
||||
|
||||
impl From<String> for Room {
|
||||
#[inline]
|
||||
fn from(string: String) -> Room {
|
||||
let string = string.replace("ROOM ", "");
|
||||
let string = string.replace("SET ", "");
|
||||
|
@ -159,7 +155,6 @@ impl From<String> for Room {
|
|||
}
|
||||
|
||||
impl Room {
|
||||
#[inline]
|
||||
pub fn to_string(&self, room_format: RoomFormat, room_type: RoomType) -> String {
|
||||
let mut tiles = String::new();
|
||||
let mut items = String::new();
|
||||
|
@ -226,7 +221,6 @@ impl Room {
|
|||
}
|
||||
|
||||
/// "changes" is a hash of old -> new tile IDs
|
||||
#[inline]
|
||||
pub fn change_tile_ids(&mut self, changes: &HashMap<String, String>) {
|
||||
self.tiles = self.tiles.iter().map(|tile_id|
|
||||
changes.get(tile_id).unwrap_or(tile_id).clone()
|
||||
|
|
|
@ -15,17 +15,14 @@ pub struct Sprite {
|
|||
}
|
||||
|
||||
impl Sprite {
|
||||
#[inline]
|
||||
fn name_line(&self) -> String {
|
||||
optional_data_line("NAME", self.name.as_ref())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dialogue_line(&self) -> String {
|
||||
optional_data_line("DLG", self.dialogue_id.as_ref())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn room_position_line(&self) -> String {
|
||||
if self.room_id.is_some() && self.position.is_some() {
|
||||
format!(
|
||||
|
@ -38,12 +35,10 @@ impl Sprite {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn colour_line(&self) -> String {
|
||||
optional_data_line("COL", self.colour_id.as_ref())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn item_lines(&self) -> String {
|
||||
if self.items.len() == 0 {
|
||||
"".to_string()
|
||||
|
@ -59,7 +54,6 @@ pub struct SpriteMissingRoomPosition;
|
|||
// todo "malformed sprite ID" or something
|
||||
|
||||
impl Sprite {
|
||||
#[inline]
|
||||
pub(crate) fn from(string: String) -> Result<Sprite, SpriteMissingRoomPosition> {
|
||||
let mut lines: Vec<&str> = string.lines().collect();
|
||||
|
||||
|
@ -118,7 +112,6 @@ impl Sprite {
|
|||
}
|
||||
|
||||
impl ToString for Sprite {
|
||||
#[inline]
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
"SPR {}\n{}{}{}{}{}{}",
|
||||
|
|
|
@ -9,7 +9,6 @@ pub enum Font {
|
|||
}
|
||||
|
||||
impl Font {
|
||||
#[inline]
|
||||
pub(crate) fn from(str: &str) -> Font {
|
||||
match str {
|
||||
"unicode_european_small" => Font::UnicodeEuropeanSmall,
|
||||
|
@ -20,7 +19,6 @@ impl Font {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn to_string(&self) -> Result<String, &'static str> {
|
||||
match &self {
|
||||
Font::UnicodeEuropeanSmall => Ok("unicode_european_small".to_string()),
|
||||
|
|
|
@ -23,12 +23,10 @@ impl PartialEq for Tile {
|
|||
}
|
||||
|
||||
impl Tile {
|
||||
#[inline]
|
||||
fn name_line(&self) -> String {
|
||||
optional_data_line("NAME", self.name.as_ref())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn wall_line(&self) -> String {
|
||||
if self.wall.is_some() {
|
||||
format!("\nWAL {}", self.wall.unwrap())
|
||||
|
@ -37,7 +35,6 @@ impl Tile {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn colour_line(&self) -> String {
|
||||
if self.colour_id.is_some() {
|
||||
format!("\nCOL {}", self.colour_id.unwrap())
|
||||
|
@ -48,7 +45,6 @@ impl Tile {
|
|||
}
|
||||
|
||||
impl From<String> for Tile {
|
||||
#[inline]
|
||||
fn from(string: String) -> Tile {
|
||||
let mut lines: Vec<&str> = string.lines().collect();
|
||||
|
||||
|
@ -88,7 +84,6 @@ impl From<String> for Tile {
|
|||
}
|
||||
|
||||
impl ToString for Tile {
|
||||
#[inline]
|
||||
fn to_string(&self) -> String {
|
||||
format!(
|
||||
"TIL {}\n{}{}{}{}",
|
||||
|
|
|
@ -5,7 +5,6 @@ pub struct Variable {
|
|||
}
|
||||
|
||||
impl From<String> for Variable {
|
||||
#[inline]
|
||||
fn from(string: String) -> Variable {
|
||||
let id_value: Vec<&str> = string.lines().collect();
|
||||
let id = id_value[0].replace("VAR ", "").to_string();
|
||||
|
@ -21,7 +20,6 @@ impl From<String> for Variable {
|
|||
}
|
||||
|
||||
impl ToString for Variable {
|
||||
#[inline]
|
||||
fn to_string(&self) -> String {
|
||||
format!("VAR {}\n{}", self.id, self.initial_value)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue