|
|
@ -1,59 +1,35 @@ |
|
|
|
import genanki |
|
|
|
import genanki |
|
|
|
import pandas as pd |
|
|
|
import pandas as pd |
|
|
|
|
|
|
|
from typing import List |
|
|
|
|
|
|
|
from functools import reduce |
|
|
|
|
|
|
|
|
|
|
|
from ankimaker.config import Config |
|
|
|
|
|
|
|
from ankimaker import generator, config |
|
|
|
from ankimaker import generator, config |
|
|
|
|
|
|
|
from ankimaker.config import Config, FilterConfig |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_model(): |
|
|
|
def load_csv(path: str) -> pd.DataFrame: |
|
|
|
my_model = genanki.Model( |
|
|
|
|
|
|
|
1607392319, |
|
|
|
|
|
|
|
'Simple Model', |
|
|
|
|
|
|
|
fields=[ |
|
|
|
|
|
|
|
{'name': 'Question'}, |
|
|
|
|
|
|
|
{'name': 'Answer'}, |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
templates=[ |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
'name': 'Card 1', |
|
|
|
|
|
|
|
'qfmt': '<div style="text-align: center;">{{Question}}</div>', |
|
|
|
|
|
|
|
'afmt': '{{FrontSide}}<hr id="answer"><div style="text-align: center;">{{Answer}}</div>', |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
] |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
return my_model |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_note(model, fields): |
|
|
|
|
|
|
|
note = genanki.Note( |
|
|
|
|
|
|
|
model=model, |
|
|
|
|
|
|
|
fields=fields |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
return note |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_csv(path): |
|
|
|
|
|
|
|
df = pd.read_csv(path, header=Config.header, sep=Config.separators) |
|
|
|
df = pd.read_csv(path, header=Config.header, sep=Config.separators) |
|
|
|
df_columns_are_unnamed = all(map(lambda x: str(x).isnumeric(), df.columns)) |
|
|
|
df_columns_are_unnamed = all(map(lambda x: str(x).isnumeric(), df.columns)) |
|
|
|
if df_columns_are_unnamed: |
|
|
|
if df_columns_are_unnamed: |
|
|
|
Config.answer_column = int(Config.answer_column) |
|
|
|
Config.answer_column = int(Config.answer_column) |
|
|
|
Config.question_column = int(Config.question_column) |
|
|
|
Config.question_column = int(Config.question_column) |
|
|
|
|
|
|
|
df = apply_filters(df) |
|
|
|
return df |
|
|
|
return df |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def add_df_to_deck(df: pd.DataFrame, deck: genanki.Deck): |
|
|
|
def add_df_to_deck(df: pd.DataFrame, deck: genanki.Deck) -> genanki.Deck: |
|
|
|
model = create_model() |
|
|
|
model = generator.create_model() |
|
|
|
|
|
|
|
|
|
|
|
for entry in df.to_dict('records'): |
|
|
|
for entry in df.to_dict('records'): |
|
|
|
question = entry[Config.question_column] |
|
|
|
question = entry[Config.question_column] |
|
|
|
answer = entry[Config.answer_column] |
|
|
|
answer = entry[Config.answer_column] |
|
|
|
content_fields = (question, answer) |
|
|
|
content_fields = (question, answer) |
|
|
|
note = create_note(model, fields=content_fields) |
|
|
|
note = generator.create_note(model, fields=content_fields) |
|
|
|
deck.add_note(note) |
|
|
|
deck.add_note(note) |
|
|
|
return deck |
|
|
|
return deck |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_config(config_file_path): |
|
|
|
def handle_config(config_file_path: str): |
|
|
|
if config_file_path is None: |
|
|
|
if config_file_path is None: |
|
|
|
Config.header = None |
|
|
|
Config.header = None |
|
|
|
Config.question_column = 0 |
|
|
|
Config.question_column = 0 |
|
|
@ -62,6 +38,60 @@ def handle_config(config_file_path): |
|
|
|
config.load_config_file(config_file_path) |
|
|
|
config.load_config_file(config_file_path) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def apply_filters(df: pd.DataFrame) -> pd.DataFrame: |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
Returns filtered dataframe removing any row that does not correspond to at least one |
|
|
|
|
|
|
|
of the filter groups defined in Configuration. |
|
|
|
|
|
|
|
:param df: Original dataframe. |
|
|
|
|
|
|
|
:return: Filtered Dataframe. |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
there_are_no_filter_to_apply = len(Config.filters) == 0 |
|
|
|
|
|
|
|
if there_are_no_filter_to_apply: |
|
|
|
|
|
|
|
return df |
|
|
|
|
|
|
|
is_in_configured_filter_rules = load_filter_from_config(df) |
|
|
|
|
|
|
|
df_filtered = df[is_in_configured_filter_rules] |
|
|
|
|
|
|
|
return df_filtered |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_filter_from_config(df: pd.DataFrame) -> pd.Series: |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
Given a dataframe, returns a series indicating which rows should be kept according to loaded |
|
|
|
|
|
|
|
Config [AnkimakerConfig]. The rows presented in any filter group should be kept. |
|
|
|
|
|
|
|
:param df: Original dataframe. |
|
|
|
|
|
|
|
:return pd.Series: Boolean Series to filter df. |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
group_filters: List[pd.Series] = list() |
|
|
|
|
|
|
|
for group in Config.filters: |
|
|
|
|
|
|
|
if len(group) > 0: |
|
|
|
|
|
|
|
group_filters.append( |
|
|
|
|
|
|
|
create_group_filter(df, group) |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
config_filter = reduce(lambda a, b: a | b, group_filters) |
|
|
|
|
|
|
|
return config_filter |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_group_filter(df: pd.DataFrame, group: List[FilterConfig]) -> pd.Series: |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
Creates a boolean series indicating which rows are in the filters configuration defined |
|
|
|
|
|
|
|
group to be used to filter the dataframe. |
|
|
|
|
|
|
|
:param df: Input dataframe to be filtered. |
|
|
|
|
|
|
|
:param group: Filter defined Group. |
|
|
|
|
|
|
|
:return: Series of boolean indicating rows that are in the group. |
|
|
|
|
|
|
|
""" |
|
|
|
|
|
|
|
rule: FilterConfig |
|
|
|
|
|
|
|
query: List[pd.Series] = list() |
|
|
|
|
|
|
|
for rule in group: |
|
|
|
|
|
|
|
__assert_rule_is_valid(df, rule) |
|
|
|
|
|
|
|
is_in_rule = df[rule.column].apply(lambda x: x in rule.values) |
|
|
|
|
|
|
|
query.append(is_in_rule) |
|
|
|
|
|
|
|
is_in_group = reduce(lambda a, b: a & b, query) |
|
|
|
|
|
|
|
return is_in_group |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __assert_rule_is_valid(df: pd.DataFrame, rule: FilterConfig): |
|
|
|
|
|
|
|
assert rule.column in df.columns |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def basic_pandas_to_anki(csv_path, output_path, name, config_file_path): |
|
|
|
def basic_pandas_to_anki(csv_path, output_path, name, config_file_path): |
|
|
|
handle_config(config_file_path) |
|
|
|
handle_config(config_file_path) |
|
|
|
df = load_csv(csv_path) |
|
|
|
df = load_csv(csv_path) |
|
|
|