inlcude_aoc is now the only method for importing data
Dieser Commit ist enthalten in:
Ursprung
313c6f4e3b
Commit
90d400eb37
21 geänderte Dateien mit 83 neuen und 232 gelöschten Zeilen
2
.gitignore
gevendort
2
.gitignore
gevendort
|
@ -2,4 +2,4 @@
|
|||
/data/**/*.txt
|
||||
!/data/2023/01a.txt
|
||||
!/data/2023/01b.txt
|
||||
!/data/20*/.gitkeep
|
||||
!/data/20*/.gitkeep
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use advent_of_code::include_aoc;
|
||||
use advent_of_code::strings::{convert_to_array, get_numbers, get_string_numbers};
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code::{include_data, include_example};
|
||||
|
||||
include_data!(DATAa 2023 01a);
|
||||
include_data!(DATAb 2023 01b);
|
||||
include_aoc!(DATAa, 2023, 01a);
|
||||
include_aoc!(DATAb, 2023, 01b);
|
||||
|
||||
fn main() {
|
||||
let mut sum: u32 = 0;
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use advent_of_code::include_aoc;
|
||||
use advent_of_code::strings::parsenumber;
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code::{include_data, include_example};
|
||||
|
||||
include_example!(DATA 2023 02);
|
||||
include_aoc!(2023, 02);
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Take {
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use advent_of_code::include_aoc;
|
||||
use advent_of_code::strings::{convert_to_array, parsenumber};
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code::{include_data, include_example};
|
||||
|
||||
include_data!(DATA 2024 01);
|
||||
include_aoc!(2024, 01);
|
||||
|
||||
pub fn splitspace(input: &str) -> (u32, u32) {
|
||||
println!("{}", input);
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
#[allow(unused_imports)]
|
||||
use advent_of_code::{include_data, include_example};
|
||||
use advent_of_code::include_aoc;
|
||||
use advent_of_code::{
|
||||
strings::{convert_to_array, parsenumber},
|
||||
KD,
|
||||
};
|
||||
|
||||
include_data!(DATA 2024 02);
|
||||
include_aoc!(2024, 02);
|
||||
|
||||
fn get_numbers(input: &str) -> Vec<u32> {
|
||||
input.split(char::is_whitespace).map(parsenumber).collect()
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use advent_of_code::include_aoc;
|
||||
use advent_of_code::strings::parsenumber;
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code::{include_data, include_example};
|
||||
use regex::{Captures, Regex};
|
||||
|
||||
include_data!(DATA 2024 03);
|
||||
include_aoc!(2024, 03);
|
||||
|
||||
fn parse_part1(input: Vec<Captures<'_>>) -> u32 {
|
||||
let mut sum = 0;
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
#![allow(unused_variables, unused_assignments)]
|
||||
#![allow(unused_imports)]
|
||||
use std::ops::Add;
|
||||
|
||||
use advent_of_code::{include_data, include_example};
|
||||
use advent_of_code::include_aoc;
|
||||
use advent_of_code::{
|
||||
strings::{convert_to_array, line_to_char},
|
||||
Kartesian, KartesianDirection, MaximumFromMap, Table,
|
||||
};
|
||||
|
||||
include_data!(DATA 2024 04);
|
||||
include_aoc!(2024, 04);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
|
||||
struct TextPos {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#[allow(unused_imports)]
|
||||
use advent_of_code::{
|
||||
include_data, include_example,
|
||||
include_aoc,
|
||||
strings::{convert_to_array, line_to_char},
|
||||
KartesianDirection,
|
||||
};
|
||||
|
@ -8,7 +7,7 @@ use advent_of_code::{Kartesian, Table};
|
|||
use log::debug;
|
||||
use num::Integer;
|
||||
|
||||
include_data!(DATA 2024 06);
|
||||
include_aoc!(2024, 06);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct DirectionalKartesian<T: Integer> {
|
||||
|
@ -139,7 +138,7 @@ fn main() {
|
|||
mod test {
|
||||
use super::*;
|
||||
|
||||
include_example!(EXAMPLE 2024 06);
|
||||
include_aoc!(EXAMPLE, 2024, 06, true);
|
||||
|
||||
#[test]
|
||||
fn test_solution_1() {
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
#![cfg_attr(debug_assertions, allow(unused_imports))]
|
||||
use std::ops::{Add, AddAssign, BitXor, Mul};
|
||||
use std::ops::{Add, AddAssign, Mul};
|
||||
|
||||
use advent_of_code::{
|
||||
numberlength,
|
||||
strings::{convert_to_array, parsenumber},
|
||||
};
|
||||
use advent_of_code_macros::{include_data, include_example};
|
||||
use log::*;
|
||||
use advent_of_code_macros::include_aoc;
|
||||
use num::{pow::Pow, Integer};
|
||||
|
||||
include_data!(DATA 2024 07);
|
||||
include_aoc!(2024, 07);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum Operation {
|
||||
|
|
|
@ -4,11 +4,10 @@ use advent_of_code::{
|
|||
strings::{convert_to_array, line_to_char},
|
||||
Kartesian, Table,
|
||||
};
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code_macros::{include_data, include_example};
|
||||
use advent_of_code_macros::include_aoc;
|
||||
use log::*;
|
||||
|
||||
include_data!(DATA 2024 08);
|
||||
include_aoc!(2024, 08);
|
||||
|
||||
const EMPTY: char = '.';
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use advent_of_code::strings::char_to_num;
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code_macros::{include_data, include_example};
|
||||
use advent_of_code_macros::include_aoc;
|
||||
|
||||
include_data!(DATA 2024 09);
|
||||
include_aoc!(2024, 09);
|
||||
|
||||
type Disk = Vec<Option<u64>>;
|
||||
|
||||
|
|
|
@ -2,10 +2,9 @@ use advent_of_code::{
|
|||
strings::{char_to_num, convert_to_array},
|
||||
Kartesian, KartesianDirection, MaximumFromMap,
|
||||
};
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code_macros::{include_data, include_example};
|
||||
use advent_of_code_macros::include_aoc;
|
||||
|
||||
include_data!(DATA 2024 10);
|
||||
include_aoc!(2024, 10);
|
||||
|
||||
type Trail = Vec<Kartesian<u32>>;
|
||||
type Map = Vec<Vec<u32>>;
|
||||
|
@ -96,7 +95,7 @@ fn main() {
|
|||
mod test {
|
||||
use super::*;
|
||||
|
||||
include_example!(MAP 2024 10);
|
||||
include_aoc!(MAP, 2024, 10, true);
|
||||
const SCORE: u32 = 36;
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#![allow(unused)]
|
||||
use std::collections::HashMap;
|
||||
|
||||
use advent_of_code::{
|
||||
|
@ -6,11 +5,10 @@ use advent_of_code::{
|
|||
strings::{convert_to_array, parsenumber},
|
||||
ExtendedOption,
|
||||
};
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code_macros::{include_data, include_example};
|
||||
use advent_of_code_macros::include_aoc;
|
||||
use num::Integer;
|
||||
|
||||
include_data!(DATA 2024 11);
|
||||
include_aoc!(2024, 11);
|
||||
|
||||
const BILLIONS: u64 = 1000_000_000;
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
#![allow(unused)]
|
||||
use std::ops::{Mul, Not};
|
||||
use std::ops::Mul;
|
||||
|
||||
use advent_of_code::{strings::convert_to_array, ExtendedOption, Kartesian, KartesianDirection, KartesianIterator, MaximumFromMap, Table, KD};
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code_macros::{include_data, include_example};
|
||||
use advent_of_code::{strings::convert_to_array, Kartesian, MaximumFromMap, Table, KD};
|
||||
use advent_of_code_macros::include_aoc;
|
||||
use log::*;
|
||||
use num::{iter, Integer};
|
||||
use num::Integer;
|
||||
|
||||
include_data!(DATA 2024 12);
|
||||
include_aoc!(2024, 12);
|
||||
|
||||
type Minimap = Vec<Vec<bool>>;
|
||||
|
||||
|
@ -269,11 +267,11 @@ fn main() {
|
|||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
include_example!(MAP_A 2024 12a);
|
||||
include_example!(MAP_B 2024 12b);
|
||||
include_example!(MAP_C 2024 12c);
|
||||
include_example!(MAP_D 2024 12d);
|
||||
include_example!(MAP_E 2024 12f);
|
||||
include_aoc!(MAP_A, 2024, 12a, true);
|
||||
include_aoc!(MAP_B, 2024, 12b, true);
|
||||
include_aoc!(MAP_C, 2024, 12c, true);
|
||||
include_aoc!(MAP_D, 2024, 12d, true);
|
||||
include_aoc!(MAP_E, 2024, 12f, true);
|
||||
|
||||
#[test]
|
||||
fn test_fence_a_perimeter() {
|
||||
|
|
|
@ -2,10 +2,9 @@ use advent_of_code::{
|
|||
strings::{convert_to_array, parsenumber},
|
||||
Kartesian, Velocity, KD,
|
||||
};
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code_macros::{include_data, include_example};
|
||||
use advent_of_code_macros::include_aoc;
|
||||
|
||||
include_data!(DATA 2024 14);
|
||||
include_aoc!(2024, 14);
|
||||
|
||||
fn parse_robot(line: &str) -> (Kartesian<u32>, Velocity<u32>) {
|
||||
let mut dir = KD::None;
|
||||
|
|
|
@ -3,11 +3,10 @@ use advent_of_code::{
|
|||
strings::{convert_to_array, line_to_char},
|
||||
Kartesian, KartesianDirection, Map,
|
||||
};
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code_macros::{include_data, include_example};
|
||||
use advent_of_code_macros::include_aoc;
|
||||
use log::debug;
|
||||
|
||||
include_data!(DATA 2024 15);
|
||||
include_aoc!(DATA 2024 15);
|
||||
|
||||
const WALL: char = '#';
|
||||
const ROBOT: char = '@';
|
||||
|
|
|
@ -5,10 +5,9 @@ use advent_of_code::{
|
|||
strings::{convert_to_array, line_to_char},
|
||||
Kartesian, KartesianDirection, Map, KD,
|
||||
};
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code_macros::{include_data, include_example};
|
||||
use advent_of_code_macros::include_aoc;
|
||||
|
||||
include_data!(DATA 2024 16);
|
||||
include_aoc!(DATA 2024 16);
|
||||
|
||||
const START: Option<char> = Some('S');
|
||||
const WALL: Option<char> = Some('#');
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use advent_of_code::strings::{convert_to_array, parsenumber};
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code_macros::{include_data, include_example};
|
||||
use advent_of_code_macros::include_aoc;
|
||||
use log::*;
|
||||
use thousands::Separable;
|
||||
|
||||
include_data!(DATA 2024 17);
|
||||
include_aoc!(DATA 2024 17);
|
||||
|
||||
const ADV: u64 = 0;
|
||||
const BXL: u64 = 1;
|
||||
|
|
|
@ -4,11 +4,10 @@ use advent_of_code::{
|
|||
strings::{convert_to_array, parsenumber},
|
||||
Kartesian, Map, ToCoords, KD,
|
||||
};
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code_macros::{include_data, include_example};
|
||||
use advent_of_code_macros::include_aoc;
|
||||
use log::*;
|
||||
|
||||
include_data!(DATA 2024 18);
|
||||
include_aoc!(DATA 2024 18);
|
||||
|
||||
const HEIGHT: usize = 71;
|
||||
const WIDTH: usize = HEIGHT;
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use advent_of_code::strings::convert_to_array;
|
||||
#[allow(unused_imports)]
|
||||
use advent_of_code_macros::{include_data, include_example};
|
||||
use advent_of_code_macros::include_aoc;
|
||||
|
||||
include_data!(DATA 2024 19);
|
||||
include_aoc!(2024, 19);
|
||||
|
||||
fn test_pattern(pattern: String, parts: Vec<String>) -> bool {
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
|
@ -17,27 +15,27 @@ fn main() {
|
|||
let mut possible = 0;
|
||||
for design in designs {
|
||||
let mut candidates = vec![design];
|
||||
'designloop :loop {
|
||||
let mut new = Vec::with_capacity(candidates.len());
|
||||
for possibility in candidates {
|
||||
for part in patterns.clone() {
|
||||
if possibility.starts_with(part.as_str()) {
|
||||
if possibility.len() == part.len() {
|
||||
println!("Found design");
|
||||
possible+=1;
|
||||
break 'designloop;
|
||||
'designloop: loop {
|
||||
let mut new = Vec::with_capacity(candidates.len());
|
||||
for possibility in candidates {
|
||||
for part in patterns.clone() {
|
||||
if possibility.starts_with(part.as_str()) {
|
||||
if possibility.len() == part.len() {
|
||||
println!("Found design");
|
||||
possible += 1;
|
||||
break 'designloop;
|
||||
}
|
||||
new.push(possibility[part.len()..].to_owned());
|
||||
}
|
||||
new.push(possibility[part.len()..].to_owned());
|
||||
}
|
||||
}
|
||||
new.sort();
|
||||
new.dedup();
|
||||
candidates = new;
|
||||
if candidates.len() == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
new.sort();
|
||||
new.dedup();
|
||||
candidates = new;
|
||||
if candidates.len() == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("There are {} possible designs", possible);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::fs::{self, read_to_string};
|
|||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{parse, LitBool, Result};
|
||||
use syn::{parse, token::Comma, LitBool, Result};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
|
@ -13,23 +13,6 @@ const DATA_DIR: &str = "data";
|
|||
const EXAMPLE_DIR: &str = "examples";
|
||||
const VAR_NAME: &str = "DATA";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct IncludeData {
|
||||
var: String,
|
||||
year: Option<String>,
|
||||
day: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for IncludeData {
|
||||
fn default() -> Self {
|
||||
IncludeData {
|
||||
var: VAR_NAME.into(),
|
||||
year: None,
|
||||
day: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_text<T: Spanned>(item: T) -> Result<String> {
|
||||
let span = item.span();
|
||||
match span.source_text() {
|
||||
|
@ -42,119 +25,8 @@ fn get_text<T: Spanned>(item: T) -> Result<String> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Parse for IncludeData {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut data = IncludeData::default();
|
||||
if input.peek(Ident) && !input.peek(LitInt) {
|
||||
data.var = get_text(input.parse::<Ident>().unwrap())?;
|
||||
}
|
||||
data.year = Some(get_text(input.parse::<LitInt>()?)?);
|
||||
data.day = Some(get_text(input.parse::<Lit>()?)?);
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
fn canonicalize(dir: &str, input: IncludeData) -> Option<String> {
|
||||
let pathname = format!("{}/{}/{}.txt", dir, input.year.unwrap(), input.day.unwrap());
|
||||
match fs::canonicalize(pathname) {
|
||||
Ok(canon) => {
|
||||
if canon.is_file() {
|
||||
match canon.to_str() {
|
||||
Some(c) => Some(c.to_string()),
|
||||
None => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// includes Data from Advent of code
|
||||
/// ```ignore (cannot-doctest-external-file-dependency)
|
||||
/// include_data!(DATA 2015 01)
|
||||
/// // or
|
||||
/// include_data!(2015 01)
|
||||
///
|
||||
/// fn main() {
|
||||
/// print!("{DATA}");
|
||||
/// }
|
||||
/// ```
|
||||
/// The Equivalent is
|
||||
/// ```ignore (cannot-doctest-external-file-dependency)
|
||||
/// static DATA: &str = include_str!("../../../data/2015/01.txt");
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn include_data(data: TokenStream) -> TokenStream {
|
||||
let input = match parse::<IncludeData>(data) {
|
||||
Ok(include) => include,
|
||||
Err(error) => return error.into_compile_error().into(),
|
||||
};
|
||||
let ident = format_ident!("{}", input.var);
|
||||
let mut comment: String;
|
||||
if input.day.is_some() && input.year.is_some() {
|
||||
comment = "Data from the data dir".into();
|
||||
let mut path = canonicalize(DATA_DIR, input.clone());
|
||||
if path.is_none() {
|
||||
comment = "Data from the examples".into();
|
||||
path = canonicalize(EXAMPLE_DIR, input)
|
||||
}
|
||||
match path {
|
||||
Some(p) => {
|
||||
return quote! {
|
||||
#[doc = #comment]
|
||||
const #ident: &str = include_str!(#p).trim_ascii();
|
||||
}
|
||||
.into();
|
||||
},
|
||||
None => comment = "failed to get data from the paths".into(),
|
||||
}
|
||||
} else {
|
||||
comment = format!("Failed to get the year({:?}) or day({:?}) falling back to default", input.year, input.day);
|
||||
}
|
||||
quote! {
|
||||
#[doc = #comment]
|
||||
const #ident: &str = "";
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Same as include_data!, but it only gets data from the example directory.
|
||||
/// This is useful for testing the examples, even when Data from the AOC is available
|
||||
#[proc_macro]
|
||||
pub fn include_example(data: TokenStream) -> TokenStream {
|
||||
let input = match parse::<IncludeData>(data) {
|
||||
Ok(include) => include,
|
||||
Err(error) => return error.into_compile_error().into(),
|
||||
};
|
||||
let ident = format_ident!("{}", input.var);
|
||||
let mut comment: String;
|
||||
if input.day.is_some() && input.year.is_some() {
|
||||
comment = "Data from the Example dir".into();
|
||||
match canonicalize(EXAMPLE_DIR, input.clone()) {
|
||||
Some(p) => {
|
||||
return quote! {
|
||||
#[doc = #comment]
|
||||
const #ident: &str = include_str!(#p).trim_ascii();
|
||||
}
|
||||
.into();
|
||||
},
|
||||
None => comment = "failed to get data from the path".into(),
|
||||
}
|
||||
} else {
|
||||
comment = format!("Failed to get the year({:?}) or day({:?}) falling back to default", input.year, input.day);
|
||||
}
|
||||
quote! {
|
||||
#[doc = #comment]
|
||||
const #ident: &str = "";
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct NewIncludeData {
|
||||
struct IncludeData {
|
||||
var: String,
|
||||
year: String,
|
||||
day: String,
|
||||
|
@ -162,20 +34,25 @@ struct NewIncludeData {
|
|||
parts: usize,
|
||||
}
|
||||
|
||||
impl Parse for NewIncludeData {
|
||||
impl Parse for IncludeData {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut v = Self::default();
|
||||
v.var = if input.peek(Ident) && !input.peek(LitInt) {
|
||||
get_text(input.parse::<Ident>().unwrap())?
|
||||
let t = get_text(input.parse::<Ident>().unwrap())?;
|
||||
input.parse::<Comma>()?;
|
||||
t
|
||||
} else {
|
||||
VAR_NAME.to_string()
|
||||
};
|
||||
v.year = get_text(input.parse::<LitInt>()?)?;
|
||||
input.parse::<Comma>()?;
|
||||
v.day = get_text(input.parse::<Lit>()?)?;
|
||||
if input.peek(LitBool) {
|
||||
if input.peek2(LitBool) {
|
||||
input.parse::<Comma>()?;
|
||||
v.example = input.parse::<LitBool>()?.value;
|
||||
}
|
||||
if input.peek(LitInt) {
|
||||
if input.peek2(LitInt) {
|
||||
input.parse::<Comma>()?;
|
||||
v.parts = input.parse::<LitInt>()?.base10_parse()?;
|
||||
}
|
||||
if v.parts == 0 {
|
||||
|
@ -185,12 +62,12 @@ impl Parse for NewIncludeData {
|
|||
}
|
||||
}
|
||||
|
||||
fn fallback(input: NewIncludeData, message: String) -> TokenStream {
|
||||
fn fallback(input: IncludeData, message: String) -> TokenStream {
|
||||
if input.parts != 1 {
|
||||
let postfixes = (1..=input.parts).map(|i| format_ident!("{}_{}", input.var, i));
|
||||
quote! {
|
||||
#[doc = #message]
|
||||
#(
|
||||
///
|
||||
const #postfixes: &str = "";
|
||||
)*
|
||||
}
|
||||
|
@ -198,7 +75,7 @@ fn fallback(input: NewIncludeData, message: String) -> TokenStream {
|
|||
} else {
|
||||
let name = format_ident!("{}", input.var);
|
||||
quote! {
|
||||
/// #name
|
||||
#[doc = #message]
|
||||
const #name: &str = "";
|
||||
}
|
||||
.into()
|
||||
|
@ -213,7 +90,7 @@ fn fallback(input: NewIncludeData, message: String) -> TokenStream {
|
|||
/// and the last is the amount of parts in the file, if it is greater than 1 the first argument is treated as an Prefix.
|
||||
#[proc_macro]
|
||||
pub fn include_aoc(data: TokenStream) -> TokenStream {
|
||||
let input = match parse::<NewIncludeData>(data) {
|
||||
let input = match parse::<IncludeData>(data) {
|
||||
Ok(data) => data,
|
||||
Err(error) => return error.into_compile_error().into(),
|
||||
};
|
||||
|
|
Laden …
Tabelle hinzufügen
In neuem Issue referenzieren